@elementor/frontend-handlers 4.0.0-547 → 4.0.0-549

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.js CHANGED
@@ -79,8 +79,7 @@ var unregisterBySelector = ({ selector, id }) => {
79
79
  };
80
80
 
81
81
  // src/lifecycle-events.ts
82
- var unmountElementTypeCallbacks = /* @__PURE__ */ new Map();
83
- var unmountElementSelectorCallbacks = /* @__PURE__ */ new Map();
82
+ var unmountCallbacks = /* @__PURE__ */ new WeakMap();
84
83
  var ELEMENT_RENDERED_EVENT_NAME = "elementor/element/rendered";
85
84
  var ELEMENT_DESTROYED_EVENT_NAME = "elementor/element/destroyed";
86
85
  var dispatchDestroyedEvent = (params) => {
@@ -96,10 +95,11 @@ var onElementRender = ({
96
95
  elementType,
97
96
  elementId
98
97
  }) => {
98
+ cleanupOnUnmount(element);
99
99
  const controller = new AbortController();
100
100
  const manualUnmount = [];
101
101
  const dispatchRenderedEvent = () => {
102
- onElementSelectorRender({ element, elementId, controller });
102
+ onElementSelectorRender({ element, controller });
103
103
  element.dispatchEvent(
104
104
  new CustomEvent(ELEMENT_RENDERED_EVENT_NAME, {
105
105
  bubbles: true,
@@ -121,6 +121,7 @@ var onElementRender = ({
121
121
  if (!elementTypeHandlers.has(elementType)) {
122
122
  return;
123
123
  }
124
+ setUnmountEntry({ element, controller, manualUnmount });
124
125
  Array.from(elementTypeHandlers.get(elementType)?.values() ?? []).forEach((handler) => {
125
126
  const settings = element.getAttribute("data-e-settings");
126
127
  const listenToChildren = (elementTypes) => ({
@@ -146,24 +147,18 @@ var onElementRender = ({
146
147
  manualUnmount.push(unmount);
147
148
  }
148
149
  });
149
- if (!unmountElementTypeCallbacks.has(elementType)) {
150
- unmountElementTypeCallbacks.set(elementType, /* @__PURE__ */ new Map());
151
- }
152
- unmountElementTypeCallbacks.get(elementType)?.set(elementId, () => {
153
- controller.abort();
154
- manualUnmount.forEach((callback) => callback());
155
- });
156
150
  };
157
151
  var onElementSelectorRender = ({
158
152
  element,
159
- elementId,
160
153
  controller
161
154
  }) => {
155
+ let requiresCleanup = false;
156
+ const manualUnmount = [];
162
157
  Array.from(elementSelectorHandlers.entries() ?? []).forEach(([selector, handlers]) => {
163
158
  if (!element.matches(selector)) {
164
159
  return;
165
160
  }
166
- const manualUnmount = [];
161
+ requiresCleanup = true;
167
162
  Array.from(handlers.values() ?? []).forEach((handler) => {
168
163
  const settings = element.getAttribute("data-e-settings");
169
164
  const unmount = handler({
@@ -175,43 +170,40 @@ var onElementSelectorRender = ({
175
170
  manualUnmount.push(unmount);
176
171
  }
177
172
  });
178
- if (!manualUnmount.length) {
179
- return;
180
- }
181
- if (!unmountElementSelectorCallbacks.get(elementId)) {
182
- unmountElementSelectorCallbacks.set(elementId, /* @__PURE__ */ new Map());
183
- }
184
- unmountElementSelectorCallbacks.get(elementId)?.set(selector, () => {
185
- controller.abort();
186
- manualUnmount.forEach((callback) => callback());
187
- });
188
173
  });
174
+ if (requiresCleanup) {
175
+ setUnmountEntry({ element, controller, manualUnmount });
176
+ }
189
177
  };
190
178
  var onElementDestroy = ({
191
179
  elementType,
192
180
  elementId,
193
181
  element
194
182
  }) => {
195
- const unmount = unmountElementTypeCallbacks.get(elementType)?.get(elementId);
196
- const unmountSelector = unmountElementSelectorCallbacks.get(elementId);
197
- if (element) {
198
- dispatchDestroyedEvent({ element, elementType, elementId });
199
- }
200
- if (unmount) {
201
- unmount();
202
- }
203
- if (unmountSelector?.size) {
204
- Array.from(unmountSelector.values()).forEach((selectorUnmount) => {
205
- selectorUnmount();
206
- });
183
+ if (!element) {
184
+ return;
207
185
  }
208
- unmountElementTypeCallbacks.get(elementType)?.delete(elementId);
209
- unmountElementSelectorCallbacks.delete(elementId);
210
- if (unmountElementTypeCallbacks.get(elementType)?.size === 0) {
211
- unmountElementTypeCallbacks.delete(elementType);
186
+ cleanupOnUnmount(element);
187
+ dispatchDestroyedEvent({ element, elementType, elementId });
188
+ };
189
+ var setUnmountEntry = ({
190
+ element,
191
+ controller,
192
+ manualUnmount
193
+ }) => {
194
+ const existingEntry = unmountCallbacks.get(element);
195
+ if (existingEntry) {
196
+ existingEntry.manualUnmount.push(...manualUnmount);
197
+ } else {
198
+ unmountCallbacks.set(element, { controller, manualUnmount });
212
199
  }
213
- if (unmountElementSelectorCallbacks.size === 0) {
214
- unmountElementSelectorCallbacks.delete(elementId);
200
+ };
201
+ var cleanupOnUnmount = (element) => {
202
+ const entry = unmountCallbacks.get(element);
203
+ if (entry) {
204
+ entry.controller.abort();
205
+ entry.manualUnmount.forEach((callback) => callback());
206
+ unmountCallbacks.delete(element);
215
207
  }
216
208
  };
217
209
 
@@ -220,7 +212,6 @@ function init() {
220
212
  window.addEventListener("elementor/element/render", (_event) => {
221
213
  const event = _event;
222
214
  const { id, type, element } = event.detail;
223
- onElementDestroy({ elementType: type, elementId: id });
224
215
  onElementRender({ element, elementType: type, elementId: id });
225
216
  });
226
217
  window.addEventListener("elementor/element/destroy", (_event) => {
package/dist/index.mjs CHANGED
@@ -49,8 +49,7 @@ var unregisterBySelector = ({ selector, id }) => {
49
49
  };
50
50
 
51
51
  // src/lifecycle-events.ts
52
- var unmountElementTypeCallbacks = /* @__PURE__ */ new Map();
53
- var unmountElementSelectorCallbacks = /* @__PURE__ */ new Map();
52
+ var unmountCallbacks = /* @__PURE__ */ new WeakMap();
54
53
  var ELEMENT_RENDERED_EVENT_NAME = "elementor/element/rendered";
55
54
  var ELEMENT_DESTROYED_EVENT_NAME = "elementor/element/destroyed";
56
55
  var dispatchDestroyedEvent = (params) => {
@@ -66,10 +65,11 @@ var onElementRender = ({
66
65
  elementType,
67
66
  elementId
68
67
  }) => {
68
+ cleanupOnUnmount(element);
69
69
  const controller = new AbortController();
70
70
  const manualUnmount = [];
71
71
  const dispatchRenderedEvent = () => {
72
- onElementSelectorRender({ element, elementId, controller });
72
+ onElementSelectorRender({ element, controller });
73
73
  element.dispatchEvent(
74
74
  new CustomEvent(ELEMENT_RENDERED_EVENT_NAME, {
75
75
  bubbles: true,
@@ -91,6 +91,7 @@ var onElementRender = ({
91
91
  if (!elementTypeHandlers.has(elementType)) {
92
92
  return;
93
93
  }
94
+ setUnmountEntry({ element, controller, manualUnmount });
94
95
  Array.from(elementTypeHandlers.get(elementType)?.values() ?? []).forEach((handler) => {
95
96
  const settings = element.getAttribute("data-e-settings");
96
97
  const listenToChildren = (elementTypes) => ({
@@ -116,24 +117,18 @@ var onElementRender = ({
116
117
  manualUnmount.push(unmount);
117
118
  }
118
119
  });
119
- if (!unmountElementTypeCallbacks.has(elementType)) {
120
- unmountElementTypeCallbacks.set(elementType, /* @__PURE__ */ new Map());
121
- }
122
- unmountElementTypeCallbacks.get(elementType)?.set(elementId, () => {
123
- controller.abort();
124
- manualUnmount.forEach((callback) => callback());
125
- });
126
120
  };
127
121
  var onElementSelectorRender = ({
128
122
  element,
129
- elementId,
130
123
  controller
131
124
  }) => {
125
+ let requiresCleanup = false;
126
+ const manualUnmount = [];
132
127
  Array.from(elementSelectorHandlers.entries() ?? []).forEach(([selector, handlers]) => {
133
128
  if (!element.matches(selector)) {
134
129
  return;
135
130
  }
136
- const manualUnmount = [];
131
+ requiresCleanup = true;
137
132
  Array.from(handlers.values() ?? []).forEach((handler) => {
138
133
  const settings = element.getAttribute("data-e-settings");
139
134
  const unmount = handler({
@@ -145,43 +140,40 @@ var onElementSelectorRender = ({
145
140
  manualUnmount.push(unmount);
146
141
  }
147
142
  });
148
- if (!manualUnmount.length) {
149
- return;
150
- }
151
- if (!unmountElementSelectorCallbacks.get(elementId)) {
152
- unmountElementSelectorCallbacks.set(elementId, /* @__PURE__ */ new Map());
153
- }
154
- unmountElementSelectorCallbacks.get(elementId)?.set(selector, () => {
155
- controller.abort();
156
- manualUnmount.forEach((callback) => callback());
157
- });
158
143
  });
144
+ if (requiresCleanup) {
145
+ setUnmountEntry({ element, controller, manualUnmount });
146
+ }
159
147
  };
160
148
  var onElementDestroy = ({
161
149
  elementType,
162
150
  elementId,
163
151
  element
164
152
  }) => {
165
- const unmount = unmountElementTypeCallbacks.get(elementType)?.get(elementId);
166
- const unmountSelector = unmountElementSelectorCallbacks.get(elementId);
167
- if (element) {
168
- dispatchDestroyedEvent({ element, elementType, elementId });
169
- }
170
- if (unmount) {
171
- unmount();
172
- }
173
- if (unmountSelector?.size) {
174
- Array.from(unmountSelector.values()).forEach((selectorUnmount) => {
175
- selectorUnmount();
176
- });
153
+ if (!element) {
154
+ return;
177
155
  }
178
- unmountElementTypeCallbacks.get(elementType)?.delete(elementId);
179
- unmountElementSelectorCallbacks.delete(elementId);
180
- if (unmountElementTypeCallbacks.get(elementType)?.size === 0) {
181
- unmountElementTypeCallbacks.delete(elementType);
156
+ cleanupOnUnmount(element);
157
+ dispatchDestroyedEvent({ element, elementType, elementId });
158
+ };
159
+ var setUnmountEntry = ({
160
+ element,
161
+ controller,
162
+ manualUnmount
163
+ }) => {
164
+ const existingEntry = unmountCallbacks.get(element);
165
+ if (existingEntry) {
166
+ existingEntry.manualUnmount.push(...manualUnmount);
167
+ } else {
168
+ unmountCallbacks.set(element, { controller, manualUnmount });
182
169
  }
183
- if (unmountElementSelectorCallbacks.size === 0) {
184
- unmountElementSelectorCallbacks.delete(elementId);
170
+ };
171
+ var cleanupOnUnmount = (element) => {
172
+ const entry = unmountCallbacks.get(element);
173
+ if (entry) {
174
+ entry.controller.abort();
175
+ entry.manualUnmount.forEach((callback) => callback());
176
+ unmountCallbacks.delete(element);
185
177
  }
186
178
  };
187
179
 
@@ -190,7 +182,6 @@ function init() {
190
182
  window.addEventListener("elementor/element/render", (_event) => {
191
183
  const event = _event;
192
184
  const { id, type, element } = event.detail;
193
- onElementDestroy({ elementType: type, elementId: id });
194
185
  onElementRender({ element, elementType: type, elementId: id });
195
186
  });
196
187
  window.addEventListener("elementor/element/destroy", (_event) => {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@elementor/frontend-handlers",
3
3
  "description": "Elementor Frontend Handlers",
4
- "version": "4.0.0-547",
4
+ "version": "4.0.0-549",
5
5
  "private": false,
6
6
  "author": "Elementor Team",
7
7
  "homepage": "https://elementor.com/",
@@ -232,7 +232,7 @@ describe( 'Frontend Handlers', () => {
232
232
 
233
233
  window.dispatchEvent(
234
234
  new CustomEvent( 'elementor/element/destroy', {
235
- detail: { id: ELEMENT_ID, type: WIDGET_ELEMENT_TYPE },
235
+ detail: { id: ELEMENT_ID, type: WIDGET_ELEMENT_TYPE, element },
236
236
  } )
237
237
  );
238
238
 
@@ -686,6 +686,52 @@ describe( 'Frontend Handlers', () => {
686
686
  expect( callbackCounts.get( PARENT_1_ID ) ).toBe( 1 );
687
687
  expect( callbackCounts.get( PARENT_2_ID ) ).toBe( 1 );
688
688
  } );
689
+
690
+ it( 'should not abort handlers of first element when rendering second element with same ID', () => {
691
+ // Arrange
692
+ const SHARED_ID = 'shared-internal-id';
693
+ let element1Signal: AbortSignal | undefined;
694
+ let element2Signal: AbortSignal | undefined;
695
+
696
+ const element1 = document.createElement( 'div' );
697
+ element1.setAttribute( 'data-e-type', WIDGET_ELEMENT_TYPE );
698
+ element1.setAttribute( 'data-id', SHARED_ID );
699
+ document.body.appendChild( element1 );
700
+
701
+ const element2 = document.createElement( 'div' );
702
+ element2.setAttribute( 'data-e-type', WIDGET_ELEMENT_TYPE );
703
+ element2.setAttribute( 'data-id', SHARED_ID );
704
+ document.body.appendChild( element2 );
705
+
706
+ register( {
707
+ elementType: WIDGET_ELEMENT_TYPE,
708
+ id: HANDLER_IDS.handler_1,
709
+ callback: ( { element, signal } ) => {
710
+ if ( element === element1 ) {
711
+ element1Signal = signal;
712
+ } else if ( element === element2 ) {
713
+ element2Signal = signal;
714
+ }
715
+ return undefined;
716
+ },
717
+ } );
718
+
719
+ // Act - Render elements
720
+ window.dispatchEvent(
721
+ new CustomEvent( 'elementor/element/render', {
722
+ detail: { id: SHARED_ID, type: WIDGET_ELEMENT_TYPE, element: element1 },
723
+ } )
724
+ );
725
+ window.dispatchEvent(
726
+ new CustomEvent( 'elementor/element/render', {
727
+ detail: { id: SHARED_ID, type: WIDGET_ELEMENT_TYPE, element: element2 },
728
+ } )
729
+ );
730
+
731
+ // Assert - Both signals should be active (not aborted)
732
+ expect( element1Signal?.aborted ).toBe( false );
733
+ expect( element2Signal?.aborted ).toBe( false );
734
+ } );
689
735
  } );
690
736
 
691
737
  describe( 'DOMContentLoaded Initialization', () => {
package/src/init.ts CHANGED
@@ -5,9 +5,6 @@ export function init() {
5
5
  const event = _event as CustomEvent< { id: string; type: string; element: Element } >;
6
6
  const { id, type, element } = event.detail;
7
7
 
8
- // Ensure the "destroy" event was not triggered before the render event.
9
- onElementDestroy( { elementType: type, elementId: id } );
10
-
11
8
  onElementRender( { element, elementType: type, elementId: id } );
12
9
  } );
13
10
 
@@ -1,7 +1,11 @@
1
1
  import { elementSelectorHandlers, elementTypeHandlers } from './handlers-registry';
2
2
 
3
- const unmountElementTypeCallbacks: Map< string, Map< string, () => void > > = new Map();
4
- const unmountElementSelectorCallbacks: Map< string, Map< string, () => void > > = new Map();
3
+ type UnmountEntry = {
4
+ controller: AbortController;
5
+ manualUnmount: ( () => void )[];
6
+ };
7
+
8
+ const unmountCallbacks = new WeakMap< Element, UnmountEntry >();
5
9
 
6
10
  const ELEMENT_RENDERED_EVENT_NAME = 'elementor/element/rendered';
7
11
  const ELEMENT_DESTROYED_EVENT_NAME = 'elementor/element/destroyed';
@@ -30,11 +34,13 @@ export const onElementRender = ( {
30
34
  elementType: string;
31
35
  elementId: string;
32
36
  } ) => {
37
+ cleanupOnUnmount( element );
38
+
33
39
  const controller = new AbortController();
34
40
  const manualUnmount: ( () => void )[] = [];
35
41
 
36
42
  const dispatchRenderedEvent = () => {
37
- onElementSelectorRender( { element, elementId, controller } );
43
+ onElementSelectorRender( { element, controller } );
38
44
 
39
45
  element.dispatchEvent(
40
46
  new CustomEvent( ELEMENT_RENDERED_EVENT_NAME, {
@@ -61,6 +67,8 @@ export const onElementRender = ( {
61
67
  return;
62
68
  }
63
69
 
70
+ setUnmountEntry( { element, controller, manualUnmount } );
71
+
64
72
  Array.from( elementTypeHandlers.get( elementType )?.values() ?? [] ).forEach( ( handler ) => {
65
73
  const settings = element.getAttribute( 'data-e-settings' );
66
74
 
@@ -92,33 +100,24 @@ export const onElementRender = ( {
92
100
  manualUnmount.push( unmount );
93
101
  }
94
102
  } );
95
-
96
- if ( ! unmountElementTypeCallbacks.has( elementType ) ) {
97
- unmountElementTypeCallbacks.set( elementType, new Map() );
98
- }
99
-
100
- unmountElementTypeCallbacks.get( elementType )?.set( elementId, () => {
101
- controller.abort();
102
-
103
- manualUnmount.forEach( ( callback ) => callback() );
104
- } );
105
103
  };
106
104
 
107
105
  export const onElementSelectorRender = ( {
108
106
  element,
109
- elementId,
110
107
  controller,
111
108
  }: {
112
109
  element: Element;
113
- elementId: string;
114
110
  controller: AbortController;
115
111
  } ) => {
112
+ let requiresCleanup = false;
113
+ const manualUnmount: ( () => void )[] = [];
114
+
116
115
  Array.from( elementSelectorHandlers.entries() ?? [] ).forEach( ( [ selector, handlers ] ) => {
117
116
  if ( ! element.matches( selector ) ) {
118
117
  return;
119
118
  }
120
119
 
121
- const manualUnmount: ( () => void )[] = [];
120
+ requiresCleanup = true;
122
121
 
123
122
  Array.from( handlers.values() ?? [] ).forEach( ( handler ) => {
124
123
  const settings = element.getAttribute( 'data-e-settings' );
@@ -133,21 +132,11 @@ export const onElementSelectorRender = ( {
133
132
  manualUnmount.push( unmount );
134
133
  }
135
134
  } );
136
-
137
- if ( ! manualUnmount.length ) {
138
- return;
139
- }
140
-
141
- if ( ! unmountElementSelectorCallbacks.get( elementId ) ) {
142
- unmountElementSelectorCallbacks.set( elementId, new Map() );
143
- }
144
-
145
- unmountElementSelectorCallbacks.get( elementId )?.set( selector, () => {
146
- controller.abort();
147
-
148
- manualUnmount.forEach( ( callback ) => callback() );
149
- } );
150
135
  } );
136
+
137
+ if ( requiresCleanup ) {
138
+ setUnmountEntry( { element, controller, manualUnmount } );
139
+ }
151
140
  };
152
141
 
153
142
  export const onElementDestroy = ( {
@@ -159,31 +148,39 @@ export const onElementDestroy = ( {
159
148
  elementId: string;
160
149
  element?: Element;
161
150
  } ) => {
162
- const unmount = unmountElementTypeCallbacks.get( elementType )?.get( elementId );
163
- const unmountSelector = unmountElementSelectorCallbacks.get( elementId );
164
-
165
- if ( element ) {
166
- dispatchDestroyedEvent( { element, elementType, elementId } );
151
+ if ( ! element ) {
152
+ return;
167
153
  }
168
154
 
169
- if ( unmount ) {
170
- unmount();
171
- }
155
+ cleanupOnUnmount( element );
172
156
 
173
- if ( unmountSelector?.size ) {
174
- Array.from( unmountSelector.values() ).forEach( ( selectorUnmount ) => {
175
- selectorUnmount();
176
- } );
177
- }
157
+ dispatchDestroyedEvent( { element, elementType, elementId } );
158
+ };
178
159
 
179
- unmountElementTypeCallbacks.get( elementType )?.delete( elementId );
180
- unmountElementSelectorCallbacks.delete( elementId );
160
+ const setUnmountEntry = ( {
161
+ element,
162
+ controller,
163
+ manualUnmount,
164
+ }: {
165
+ element: Element;
166
+ controller: AbortController;
167
+ manualUnmount: ( () => void )[];
168
+ } ) => {
169
+ const existingEntry = unmountCallbacks.get( element );
181
170
 
182
- if ( unmountElementTypeCallbacks.get( elementType )?.size === 0 ) {
183
- unmountElementTypeCallbacks.delete( elementType );
171
+ if ( existingEntry ) {
172
+ existingEntry.manualUnmount.push( ...manualUnmount );
173
+ } else {
174
+ unmountCallbacks.set( element, { controller, manualUnmount } );
184
175
  }
176
+ };
177
+
178
+ const cleanupOnUnmount = ( element: Element ) => {
179
+ const entry = unmountCallbacks.get( element );
185
180
 
186
- if ( unmountElementSelectorCallbacks.size === 0 ) {
187
- unmountElementSelectorCallbacks.delete( elementId );
181
+ if ( entry ) {
182
+ entry.controller.abort();
183
+ entry.manualUnmount.forEach( ( callback ) => callback() );
184
+ unmountCallbacks.delete( element );
188
185
  }
189
186
  };