@elementor/editor-canvas 4.0.0-manual → 4.0.0

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 (29) hide show
  1. package/dist/index.d.mts +8 -2
  2. package/dist/index.d.ts +8 -2
  3. package/dist/index.js +127 -78
  4. package/dist/index.mjs +92 -43
  5. package/package.json +18 -18
  6. package/src/hooks/__tests__/use-style-items.test.ts +57 -0
  7. package/src/hooks/use-style-items.ts +2 -2
  8. package/src/index.ts +1 -1
  9. package/src/legacy/create-nested-templated-element-type.ts +15 -2
  10. package/src/legacy/create-templated-element-type.ts +8 -0
  11. package/src/legacy/types.ts +3 -1
  12. package/src/mcp/canvas-mcp.ts +5 -7
  13. package/src/mcp/resources/breakpoints-resource.ts +11 -4
  14. package/src/mcp/resources/document-structure-resource.ts +18 -13
  15. package/src/mcp/tools/build-composition/schema.ts +1 -1
  16. package/src/mcp/tools/build-composition/tool.ts +5 -1
  17. package/src/mcp/utils/__tests__/get-composition-target-container.test.ts +59 -0
  18. package/src/mcp/utils/get-composition-target-container.ts +15 -0
  19. package/src/renderers/__tests__/create-styles-renderer.test.ts +117 -0
  20. package/src/renderers/create-styles-renderer.ts +13 -3
  21. package/src/style-commands/__tests__/paste-style.test.ts +5 -3
  22. package/src/style-commands/__tests__/reset-style.test.ts +3 -3
  23. package/src/style-commands/paste-style.ts +7 -1
  24. package/src/style-commands/reset-style.ts +1 -1
  25. package/src/style-commands/undoable-actions/paste-element-style.ts +1 -1
  26. package/src/style-commands/undoable-actions/reset-element-style.ts +1 -1
  27. package/src/transformers/styles/__tests__/size-transformer.test.ts +24 -0
  28. package/src/transformers/styles/size-transformer.ts +3 -0
  29. /package/src/{style-commands/utils.ts → utils/command-utils.ts} +0 -0
package/dist/index.mjs CHANGED
@@ -2,7 +2,7 @@
2
2
  import { v1ReadyEvent } from "@elementor/editor-v1-adapters";
3
3
  var BREAKPOINTS_SCHEMA_URI = "elementor://breakpoints/list";
4
4
  var initBreakpointsResource = (reg) => {
5
- const { mcpServer, sendResourceUpdated } = reg;
5
+ const { resource, sendResourceUpdated } = reg;
6
6
  const getBreakpointsList = () => {
7
7
  const { breakpoints } = window.elementor?.config?.responsive || {};
8
8
  if (!breakpoints) {
@@ -26,9 +26,16 @@ var initBreakpointsResource = (reg) => {
26
26
  }
27
27
  ]
28
28
  });
29
- mcpServer.resource("breakpoints ", BREAKPOINTS_SCHEMA_URI, () => {
30
- return buildResourceResponse();
31
- });
29
+ resource(
30
+ "breakpoints ",
31
+ BREAKPOINTS_SCHEMA_URI,
32
+ {
33
+ description: "Breakpoints list."
34
+ },
35
+ () => {
36
+ return buildResourceResponse();
37
+ }
38
+ );
32
39
  window.addEventListener(v1ReadyEvent().name, () => {
33
40
  sendResourceUpdated({
34
41
  uri: BREAKPOINTS_SCHEMA_URI,
@@ -799,14 +806,22 @@ var UnknownStyleStateError = createError({
799
806
  var SELECTORS_MAP = {
800
807
  class: "."
801
808
  };
809
+ var DEFAULT_BREAKPOINT = "desktop";
810
+ var DEFAULT_STATE = "normal";
811
+ function getStyleUniqueKey(style) {
812
+ const breakpoint = style.variants[0]?.meta?.breakpoint ?? DEFAULT_BREAKPOINT;
813
+ const state = style.variants[0]?.meta?.state ?? DEFAULT_STATE;
814
+ return `${style.id}-${breakpoint}-${state}`;
815
+ }
802
816
  function createStylesRenderer({ resolve, breakpoints, selectorPrefix = "" }) {
803
817
  return async ({ styles, signal }) => {
804
- const seenIds = /* @__PURE__ */ new Set();
818
+ const seenKeys = /* @__PURE__ */ new Set();
805
819
  const uniqueStyles = styles.filter((style) => {
806
- if (seenIds.has(style.id)) {
820
+ const key = getStyleUniqueKey(style);
821
+ if (seenKeys.has(key)) {
807
822
  return false;
808
823
  }
809
- seenIds.add(style.id);
824
+ seenKeys.add(key);
810
825
  return true;
811
826
  });
812
827
  const stylesCssPromises = uniqueStyles.map(async (style) => {
@@ -983,7 +998,7 @@ function createProviderSubscriber2({ provider, renderStyles, setStyleItems, cach
983
998
  }
984
999
  async function createItems(signal) {
985
1000
  const allStyles = provider.actions.all();
986
- const styles = allStyles.reverse().map((style) => {
1001
+ const styles = [...allStyles].reverse().map((style) => {
987
1002
  return {
988
1003
  ...style,
989
1004
  cssName: provider.actions.resolveCssName(style.id)
@@ -991,7 +1006,7 @@ function createProviderSubscriber2({ provider, renderStyles, setStyleItems, cach
991
1006
  });
992
1007
  return renderStyles({ styles: breakToBreakpoints(styles), signal }).then((rendered) => {
993
1008
  rebuildCache(cache, allStyles, rendered);
994
- return rendered;
1009
+ return getOrderedItems(cache);
995
1010
  });
996
1011
  }
997
1012
  function breakToBreakpoints(styles) {
@@ -1591,6 +1606,9 @@ var shadowTransformer = createTransformer((value) => {
1591
1606
 
1592
1607
  // src/transformers/styles/size-transformer.ts
1593
1608
  var sizeTransformer = createTransformer((value) => {
1609
+ if (value.unit === "auto") {
1610
+ return "auto";
1611
+ }
1594
1612
  return value.unit === "custom" ? value.size : `${value.size}${value.unit}`;
1595
1613
  });
1596
1614
 
@@ -1974,6 +1992,7 @@ function createTemplatedElementView({
1974
1992
  this._lastResolvedSettingsHash = settingsHash;
1975
1993
  const context = {
1976
1994
  id: this.model.get("id"),
1995
+ interaction_id: this.getInteractionId(),
1977
1996
  type,
1978
1997
  settings,
1979
1998
  base_styles: baseStylesDictionary
@@ -2008,6 +2027,11 @@ function createTemplatedElementView({
2008
2027
  _openEditingPanel(options) {
2009
2028
  this._doAfterRender(() => super._openEditingPanel(options));
2010
2029
  }
2030
+ getInteractionId() {
2031
+ const originId = this.model.get("originId");
2032
+ const id = this.model.get("id");
2033
+ return originId ?? id;
2034
+ }
2011
2035
  };
2012
2036
  }
2013
2037
 
@@ -2040,10 +2064,11 @@ function createNestedTemplatedElementType({
2040
2064
  }
2041
2065
  function buildEditorAttributes(model) {
2042
2066
  const id = model.get("id");
2067
+ const originId = model.get("originId");
2043
2068
  const cid = model.cid ?? "";
2044
2069
  const attrs = {
2045
2070
  "data-model-cid": cid,
2046
- "data-interaction-id": id,
2071
+ "data-interaction-id": originId ?? id,
2047
2072
  "x-ignore": "true"
2048
2073
  };
2049
2074
  return Object.entries(attrs).map(([key, value]) => `${key}="${value}"`).join(" ");
@@ -2077,6 +2102,9 @@ function createNestedTemplatedElementView({
2077
2102
  invalidateRenderCache() {
2078
2103
  this._lastResolvedSettingsHash = null;
2079
2104
  },
2105
+ renderOnChange() {
2106
+ this.render();
2107
+ },
2080
2108
  render() {
2081
2109
  this._abortController?.abort();
2082
2110
  this._abortController = new AbortController();
@@ -2120,6 +2148,7 @@ function createNestedTemplatedElementView({
2120
2148
  this._lastResolvedSettingsHash = settingsHash;
2121
2149
  const context = {
2122
2150
  id: model.get("id"),
2151
+ interaction_id: this.getInteractionId(),
2123
2152
  type,
2124
2153
  settings,
2125
2154
  base_styles: baseStylesDictionary,
@@ -2249,6 +2278,11 @@ function createNestedTemplatedElementView({
2249
2278
  },
2250
2279
  _openEditingPanel(options) {
2251
2280
  this._doAfterRender(() => parentOpenEditingPanel.call(this, options));
2281
+ },
2282
+ getInteractionId() {
2283
+ const originId = this.model.get("originId");
2284
+ const id = this.model.get("id");
2285
+ return originId ?? id;
2252
2286
  }
2253
2287
  });
2254
2288
  }
@@ -2907,7 +2941,7 @@ function initTabsModelExtensions() {
2907
2941
  import { __privateListenTo as listenTo, commandEndEvent as commandEndEvent4 } from "@elementor/editor-v1-adapters";
2908
2942
  var DOCUMENT_STRUCTURE_URI = "elementor://document/structure";
2909
2943
  var initDocumentStructureResource = (reg) => {
2910
- const { mcpServer, sendResourceUpdated } = reg;
2944
+ const { resource, sendResourceUpdated } = reg;
2911
2945
  let currentDocumentStructure = null;
2912
2946
  const updateDocumentStructure = () => {
2913
2947
  const structure = getDocumentStructure();
@@ -2929,17 +2963,23 @@ var initDocumentStructureResource = (reg) => {
2929
2963
  updateDocumentStructure
2930
2964
  );
2931
2965
  updateDocumentStructure();
2932
- mcpServer.resource("document-structure", DOCUMENT_STRUCTURE_URI, async () => {
2933
- const structure = getDocumentStructure();
2934
- return {
2935
- contents: [
2936
- {
2937
- uri: DOCUMENT_STRUCTURE_URI,
2938
- text: JSON.stringify(structure, null, 2)
2939
- }
2940
- ]
2941
- };
2942
- });
2966
+ resource(
2967
+ "document-structure",
2968
+ DOCUMENT_STRUCTURE_URI,
2969
+ {
2970
+ description: "Document structure."
2971
+ },
2972
+ async () => {
2973
+ return {
2974
+ contents: [
2975
+ {
2976
+ uri: DOCUMENT_STRUCTURE_URI,
2977
+ text: JSON.stringify(getDocumentStructure(), null, 2)
2978
+ }
2979
+ ]
2980
+ };
2981
+ }
2982
+ );
2943
2983
  };
2944
2984
  function getDocumentStructure() {
2945
2985
  const extendedWindow = window;
@@ -2979,6 +3019,7 @@ function extractElementData(element) {
2979
3019
  }
2980
3020
 
2981
3021
  // src/mcp/tools/build-composition/tool.ts
3022
+ import { getCurrentDocument } from "@elementor/editor-documents";
2982
3023
  import {
2983
3024
  createElement as createElement8,
2984
3025
  deleteElement,
@@ -3409,6 +3450,16 @@ var CompositionBuilder = class _CompositionBuilder {
3409
3450
  }
3410
3451
  };
3411
3452
 
3453
+ // src/mcp/utils/get-composition-target-container.ts
3454
+ import { COMPONENT_DOCUMENT_TYPE } from "@elementor/editor-documents";
3455
+ function getCompositionTargetContainer(documentContainer, documentType) {
3456
+ const firstChild = documentContainer.children?.[0];
3457
+ if (documentType === COMPONENT_DOCUMENT_TYPE && firstChild) {
3458
+ return firstChild;
3459
+ }
3460
+ return documentContainer;
3461
+ }
3462
+
3412
3463
  // src/mcp/tools/build-composition/prompt.ts
3413
3464
  import { toolPrompts } from "@elementor/editor-mcp";
3414
3465
  var generatePrompt = () => {
@@ -3548,7 +3599,7 @@ Note: No height/width specified on any element - flexbox handles layout automati
3548
3599
  };
3549
3600
 
3550
3601
  // src/mcp/tools/build-composition/schema.ts
3551
- import { zod as z } from "@elementor/editor-mcp";
3602
+ import { z } from "@elementor/schema";
3552
3603
  var inputSchema = {
3553
3604
  xmlStructure: z.string().describe("The XML structure representing the composition to be built"),
3554
3605
  elementConfig: z.record(
@@ -3604,6 +3655,8 @@ var initBuildCompositionsTool = (reg) => {
3604
3655
  const errors = [];
3605
3656
  const rootContainers = [];
3606
3657
  const documentContainer = getContainer3("document");
3658
+ const currentDocument = getCurrentDocument();
3659
+ const targetContainer = getCompositionTargetContainer(documentContainer, currentDocument?.type.value);
3607
3660
  try {
3608
3661
  const compositionBuilder = CompositionBuilder.fromXMLString(xmlStructure, {
3609
3662
  createElement: createElement8,
@@ -3616,7 +3669,7 @@ var initBuildCompositionsTool = (reg) => {
3616
3669
  configErrors,
3617
3670
  invalidStyles,
3618
3671
  rootContainers: generatedRootContainers
3619
- } = compositionBuilder.build(documentContainer);
3672
+ } = compositionBuilder.build(targetContainer);
3620
3673
  rootContainers.push(...generatedRootContainers);
3621
3674
  generatedXML = new XMLSerializer().serializeToString(compositionBuilder.getXML());
3622
3675
  if (configErrors.length) {
@@ -4000,14 +4053,12 @@ var initGetElementConfigTool = (reg) => {
4000
4053
  var initCanvasMcp = (reg) => {
4001
4054
  const { setMCPDescription } = reg;
4002
4055
  setMCPDescription(
4003
- `Everything related to creative design, layout, styling and building the pages, specifically element of type "widget".
4056
+ `Everything related to V4 ( Atomic ) canvas.
4004
4057
  # Canvas workflow for new compositions
4005
- - Check existing global variables
4006
- - Check existing global classes
4007
- - Create missing global variables
4008
- - Create reusable global classes
4009
- - Build valid XML with minimal inline styles (layout/positioning only)
4010
- - Apply global classes to elements`
4058
+ - Configure elements settings and styles
4059
+ - Build compositions/sections out of V4 atomic elements using context aware designs using the website resources
4060
+ - Get and retrieve element configuration values
4061
+ `
4011
4062
  );
4012
4063
  initWidgetsSchemaResource(reg);
4013
4064
  initDocumentStructureResource(reg);
@@ -4225,18 +4276,7 @@ import {
4225
4276
  commandStartEvent
4226
4277
  } from "@elementor/editor-v1-adapters";
4227
4278
 
4228
- // src/style-commands/undoable-actions/paste-element-style.ts
4229
- import {
4230
- createElementStyle as createElementStyle2,
4231
- deleteElementStyle,
4232
- getElementStyles as getElementStyles3,
4233
- updateElementStyle as updateElementStyle2
4234
- } from "@elementor/editor-elements";
4235
- import { ELEMENTS_STYLES_RESERVED_LABEL } from "@elementor/editor-styles-repository";
4236
- import { undoable as undoable2 } from "@elementor/editor-v1-adapters";
4237
- import { __ as __6 } from "@wordpress/i18n";
4238
-
4239
- // src/style-commands/utils.ts
4279
+ // src/utils/command-utils.ts
4240
4280
  import { getElementLabel as getElementLabel2, getWidgetsCache as getWidgetsCache8 } from "@elementor/editor-elements";
4241
4281
  import { CLASSES_PROP_KEY } from "@elementor/editor-props";
4242
4282
  import { __ as __5 } from "@wordpress/i18n";
@@ -4279,6 +4319,15 @@ function getTitleForContainers(containers) {
4279
4319
  }
4280
4320
 
4281
4321
  // src/style-commands/undoable-actions/paste-element-style.ts
4322
+ import {
4323
+ createElementStyle as createElementStyle2,
4324
+ deleteElementStyle,
4325
+ getElementStyles as getElementStyles3,
4326
+ updateElementStyle as updateElementStyle2
4327
+ } from "@elementor/editor-elements";
4328
+ import { ELEMENTS_STYLES_RESERVED_LABEL } from "@elementor/editor-styles-repository";
4329
+ import { undoable as undoable2 } from "@elementor/editor-v1-adapters";
4330
+ import { __ as __6 } from "@wordpress/i18n";
4282
4331
  var undoablePasteElementStyle = () => undoable2(
4283
4332
  {
4284
4333
  do: ({ containers, newStyle }) => {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@elementor/editor-canvas",
3
3
  "description": "Elementor Editor Canvas",
4
- "version": "4.0.0-manual",
4
+ "version": "4.0.0",
5
5
  "private": false,
6
6
  "author": "Elementor Team",
7
7
  "homepage": "https://elementor.com/",
@@ -37,24 +37,24 @@
37
37
  "react-dom": "^18.3.1"
38
38
  },
39
39
  "dependencies": {
40
- "@elementor/editor": "4.0.0-manual",
41
- "@elementor/editor-controls": "4.0.0-manual",
42
- "@elementor/editor-documents": "4.0.0-manual",
43
- "@elementor/editor-elements": "4.0.0-manual",
44
- "@elementor/editor-interactions": "4.0.0-manual",
45
- "@elementor/editor-mcp": "4.0.0-manual",
46
- "@elementor/editor-notifications": "4.0.0-manual",
47
- "@elementor/editor-props": "4.0.0-manual",
48
- "@elementor/editor-responsive": "4.0.0-manual",
49
- "@elementor/editor-styles": "4.0.0-manual",
50
- "@elementor/editor-styles-repository": "4.0.0-manual",
51
- "@elementor/editor-ui": "4.0.0-manual",
52
- "@elementor/editor-v1-adapters": "4.0.0-manual",
53
- "@elementor/schema": "4.0.0-manual",
54
- "@elementor/twing": "4.0.0-manual",
40
+ "@elementor/editor": "4.0.0",
41
+ "@elementor/editor-controls": "4.0.0",
42
+ "@elementor/editor-documents": "4.0.0",
43
+ "@elementor/editor-elements": "4.0.0",
44
+ "@elementor/editor-interactions": "4.0.0",
45
+ "@elementor/editor-mcp": "4.0.0",
46
+ "@elementor/editor-notifications": "4.0.0",
47
+ "@elementor/editor-props": "4.0.0",
48
+ "@elementor/editor-responsive": "4.0.0",
49
+ "@elementor/editor-styles": "4.0.0",
50
+ "@elementor/editor-styles-repository": "4.0.0",
51
+ "@elementor/editor-ui": "4.0.0",
52
+ "@elementor/editor-v1-adapters": "4.0.0",
53
+ "@elementor/schema": "4.0.0",
54
+ "@elementor/twing": "4.0.0",
55
55
  "@elementor/ui": "1.36.17",
56
- "@elementor/utils": "4.0.0-manual",
57
- "@elementor/wp-media": "4.0.0-manual",
56
+ "@elementor/utils": "4.0.0",
57
+ "@elementor/wp-media": "4.0.0",
58
58
  "@floating-ui/react": "^0.27.5",
59
59
  "@wordpress/i18n": "^5.13.0"
60
60
  },
@@ -128,6 +128,9 @@ describe( 'useStyleItems', () => {
128
128
 
129
129
  jest.mocked( stylesRepository ).getProviders.mockReturnValue( [ mockProvider1, mockProvider2 ] );
130
130
 
131
+ const provider1OriginalOrder = mockProvider1.actions.all().map( ( s ) => s.id );
132
+ const provider2OriginalOrder = mockProvider2.actions.all().map( ( s ) => s.id );
133
+
131
134
  let attachPreviewCallback: () => Promise< void >;
132
135
 
133
136
  jest.mocked( registerDataHook ).mockImplementation( ( position, command, callback ) => {
@@ -156,6 +159,9 @@ describe( 'useStyleItems', () => {
156
159
  { id: 'style2', breakpoint: 'desktop' },
157
160
  { id: 'style1', breakpoint: 'desktop' },
158
161
  ] );
162
+
163
+ expect( mockProvider1.actions.all().map( ( s ) => s.id ) ).toEqual( provider1OriginalOrder );
164
+ expect( mockProvider2.actions.all().map( ( s ) => s.id ) ).toEqual( provider2OriginalOrder );
159
165
  } );
160
166
 
161
167
  it( 'should return style items ordered by provider priority and breakpoint', async () => {
@@ -283,6 +289,57 @@ describe( 'useStyleItems', () => {
283
289
  expect( result.current ).toHaveLength( 2 );
284
290
  } );
285
291
 
292
+ it( 'should maintain breakpoint order after style update', async () => {
293
+ // Arrange.
294
+ const renderStylesMock = jest.fn().mockImplementation( ( { styles } ) =>
295
+ Promise.resolve(
296
+ styles.map( ( style: StyleDefinition ) => ( {
297
+ id: style.id,
298
+ breakpoint: style?.variants[ 0 ]?.meta.breakpoint || 'desktop',
299
+ } ) )
300
+ )
301
+ );
302
+
303
+ jest.mocked( useStyleRenderer ).mockReturnValue( renderStylesMock );
304
+
305
+ const mockProvider = createMockStylesProvider( { key: 'provider1', priority: 1 }, [
306
+ createMockStyleDefinitionWithVariants( {
307
+ id: 'style1',
308
+ variants: [
309
+ { meta: { breakpoint: null, state: null }, props: { padding: '10px' }, custom_css: null },
310
+ { meta: { breakpoint: 'tablet', state: null }, props: { padding: '8px' }, custom_css: null },
311
+ { meta: { breakpoint: 'mobile', state: null }, props: { padding: '5px' }, custom_css: null },
312
+ ],
313
+ } ),
314
+ ] );
315
+
316
+ jest.mocked( stylesRepository ).getProviders.mockReturnValue( [ mockProvider ] );
317
+
318
+ // Act - initial render.
319
+ const { result } = renderHook( () => useStyleItems() );
320
+
321
+ await act( async () => {
322
+ mockProvider.actions.updateProps?.( {
323
+ id: 'style1',
324
+ meta: { breakpoint: 'tablet', state: null },
325
+ props: { padding: '12px' },
326
+ } );
327
+ } );
328
+
329
+ // Assert - items should be ordered by breakpoint (desktop, tablet, mobile).
330
+ const breakpointOrder = result.current.map( ( item ) => item.breakpoint );
331
+ expect( breakpointOrder ).toEqual( [ 'desktop', 'tablet', 'mobile' ] );
332
+
333
+ // Act - update again (should maintain order).
334
+ await act( async () => {
335
+ mockProvider.actions.update?.( { id: 'style1', label: 'Updated' } );
336
+ } );
337
+
338
+ // Assert - order should still be maintained.
339
+ const breakpointOrderAfterUpdate = result.current.map( ( item ) => item.breakpoint );
340
+ expect( breakpointOrderAfterUpdate ).toEqual( [ 'desktop', 'tablet', 'mobile' ] );
341
+ } );
342
+
286
343
  it( 'should only re-render changed styles on differential update', async () => {
287
344
  // Arrange.
288
345
  const renderStylesMock = jest.fn().mockImplementation( ( { styles } ) =>
@@ -181,7 +181,7 @@ function createProviderSubscriber( { provider, renderStyles, setStyleItems, cach
181
181
  async function createItems( signal: AbortSignal ) {
182
182
  const allStyles = provider.actions.all();
183
183
 
184
- const styles = allStyles.reverse().map( ( style ) => {
184
+ const styles = [ ...allStyles ].reverse().map( ( style ) => {
185
185
  return {
186
186
  ...style,
187
187
  cssName: provider.actions.resolveCssName( style.id ),
@@ -191,7 +191,7 @@ function createProviderSubscriber( { provider, renderStyles, setStyleItems, cach
191
191
  return renderStyles( { styles: breakToBreakpoints( styles ), signal } ).then( ( rendered ) => {
192
192
  rebuildCache( cache, allStyles, rendered );
193
193
 
194
- return rendered;
194
+ return getOrderedItems( cache );
195
195
  } );
196
196
  }
197
197
 
package/src/index.ts CHANGED
@@ -2,7 +2,7 @@ export { BREAKPOINTS_SCHEMA_URI } from './mcp/resources/breakpoints-resource';
2
2
  export { STYLE_SCHEMA_URI } from './mcp/resources/widgets-schema-resource';
3
3
 
4
4
  export { init } from './init';
5
- export { isAtomicWidget } from './style-commands/utils';
5
+ export { isAtomicWidget } from './utils/command-utils';
6
6
 
7
7
  export {
8
8
  createTemplatedElementView,
@@ -61,13 +61,14 @@ export function createNestedTemplatedElementType( {
61
61
  };
62
62
  }
63
63
 
64
- function buildEditorAttributes( model: { get: ( key: 'id' ) => string; cid?: string } ): string {
64
+ function buildEditorAttributes( model: ElementView[ 'model' ] ): string {
65
65
  const id = model.get( 'id' );
66
+ const originId = model.get( 'originId' );
66
67
  const cid = model.cid ?? '';
67
68
 
68
69
  const attrs: Record< string, string > = {
69
70
  'data-model-cid': cid,
70
- 'data-interaction-id': id,
71
+ 'data-interaction-id': originId ?? id,
71
72
  'x-ignore': 'true',
72
73
  };
73
74
 
@@ -116,6 +117,10 @@ export function createNestedTemplatedElementView( {
116
117
  this._lastResolvedSettingsHash = null;
117
118
  },
118
119
 
120
+ renderOnChange() {
121
+ this.render();
122
+ },
123
+
119
124
  render() {
120
125
  this._abortController?.abort();
121
126
  this._abortController = new AbortController();
@@ -181,6 +186,7 @@ export function createNestedTemplatedElementView( {
181
186
 
182
187
  const context = {
183
188
  id: model.get( 'id' ),
189
+ interaction_id: this.getInteractionId(),
184
190
  type,
185
191
  settings,
186
192
  base_styles: baseStylesDictionary,
@@ -361,5 +367,12 @@ export function createNestedTemplatedElementView( {
361
367
  _openEditingPanel( options?: { scrollIntoView: boolean } ) {
362
368
  this._doAfterRender( () => parentOpenEditingPanel.call( this, options ) );
363
369
  },
370
+
371
+ getInteractionId() {
372
+ const originId = this.model.get( 'originId' );
373
+ const id = this.model.get( 'id' );
374
+
375
+ return originId ?? id;
376
+ },
364
377
  } ) as unknown as typeof ElementView;
365
378
  }
@@ -162,6 +162,7 @@ export function createTemplatedElementView( {
162
162
 
163
163
  const context = {
164
164
  id: this.model.get( 'id' ),
165
+ interaction_id: this.getInteractionId(),
165
166
  type,
166
167
  settings,
167
168
  base_styles: baseStylesDictionary,
@@ -206,5 +207,12 @@ export function createTemplatedElementView( {
206
207
  _openEditingPanel( options?: { scrollIntoView: boolean } ) {
207
208
  this._doAfterRender( () => super._openEditingPanel( options ) );
208
209
  }
210
+
211
+ getInteractionId() {
212
+ const originId = this.model.get( 'originId' );
213
+ const id = this.model.get( 'id' );
214
+
215
+ return originId ?? id;
216
+ }
209
217
  };
210
218
  }
@@ -203,6 +203,7 @@ type BackboneCollection< Model extends object > = {
203
203
 
204
204
  export type ElementModel = {
205
205
  id: string;
206
+ originId?: string;
206
207
  elType: string;
207
208
  settings: BackboneModel< Props >;
208
209
  editor_settings: Record< string, unknown >;
@@ -223,13 +224,14 @@ type ContextMenuGroup = {
223
224
  actions: ContextMenuAction[];
224
225
  };
225
226
 
227
+ export type ContextMenuEventData = { location: string; secondaryLocation: string; trigger: string };
226
228
  export type ContextMenuAction = {
227
229
  name: string;
228
230
  icon: string;
229
231
  title: string | ( () => string );
230
232
  shortcut?: string;
231
233
  isEnabled: () => boolean;
232
- callback: ( _: unknown, eventData: unknown ) => void;
234
+ callback: ( _: unknown, eventData: ContextMenuEventData ) => void;
233
235
  };
234
236
 
235
237
  export type ReplacementSettings = {
@@ -10,14 +10,12 @@ import { initGetElementConfigTool } from './tools/get-element-config/tool';
10
10
  export const initCanvasMcp = ( reg: MCPRegistryEntry ) => {
11
11
  const { setMCPDescription } = reg;
12
12
  setMCPDescription(
13
- `Everything related to creative design, layout, styling and building the pages, specifically element of type "widget".
13
+ `Everything related to V4 ( Atomic ) canvas.
14
14
  # Canvas workflow for new compositions
15
- - Check existing global variables
16
- - Check existing global classes
17
- - Create missing global variables
18
- - Create reusable global classes
19
- - Build valid XML with minimal inline styles (layout/positioning only)
20
- - Apply global classes to elements`
15
+ - Configure elements settings and styles
16
+ - Build compositions/sections out of V4 atomic elements using context aware designs using the website resources
17
+ - Get and retrieve element configuration values
18
+ `
21
19
  );
22
20
  initWidgetsSchemaResource( reg );
23
21
  initDocumentStructureResource( reg );
@@ -5,7 +5,7 @@ import { v1ReadyEvent } from '@elementor/editor-v1-adapters';
5
5
  export const BREAKPOINTS_SCHEMA_URI = 'elementor://breakpoints/list';
6
6
 
7
7
  export const initBreakpointsResource = ( reg: MCPRegistryEntry ) => {
8
- const { mcpServer, sendResourceUpdated } = reg;
8
+ const { resource, sendResourceUpdated } = reg;
9
9
 
10
10
  const getBreakpointsList = () => {
11
11
  const { breakpoints } = ( window as unknown as ExtendedWindow ).elementor?.config?.responsive || {};
@@ -34,9 +34,16 @@ export const initBreakpointsResource = ( reg: MCPRegistryEntry ) => {
34
34
  ],
35
35
  } );
36
36
 
37
- mcpServer.resource( 'breakpoints ', BREAKPOINTS_SCHEMA_URI, () => {
38
- return buildResourceResponse();
39
- } );
37
+ resource(
38
+ 'breakpoints ',
39
+ BREAKPOINTS_SCHEMA_URI,
40
+ {
41
+ description: 'Breakpoints list.',
42
+ },
43
+ () => {
44
+ return buildResourceResponse();
45
+ }
46
+ );
40
47
 
41
48
  window.addEventListener( v1ReadyEvent().name, () => {
42
49
  sendResourceUpdated( {
@@ -39,7 +39,7 @@ type ElementorContainer = {
39
39
  export const DOCUMENT_STRUCTURE_URI = 'elementor://document/structure';
40
40
 
41
41
  export const initDocumentStructureResource = ( reg: MCPRegistryEntry ) => {
42
- const { mcpServer, sendResourceUpdated } = reg;
42
+ const { resource, sendResourceUpdated } = reg;
43
43
 
44
44
  let currentDocumentStructure: string | null = null;
45
45
 
@@ -69,18 +69,23 @@ export const initDocumentStructureResource = ( reg: MCPRegistryEntry ) => {
69
69
  // Initialize on load
70
70
  updateDocumentStructure();
71
71
 
72
- mcpServer.resource( 'document-structure', DOCUMENT_STRUCTURE_URI, async () => {
73
- const structure = getDocumentStructure();
74
-
75
- return {
76
- contents: [
77
- {
78
- uri: DOCUMENT_STRUCTURE_URI,
79
- text: JSON.stringify( structure, null, 2 ),
80
- },
81
- ],
82
- };
83
- } );
72
+ resource(
73
+ 'document-structure',
74
+ DOCUMENT_STRUCTURE_URI,
75
+ {
76
+ description: 'Document structure.',
77
+ },
78
+ async () => {
79
+ return {
80
+ contents: [
81
+ {
82
+ uri: DOCUMENT_STRUCTURE_URI,
83
+ text: JSON.stringify( getDocumentStructure(), null, 2 ),
84
+ },
85
+ ],
86
+ };
87
+ }
88
+ );
84
89
  };
85
90
 
86
91
  function getDocumentStructure() {
@@ -1,4 +1,4 @@
1
- import { zod as z } from '@elementor/editor-mcp';
1
+ import { z } from '@elementor/schema';
2
2
 
3
3
  import { STYLE_SCHEMA_URI, WIDGET_SCHEMA_URI } from '../../resources/widgets-schema-resource';
4
4