@elementor/editor-canvas 4.2.0-946 → 4.2.0-beta1

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.
@@ -8,19 +8,27 @@ import {
8
8
  } from '@elementor/editor-v1-adapters';
9
9
 
10
10
  import type { ElementOverlayConfig } from '../types/element-overlay';
11
- import { GridOutlineOverlay } from './grid-outline';
11
+ import { GridEmptyCellPositioner, GridOutlineOverlay } from './grid-outline';
12
12
  import { OutlineOverlay } from './outline-overlay';
13
13
 
14
14
  const ELEMENTS_DATA_ATTR = 'atomic';
15
+ const E_GRID_TYPE = 'e-grid';
16
+
17
+ const isGridElement = ( element: HTMLElement ): boolean =>
18
+ [ element.dataset.eType, element.dataset.element_type ].includes( E_GRID_TYPE );
15
19
 
16
20
  const overlayRegistry: ElementOverlayConfig[] = [
17
21
  {
18
22
  component: OutlineOverlay,
19
23
  shouldRender: () => true,
20
24
  },
25
+ {
26
+ component: GridEmptyCellPositioner,
27
+ shouldRender: ( { element } ) => isGridElement( element ),
28
+ },
21
29
  {
22
30
  component: GridOutlineOverlay,
23
- shouldRender: ( { element, isSelected } ) => isSelected && element.dataset.eType === 'e-grid',
31
+ shouldRender: ( { element, isSelected } ) => isSelected && isGridElement( element ),
24
32
  },
25
33
  ];
26
34
 
@@ -0,0 +1,46 @@
1
+ import { useEffect } from 'react';
2
+
3
+ import { useElementRect } from '../../hooks/use-element-rect';
4
+ import { useGridTracks } from '../../hooks/use-grid-tracks';
5
+ import type { ElementOverlayProps } from '../../types/element-overlay';
6
+ import { findFirstEmptyCell } from '../../utils/find-first-empty-cell';
7
+
8
+ const CSS_VAR_ROW = '--e-grid-empty-cell-row';
9
+ const CSS_VAR_COL = '--e-grid-empty-cell-col';
10
+ const CSS_VAR_VISIBILITY = '--e-grid-empty-cell-visibility';
11
+
12
+ const clearGridEmptyCellStyles = ( target: HTMLElement ): void => {
13
+ target.style.removeProperty( CSS_VAR_ROW );
14
+ target.style.removeProperty( CSS_VAR_COL );
15
+ target.style.removeProperty( CSS_VAR_VISIBILITY );
16
+ };
17
+
18
+ export const GridEmptyCellPositioner = ( { element }: ElementOverlayProps ): null => {
19
+ const rect = useElementRect( element );
20
+ const tracks = useGridTracks( element, rect );
21
+
22
+ useEffect( () => {
23
+ if ( ! element ) {
24
+ return;
25
+ }
26
+
27
+ const firstEmpty = findFirstEmptyCell( element, tracks.columns.length, tracks.rows.length );
28
+
29
+ if ( ! firstEmpty ) {
30
+ element.style.removeProperty( CSS_VAR_ROW );
31
+ element.style.removeProperty( CSS_VAR_COL );
32
+ element.style.setProperty( CSS_VAR_VISIBILITY, 'hidden' );
33
+ return () => clearGridEmptyCellStyles( element );
34
+ }
35
+
36
+ element.style.setProperty( CSS_VAR_ROW, String( firstEmpty.row + 1 ) );
37
+ element.style.setProperty( CSS_VAR_COL, String( firstEmpty.col + 1 ) );
38
+ element.style.setProperty( CSS_VAR_VISIBILITY, 'visible' );
39
+
40
+ return () => {
41
+ clearGridEmptyCellStyles( element );
42
+ };
43
+ }, [ element, tracks ] );
44
+
45
+ return null;
46
+ };
@@ -54,6 +54,14 @@ const renderLines = ( tracks: GridTracks, width: number, height: number ) => {
54
54
  ];
55
55
  };
56
56
 
57
+ const isDragActiveFromDom = ( element: HTMLElement | null ): boolean => {
58
+ return Boolean(
59
+ element?.querySelector(
60
+ '.e-dragging-over, .elementor-dragging-on-child, .elementor-draggable-over, .elementor-widget-placeholder, .elementor-sortable-placeholder'
61
+ )
62
+ );
63
+ };
64
+
57
65
  export function GridOutline( { element, tracks, width, height }: Props ) {
58
66
  const cells = useMemo( () => computeCellRects( tracks, width, height ), [ tracks, width, height ] );
59
67
  const hasGap = tracks.columnGap > 0 || tracks.rowGap > 0;
@@ -76,7 +84,9 @@ export function GridOutline( { element, tracks, width, height }: Props ) {
76
84
  >
77
85
  { hasGap ? renderCells( cells, tracks.borderColor ) : renderLines( tracks, width, height ) }
78
86
  </svg>
79
- { emptyCellRect && <FirstEmptyCell rect={ emptyCellRect } color={ tracks.borderColor } /> }
87
+ { emptyCellRect && ! isDragActiveFromDom( element ) && (
88
+ <FirstEmptyCell rect={ emptyCellRect } color={ tracks.borderColor } />
89
+ ) }
80
90
  </>
81
91
  );
82
92
  }
@@ -1 +1,2 @@
1
+ export { GridEmptyCellPositioner } from './grid-empty-cell-positioner';
1
2
  export { GridOutlineOverlay } from './grid-outline-overlay';
package/src/index.ts CHANGED
@@ -44,3 +44,4 @@ export { UnknownStyleTypeError, UnknownStyleStateError } from './renderers/error
44
44
  export { useCanvasDocument } from './hooks/use-canvas-document';
45
45
  export { useEscapeOnCanvas } from './hooks/use-escape-on-canvas';
46
46
  export { doAfterRender } from './utils/after-render';
47
+ export { ELEMENT_ADDED_EVENT, type ElementAddedEvent } from './mcp/tools/build-composition/tool';
@@ -5,10 +5,13 @@ import {
5
5
  getContainer,
6
6
  getWidgetsCache,
7
7
  type V1Element,
8
+ type V1ElementData,
8
9
  } from '@elementor/editor-elements';
9
10
  import { type MCPRegistryEntry } from '@elementor/editor-mcp';
11
+ import { dispatchMcpStylesAppliedEvent } from '@elementor/editor-mcp';
10
12
 
11
13
  import { CompositionBuilder } from '../../../composition-builder/composition-builder';
14
+ import { trackCanvasEvent } from '../../../utils/tracking';
12
15
  import { AVAILABLE_WIDGETS_URI_V4 } from '../../resources/available-widgets-resource';
13
16
  import { DYNAMIC_TAGS_URI } from '../../resources/dynamic-tags-resource';
14
17
  import { BEST_PRACTICES_URI, WIDGET_SCHEMA_URI } from '../../resources/widgets-schema-resource';
@@ -19,6 +22,13 @@ import { BUILD_COMPOSITIONS_GUIDE_URI, generatePrompt } from './prompt';
19
22
  import { inputSchema as schema, outputSchema } from './schema';
20
23
  import { adaptLeafRootParams } from './xml-leaf-wrapper';
21
24
 
25
+ export type ElementAddedEvent = {
26
+ element: V1ElementData;
27
+ executedBy: 'mcp_tool' | 'user';
28
+ };
29
+
30
+ export const ELEMENT_ADDED_EVENT = 'elementor/canvas/element-added';
31
+
22
32
  export const initBuildCompositionsTool = ( reg: MCPRegistryEntry ) => {
23
33
  const { addTool, resource } = reg;
24
34
 
@@ -80,6 +90,18 @@ export const initBuildCompositionsTool = ( reg: MCPRegistryEntry ) => {
80
90
  rootContainers.push( ...generatedRootContainers );
81
91
  generatedXML = new XMLSerializer().serializeToString( compositionBuilder.getXML() );
82
92
 
93
+ rootContainers.forEach( ( container ) => {
94
+ const elementData = container.model?.toJSON();
95
+
96
+ if ( elementData ) {
97
+ onElementAdded( elementData as V1ElementData );
98
+ }
99
+ } );
100
+
101
+ Object.values( stylesConfig ).forEach( ( styleValue ) => {
102
+ dispatchMcpStylesAppliedEvent( { styleValue } );
103
+ } );
104
+
83
105
  if ( configErrors.length ) {
84
106
  errors.push( ...configErrors.map( ( msg ) => new Error( msg ) ) );
85
107
  }
@@ -186,3 +208,30 @@ function assertCompositionXmlUsesV4WidgetsOnly( xmlStructure: string ) {
186
208
  }
187
209
  }
188
210
  }
211
+
212
+ function onElementAdded( element: V1ElementData ) {
213
+ const elType = element.elType ?? '';
214
+ const widgetType = element.widgetType ?? '';
215
+ const elementName = elType === 'widget' ? widgetType : elType;
216
+
217
+ trackCanvasEvent( {
218
+ eventName: 'add_element',
219
+ executed_by: 'mcp_tool',
220
+ element_name: elementName,
221
+ element_type: elType,
222
+ widget_type: widgetType,
223
+ } );
224
+
225
+ const event: ElementAddedEvent = {
226
+ element,
227
+ executedBy: 'mcp_tool',
228
+ };
229
+
230
+ window.dispatchEvent( new CustomEvent( ELEMENT_ADDED_EVENT, { detail: event } ) );
231
+
232
+ if ( element.elements?.length ) {
233
+ element.elements?.forEach( ( childElement ) => {
234
+ onElementAdded( childElement );
235
+ } );
236
+ }
237
+ }
@@ -1,5 +1,6 @@
1
1
  import { getContainer, getWidgetsCache } from '@elementor/editor-elements';
2
2
  import { type MCPRegistryEntry } from '@elementor/editor-mcp';
3
+ import { dispatchMcpStylesAppliedEvent } from '@elementor/editor-mcp';
3
4
  import { type PropValue, Schema } from '@elementor/editor-props';
4
5
 
5
6
  import { DYNAMIC_TAGS_URI } from '../../resources/dynamic-tags-resource';
@@ -115,6 +116,8 @@ async function applyStyleFromCss( opts: {
115
116
  propertyValue: styleValue,
116
117
  customCssWriteMode: 'merge-with-stored',
117
118
  } );
119
+
120
+ dispatchMcpStylesAppliedEvent( { styleValue } );
118
121
  } catch ( error ) {
119
122
  throw new Error(
120
123
  createUpdateErrorMessage( {
@@ -0,0 +1,14 @@
1
+ import { trackEvent } from '@elementor/events';
2
+
3
+ type ElementEventData = {
4
+ eventName: 'add_element';
5
+ element_name: string;
6
+ element_type: string;
7
+ widget_type: string;
8
+ location?: string;
9
+ executed_by: 'user' | 'mcp_tool' | 'system';
10
+ };
11
+
12
+ export const trackCanvasEvent = ( data: ElementEventData ) => {
13
+ trackEvent( data );
14
+ };