@elementor/editor-components 3.33.0-261 → 3.33.0-262

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.
@@ -1,6 +1,6 @@
1
1
  import * as React from 'react';
2
- import { useEffect, useMemo, useState } from 'react';
3
- import { getElementLabel, type V1Element, type V1ElementData } from '@elementor/editor-elements';
2
+ import { useEffect, useMemo, useRef, useState } from 'react';
3
+ import { getElementLabel, type V1ElementData } from '@elementor/editor-elements';
4
4
  import { ThemeProvider } from '@elementor/editor-ui';
5
5
  import { StarIcon } from '@elementor/icons';
6
6
  import { __useDispatch as useDispatch } from '@elementor/store';
@@ -11,13 +11,20 @@ import { __ } from '@wordpress/i18n';
11
11
  import { useComponents } from '../../hooks/use-components';
12
12
  import { slice } from '../../store/store';
13
13
  import { type ComponentFormValues } from '../../types';
14
+ import { trackComponentEvent } from '../../utils/tracking';
14
15
  import { useForm } from './hooks/use-form';
15
16
  import { createBaseComponentSchema, createSubmitComponentSchema } from './utils/component-form-schema';
17
+ import {
18
+ type ComponentEventData,
19
+ type ContextMenuEventOptions,
20
+ getComponentEventData,
21
+ } from './utils/get-component-event-data';
16
22
  import { replaceElementWithComponent } from './utils/replace-element-with-component';
17
23
 
18
24
  type SaveAsComponentEventData = {
19
- element: V1Element;
25
+ element: V1ElementData;
20
26
  anchorPosition: { top: number; left: number };
27
+ options?: ContextMenuEventOptions;
21
28
  };
22
29
 
23
30
  type ResultNotification = {
@@ -28,7 +35,7 @@ type ResultNotification = {
28
35
 
29
36
  export function CreateComponentForm() {
30
37
  const [ element, setElement ] = useState< {
31
- element: V1Element;
38
+ element: V1ElementData;
32
39
  elementLabel: string;
33
40
  } | null >( null );
34
41
 
@@ -38,12 +45,20 @@ export function CreateComponentForm() {
38
45
 
39
46
  const dispatch = useDispatch();
40
47
 
48
+ const eventData = useRef< ComponentEventData | null >( null );
49
+
41
50
  useEffect( () => {
42
51
  const OPEN_SAVE_AS_COMPONENT_FORM_EVENT = 'elementor/editor/open-save-as-component-form';
43
52
 
44
53
  const openPopup = ( event: CustomEvent< SaveAsComponentEventData > ) => {
45
54
  setElement( { element: event.detail.element, elementLabel: getElementLabel( event.detail.element.id ) } );
46
55
  setAnchorPosition( event.detail.anchorPosition );
56
+
57
+ eventData.current = getComponentEventData( event.detail.element, event.detail.options );
58
+ trackComponentEvent( {
59
+ action: 'createClicked',
60
+ ...eventData.current,
61
+ } );
47
62
  };
48
63
 
49
64
  window.addEventListener( OPEN_SAVE_AS_COMPONENT_FORM_EVENT, openPopup as EventListener );
@@ -65,7 +80,7 @@ export function CreateComponentForm() {
65
80
  slice.actions.addUnpublished( {
66
81
  uid,
67
82
  name: values.componentName,
68
- elements: [ element.element.model.toJSON( { remove: [ 'default' ] } ) as V1ElementData ],
83
+ elements: [ element.element ],
69
84
  } )
70
85
  );
71
86
 
@@ -73,6 +88,13 @@ export function CreateComponentForm() {
73
88
 
74
89
  replaceElementWithComponent( element.element, { uid, name: values.componentName } );
75
90
 
91
+ trackComponentEvent( {
92
+ action: 'created',
93
+ component_uid: uid,
94
+ component_name: values.componentName,
95
+ ...eventData.current,
96
+ } );
97
+
76
98
  setResultNotification( {
77
99
  show: true,
78
100
  // Translators: %1$s: Component name, %2$s: Component UID
@@ -100,6 +122,11 @@ export function CreateComponentForm() {
100
122
 
101
123
  const cancelSave = () => {
102
124
  resetAndClosePopup();
125
+
126
+ trackComponentEvent( {
127
+ action: 'createCancelled',
128
+ ...eventData.current,
129
+ } );
103
130
  };
104
131
 
105
132
  return (
@@ -0,0 +1,54 @@
1
+ import { type V1ElementData } from '@elementor/editor-elements';
2
+
3
+ export type ComponentEventData = {
4
+ nested_elements_count: number;
5
+ nested_components_count: number;
6
+ top_element_type: string;
7
+ location?: string;
8
+ secondary_location?: string;
9
+ trigger?: string;
10
+ };
11
+
12
+ export type ContextMenuEventOptions = Record< string, unknown > & {
13
+ location: string;
14
+ secondaryLocation: string;
15
+ trigger: string;
16
+ };
17
+
18
+ export const getComponentEventData = (
19
+ containerElement: V1ElementData,
20
+ options?: ContextMenuEventOptions
21
+ ): ComponentEventData => {
22
+ const { elementsCount, componentsCount } = countNestedElements( containerElement );
23
+
24
+ return {
25
+ nested_elements_count: elementsCount,
26
+ nested_components_count: componentsCount,
27
+ top_element_type: containerElement.elType,
28
+ location: options?.location,
29
+ secondary_location: options?.secondaryLocation,
30
+ trigger: options?.trigger,
31
+ };
32
+ };
33
+
34
+ function countNestedElements( container: V1ElementData ): { elementsCount: number; componentsCount: number } {
35
+ if ( ! container.elements || container.elements.length === 0 ) {
36
+ return { elementsCount: 0, componentsCount: 0 };
37
+ }
38
+
39
+ let elementsCount = container.elements.length;
40
+ let componentsCount = 0;
41
+
42
+ for ( const element of container.elements ) {
43
+ if ( element.widgetType === 'e-component' ) {
44
+ componentsCount++;
45
+ }
46
+
47
+ const { elementsCount: nestedElementsCount, componentsCount: nestedComponentsCount } =
48
+ countNestedElements( element );
49
+ elementsCount += nestedElementsCount;
50
+ componentsCount += nestedComponentsCount;
51
+ }
52
+
53
+ return { elementsCount, componentsCount };
54
+ }
@@ -1,4 +1,4 @@
1
- import { replaceElement, type V1Element, type V1ElementModelProps } from '@elementor/editor-elements';
1
+ import { replaceElement, type V1ElementData, type V1ElementModelProps } from '@elementor/editor-elements';
2
2
 
3
3
  type ComponentInstanceParams = {
4
4
  id?: number;
@@ -6,7 +6,7 @@ type ComponentInstanceParams = {
6
6
  uid: string;
7
7
  };
8
8
 
9
- export const replaceElementWithComponent = ( element: V1Element, component: ComponentInstanceParams ) => {
9
+ export const replaceElementWithComponent = ( element: V1ElementData, component: ComponentInstanceParams ) => {
10
10
  replaceElement( {
11
11
  currentElement: element,
12
12
  newElement: createComponentModel( component ),
@@ -12,6 +12,10 @@ import { __privateRunCommand as runCommand } from '@elementor/editor-v1-adapters
12
12
  import { __ } from '@wordpress/i18n';
13
13
 
14
14
  import { apiClient } from './api';
15
+ import { type ExtendedWindow } from './types';
16
+ import { trackComponentEvent } from './utils/tracking';
17
+
18
+ type ContextMenuEventData = { location: string; secondaryLocation: string; trigger: string };
15
19
 
16
20
  export const TYPE = 'e-component';
17
21
 
@@ -35,7 +39,8 @@ function createComponentView(
35
39
  options: CreateTemplatedElementTypeOptions & { showLockedByModal?: ( lockedBy: string ) => void }
36
40
  ): typeof ElementView {
37
41
  return class extends createTemplatedElementView( options ) {
38
- legacyWindow = window as unknown as LegacyWindow;
42
+ legacyWindow = window as unknown as LegacyWindow & ExtendedWindow;
43
+ eventsManagerConfig = this.legacyWindow.elementorCommon.eventsManager.config;
39
44
 
40
45
  afterSettingsResolve( settings: { [ key: string ]: unknown } ) {
41
46
  if ( settings.component ) {
@@ -86,7 +91,8 @@ function createComponentView(
86
91
  icon: 'eicon-edit',
87
92
  title: () => __( 'Edit Component', 'elementor' ),
88
93
  isEnabled: () => true,
89
- callback: () => this.switchDocument(),
94
+ callback: ( _: unknown, eventData: ContextMenuEventData ) =>
95
+ this.editComponent( eventData ),
90
96
  },
91
97
  ],
92
98
  },
@@ -111,10 +117,31 @@ function createComponentView(
111
117
  }
112
118
  }
113
119
 
120
+ editComponent( { trigger, location, secondaryLocation }: ContextMenuEventData ) {
121
+ this.switchDocument();
122
+
123
+ const editorSettings = this.model.get( 'editor_settings' );
124
+
125
+ trackComponentEvent( {
126
+ action: 'edited',
127
+ component_uid: editorSettings?.component_uid,
128
+ component_name: editorSettings?.title,
129
+ location,
130
+ secondary_location: secondaryLocation,
131
+ trigger,
132
+ } );
133
+ }
134
+
114
135
  handleDblClick( e: MouseEvent ) {
115
136
  e.stopPropagation();
116
137
 
117
- this.switchDocument();
138
+ const { triggers, locations, secondaryLocations } = this.eventsManagerConfig;
139
+
140
+ this.editComponent( {
141
+ trigger: triggers.doubleClick,
142
+ location: locations.canvas,
143
+ secondaryLocation: secondaryLocations.canvasElement,
144
+ } );
118
145
  }
119
146
 
120
147
  events() {
package/src/init.ts CHANGED
@@ -25,15 +25,19 @@ import { removeComponentStyles } from './store/remove-component-styles';
25
25
  import { slice } from './store/store';
26
26
  import { beforeSave } from './sync/before-save';
27
27
  import { type ExtendedWindow } from './types';
28
+ import { onElementDrop } from './utils/tracking';
28
29
 
29
30
  const COMPONENT_DOCUMENT_TYPE = 'elementor_component';
30
31
 
31
32
  export function init() {
32
33
  stylesRepository.register( componentsStylesProvider );
34
+
33
35
  registerSlice( slice );
36
+
34
37
  registerElementType( TYPE, ( options: CreateTemplatedElementTypeOptions ) =>
35
38
  createComponentType( { ...options, showLockedByModal: openEditModeDialog } )
36
39
  );
40
+
37
41
  registerDataHook( 'dependency', 'editor/documents/close', ( args ) => {
38
42
  const document = getV1CurrentDocument();
39
43
  if ( document.config.type === COMPONENT_DOCUMENT_TYPE ) {
@@ -42,6 +46,8 @@ export function init() {
42
46
  return true;
43
47
  } );
44
48
 
49
+ registerDataHook( 'after', 'preview/drop', onElementDrop );
50
+
45
51
  ( window as unknown as ExtendedWindow ).elementorCommon.__beforeSave = beforeSave;
46
52
 
47
53
  injectTab( {
@@ -0,0 +1,47 @@
1
+ import { type V1Element } from '@elementor/editor-elements';
2
+ import { getMixpanel } from '@elementor/mixpanel';
3
+ import { __getState as getState } from '@elementor/store';
4
+
5
+ import { selectCreatedThisSession } from '../store/store';
6
+ import { type ExtendedWindow } from '../types';
7
+
8
+ type ComponentEventData = Record< string, unknown > & {
9
+ action: 'createClicked' | 'created' | 'createCancelled' | 'instanceAdded' | 'edited';
10
+ };
11
+
12
+ export const trackComponentEvent = ( { action, ...data }: ComponentEventData ) => {
13
+ const { dispatchEvent, config } = getMixpanel();
14
+ if ( ! config?.names?.components?.[ action ] ) {
15
+ return;
16
+ }
17
+
18
+ const name = config.names.components[ action ];
19
+ dispatchEvent?.( name, data );
20
+ };
21
+
22
+ export const onElementDrop = ( _args: unknown, element: V1Element ) => {
23
+ if ( ! ( element.model.get( 'widgetType' ) === 'e-component' ) ) {
24
+ return;
25
+ }
26
+
27
+ const editorSettings = element.model.get( 'editor_settings' );
28
+ const componentName = editorSettings?.title;
29
+ const componentUID = editorSettings?.component_uid;
30
+ const instanceId = element.id;
31
+
32
+ const createdThisSession = selectCreatedThisSession( getState() );
33
+ const isSameSessionReuse = componentUID && createdThisSession.includes( componentUID );
34
+
35
+ const eventsManagerConfig = ( window as unknown as ExtendedWindow ).elementorCommon.eventsManager.config;
36
+ const { locations, secondaryLocations } = eventsManagerConfig;
37
+
38
+ trackComponentEvent( {
39
+ action: 'instanceAdded',
40
+ instance_id: instanceId,
41
+ component_uid: componentUID,
42
+ component_name: componentName,
43
+ is_same_session_reuse: isSameSessionReuse,
44
+ location: locations.widgetPanel,
45
+ secondary_location: secondaryLocations.componentsTab,
46
+ } );
47
+ };