@elementor/editor-elements 4.1.0-726 → 4.1.0-727

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.
@@ -2,6 +2,7 @@ import { undoable } from '@elementor/editor-v1-adapters';
2
2
  import { __ } from '@wordpress/i18n';
3
3
 
4
4
  import { moveElement } from './move-element';
5
+ import { resolveContainer } from './resolve-element';
5
6
  import { type V1Element } from './types';
6
7
 
7
8
  type MoveOptions = {
@@ -30,6 +31,9 @@ type MovedElement = {
30
31
  originalIndex: number;
31
32
  targetContainer: V1Element;
32
33
  options?: MoveOptions;
34
+ elementId: string;
35
+ originalContainerId: string;
36
+ targetContainerId: string;
33
37
  };
34
38
 
35
39
  type MovedElementsResult = {
@@ -82,6 +86,9 @@ export const moveElements = ( {
82
86
  originalIndex,
83
87
  targetContainer: target,
84
88
  options,
89
+ elementId: newElement.id,
90
+ originalContainerId: originalContainer.id,
91
+ targetContainerId: target.id,
85
92
  } );
86
93
  } );
87
94
 
@@ -90,51 +97,68 @@ export const moveElements = ( {
90
97
  undo: ( _: { moves: MoveInput[] }, { movedElements }: MovedElementsResult ) => {
91
98
  onRestoreElements?.();
92
99
 
93
- [ ...movedElements ].reverse().forEach( ( { element, originalContainer, originalIndex } ) => {
94
- const freshElement = element.lookup?.();
95
- const freshOriginalContainer = originalContainer.lookup?.();
96
-
97
- if ( ! freshElement || ! freshOriginalContainer ) {
98
- return;
99
- }
100
-
101
- moveElement( {
102
- element: freshElement,
103
- targetContainer: freshOriginalContainer,
104
- options: {
105
- useHistory: false,
106
- at: originalIndex >= 0 ? originalIndex : undefined,
107
- },
100
+ // Fallback for async-rendered nested elements whose views may not exist yet (ED-22825).
101
+ [ ...movedElements ]
102
+ .reverse()
103
+ .forEach( ( { element, elementId, originalContainer, originalContainerId, originalIndex } ) => {
104
+ const freshElement = resolveContainer( element, elementId );
105
+ const freshOriginalContainer = resolveContainer( originalContainer, originalContainerId );
106
+
107
+ if ( ! freshElement || ! freshOriginalContainer ) {
108
+ return;
109
+ }
110
+
111
+ moveElement( {
112
+ element: freshElement,
113
+ targetContainer: freshOriginalContainer,
114
+ options: {
115
+ useHistory: false,
116
+ at: originalIndex >= 0 ? originalIndex : undefined,
117
+ },
118
+ } );
108
119
  } );
109
- } );
110
120
  },
111
121
  redo: ( _: { moves: MoveInput[] }, { movedElements }: MovedElementsResult ): MovedElementsResult => {
112
122
  const newMovedElements: MovedElement[] = [];
113
123
  onMoveElements?.();
114
124
 
115
- movedElements.forEach( ( { element, originalContainer, originalIndex, targetContainer, options } ) => {
116
- const freshElement = element.lookup?.();
117
- const freshOriginalContainer = originalContainer.lookup?.();
118
- const freshTarget = targetContainer.lookup?.();
119
-
120
- if ( ! freshElement || ! freshOriginalContainer || ! freshTarget ) {
121
- return;
122
- }
123
-
124
- const newElement = moveElement( {
125
- element: freshElement,
126
- targetContainer: freshTarget,
127
- options: { ...options, useHistory: false },
128
- } );
129
-
130
- newMovedElements.push( {
131
- element: newElement,
132
- originalContainer: freshOriginalContainer,
125
+ movedElements.forEach(
126
+ ( {
127
+ element,
128
+ elementId,
129
+ originalContainer,
130
+ originalContainerId,
133
131
  originalIndex,
134
- targetContainer: freshTarget,
132
+ targetContainer,
133
+ targetContainerId,
135
134
  options,
136
- } );
137
- } );
135
+ } ) => {
136
+ const freshElement = resolveContainer( element, elementId );
137
+ const freshOriginalContainer = resolveContainer( originalContainer, originalContainerId );
138
+ const freshTarget = resolveContainer( targetContainer, targetContainerId );
139
+
140
+ if ( ! freshElement || ! freshOriginalContainer || ! freshTarget ) {
141
+ return;
142
+ }
143
+
144
+ const newElement = moveElement( {
145
+ element: freshElement,
146
+ targetContainer: freshTarget,
147
+ options: { ...options, useHistory: false },
148
+ } );
149
+
150
+ newMovedElements.push( {
151
+ element: newElement,
152
+ originalContainer: freshOriginalContainer,
153
+ originalIndex,
154
+ targetContainer: freshTarget,
155
+ options,
156
+ elementId: newElement.id,
157
+ originalContainerId: freshOriginalContainer.id,
158
+ targetContainerId: freshTarget.id,
159
+ } );
160
+ }
161
+ );
138
162
 
139
163
  return { movedElements: newMovedElements };
140
164
  },
@@ -4,6 +4,7 @@ import { __ } from '@wordpress/i18n';
4
4
  import { createElement } from './create-element';
5
5
  import { deleteElement } from './delete-element';
6
6
  import { getContainer } from './get-container';
7
+ import { addModelToParent, removeModelFromParent, resolveContainer } from './resolve-element';
7
8
  import { type V1Element, type V1ElementModelProps } from './types';
8
9
 
9
10
  type RemoveElementsParams = {
@@ -19,6 +20,8 @@ type RemovedElement = {
19
20
  parent: V1Element;
20
21
  model: V1ElementModelProps;
21
22
  at: number;
23
+ containerId: string;
24
+ parentId: string;
22
25
  };
23
26
 
24
27
  type RemovedElementsResult = {
@@ -46,6 +49,8 @@ export const removeElements = ( {
46
49
  parent: container.parent,
47
50
  model: container.model.toJSON(),
48
51
  at: container.view?._index ?? 0,
52
+ containerId: container.id,
53
+ parentId: container.parent.id,
49
54
  } );
50
55
  }
51
56
  } );
@@ -64,8 +69,8 @@ export const removeElements = ( {
64
69
  undo: ( _: { elementIds: string[] }, { removedElements }: RemovedElementsResult ) => {
65
70
  onRestoreElements?.();
66
71
 
67
- [ ...removedElements ].reverse().forEach( ( { parent, model, at } ) => {
68
- const freshParent = parent.lookup?.();
72
+ [ ...removedElements ].reverse().forEach( ( { parent, parentId, model, at } ) => {
73
+ const freshParent = resolveContainer( parent, parentId );
69
74
 
70
75
  if ( freshParent ) {
71
76
  createElement( {
@@ -73,7 +78,11 @@ export const removeElements = ( {
73
78
  model,
74
79
  options: { useHistory: false, at },
75
80
  } );
81
+
82
+ return;
76
83
  }
84
+
85
+ addModelToParent( parentId, model, { at } );
77
86
  } );
78
87
  },
79
88
  redo: (
@@ -84,24 +93,37 @@ export const removeElements = ( {
84
93
 
85
94
  const newRemovedElements: RemovedElement[] = [];
86
95
 
87
- removedElements.forEach( ( { container, parent, model, at } ) => {
88
- const freshContainer = container.lookup?.();
89
- const freshParent = parent.lookup?.();
96
+ removedElements.forEach( ( { container, parent, model, at, containerId, parentId } ) => {
97
+ const freshContainer = resolveContainer( container, containerId );
98
+ const freshParent = resolveContainer( parent, parentId );
99
+
100
+ if ( freshContainer && freshParent ) {
101
+ deleteElement( {
102
+ container: freshContainer,
103
+ options: { useHistory: false },
104
+ } );
105
+
106
+ newRemovedElements.push( {
107
+ container: freshContainer,
108
+ parent: freshParent,
109
+ model,
110
+ at,
111
+ containerId,
112
+ parentId,
113
+ } );
90
114
 
91
- if ( ! freshContainer || ! freshParent ) {
92
115
  return;
93
116
  }
94
117
 
95
- deleteElement( {
96
- container: freshContainer,
97
- options: { useHistory: false },
98
- } );
118
+ removeModelFromParent( parentId, containerId );
99
119
 
100
120
  newRemovedElements.push( {
101
- container: freshContainer,
102
- parent: freshParent,
121
+ container,
122
+ parent,
103
123
  model,
104
124
  at,
125
+ containerId,
126
+ parentId,
105
127
  } );
106
128
  } );
107
129
 
@@ -0,0 +1,50 @@
1
+ import { getContainer } from './get-container';
2
+ import { type BackboneModel, type ExtendedWindow, type V1Element, type V1ElementModelProps } from './types';
3
+
4
+ function isConnected( container: V1Element | null | undefined ): container is V1Element {
5
+ if ( ! container ) {
6
+ return false;
7
+ }
8
+
9
+ if ( ! container.view?.el ) {
10
+ return true;
11
+ }
12
+
13
+ return container.view.el.isConnected;
14
+ }
15
+
16
+ export function resolveContainer( container: V1Element, id: string ): V1Element | null {
17
+ const looked = container.lookup?.();
18
+
19
+ if ( isConnected( looked ) ) {
20
+ return looked;
21
+ }
22
+
23
+ const byId = getContainer( id );
24
+
25
+ if ( isConnected( byId ) ) {
26
+ return byId;
27
+ }
28
+
29
+ return null;
30
+ }
31
+
32
+ function getDocumentUtils() {
33
+ return ( window as unknown as ExtendedWindow ).$e?.components?.get?.( 'document' )?.utils;
34
+ }
35
+
36
+ export function findModelInDocument( id: string ): BackboneModel | null {
37
+ return getDocumentUtils()?.findModelById?.( id ) ?? null;
38
+ }
39
+
40
+ export function addModelToParent(
41
+ parentId: string,
42
+ childData: V1ElementModelProps,
43
+ options?: { at?: number }
44
+ ): boolean {
45
+ return getDocumentUtils()?.addModelToParent?.( parentId, childData, options ) ?? false;
46
+ }
47
+
48
+ export function removeModelFromParent( parentId: string, childId: string ): boolean {
49
+ return getDocumentUtils()?.removeModelFromParent?.( parentId, childId ) ?? false;
50
+ }
package/src/sync/types.ts CHANGED
@@ -4,6 +4,17 @@ import { type ClassState, type StyleDefinition, type StyleDefinitionID } from '@
4
4
  import { type ControlItem, type PseudoState } from '../types';
5
5
 
6
6
  export type ExtendedWindow = Window & {
7
+ $e?: {
8
+ components?: {
9
+ get?: ( name: string ) => {
10
+ utils?: {
11
+ findModelById?: ( id: string, collection?: unknown ) => BackboneModel | null;
12
+ addModelToParent?: ( parentId: string, childData: unknown, options?: { at?: number } ) => boolean;
13
+ removeModelFromParent?: ( parentId: string, childId: string ) => boolean;
14
+ };
15
+ };
16
+ };
17
+ };
7
18
  elementor?: {
8
19
  selection?: {
9
20
  getElements: () => V1Element[];
@@ -18,6 +29,9 @@ export type ExtendedWindow = Window & {
18
29
  getCurrentId?: () => number;
19
30
  };
20
31
  getContainer?: ( id: string ) => V1Element | undefined;
32
+ helpers?: {
33
+ isAtomicWidget?: ( model: unknown ) => boolean;
34
+ };
21
35
  };
22
36
  elementorCommon?: {
23
37
  helpers?: {
@@ -26,6 +40,19 @@ export type ExtendedWindow = Window & {
26
40
  };
27
41
  };
28
42
 
43
+ export type BackboneModel = {
44
+ get: ( key: string ) => unknown;
45
+ set: ( key: string, value: unknown ) => void;
46
+ toJSON: () => Record< string, unknown >;
47
+ };
48
+
49
+ export type BackboneCollection = {
50
+ models: BackboneModel[];
51
+ add: ( model: unknown, options?: Record< string, unknown > ) => void;
52
+ remove: ( model: BackboneModel, options?: Record< string, unknown > ) => void;
53
+ findWhere: ( attrs: Record< string, unknown > ) => BackboneModel | undefined;
54
+ };
55
+
29
56
  export type V1Element = {
30
57
  id: string;
31
58
  model: V1Model< V1ElementModelProps >;