@elementor/editor-canvas 4.1.0-manual → 4.2.0-839

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 (41) hide show
  1. package/dist/index.d.mts +22 -4
  2. package/dist/index.d.ts +22 -4
  3. package/dist/index.js +773 -137
  4. package/dist/index.mjs +730 -86
  5. package/package.json +18 -18
  6. package/src/__tests__/settings-props-resolver.test.ts +3 -0
  7. package/src/composition-builder/composition-builder.ts +5 -5
  8. package/src/form-structure/utils.ts +4 -0
  9. package/src/hooks/__tests__/use-style-items.test.ts +55 -0
  10. package/src/hooks/use-style-items.ts +12 -14
  11. package/src/index.ts +2 -0
  12. package/src/init-settings-transformers.ts +4 -0
  13. package/src/init-style-transformers.ts +2 -0
  14. package/src/legacy/create-nested-templated-element-type.ts +11 -2
  15. package/src/legacy/create-pending-element.ts +74 -0
  16. package/src/legacy/create-templated-element-type.ts +2 -2
  17. package/src/legacy/types.ts +9 -1
  18. package/src/mcp/canvas-mcp.ts +8 -0
  19. package/src/mcp/resources/available-widgets-resource.ts +67 -0
  20. package/src/mcp/resources/document-structure-resource.ts +51 -36
  21. package/src/mcp/resources/editor-state-resource.ts +122 -0
  22. package/src/mcp/resources/general-context-resource.ts +99 -0
  23. package/src/mcp/resources/selected-element-resource.ts +217 -0
  24. package/src/mcp/resources/widgets-schema-resource.ts +74 -14
  25. package/src/mcp/tools/build-composition/prompt.ts +6 -0
  26. package/src/mcp/tools/build-composition/tool.ts +26 -0
  27. package/src/mcp/tools/configure-element/prompt.ts +6 -6
  28. package/src/mcp/tools/configure-element/schema.ts +1 -1
  29. package/src/mcp/tools/configure-element/tool.ts +12 -0
  30. package/src/mcp/tools/get-element-config/tool.ts +13 -3
  31. package/src/mcp/utils/do-update-element-property.ts +1 -1
  32. package/src/mcp/utils/element-data-util.ts +46 -0
  33. package/src/mcp/utils/validate-input.ts +1 -1
  34. package/src/sync/global-styles-imported-event.ts +8 -0
  35. package/src/transformers/settings/date-range-transformer.ts +12 -0
  36. package/src/transformers/settings/time-range-transformer.ts +12 -0
  37. package/src/transformers/shared/image-src-transformer.ts +2 -0
  38. package/src/transformers/shared/image-transformer.ts +4 -1
  39. package/src/transformers/styles/span-transformer.ts +5 -0
  40. package/src/utils/after-render.ts +26 -0
  41. package/src/mcp/utils/generate-available-tags.ts +0 -23
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.1.0-manual",
4
+ "version": "4.2.0-839",
5
5
  "private": false,
6
6
  "author": "Elementor Team",
7
7
  "homepage": "https://elementor.com/",
@@ -37,25 +37,25 @@
37
37
  "react-dom": "^18.3.1"
38
38
  },
39
39
  "dependencies": {
40
- "@elementor/editor": "4.1.0-manual",
40
+ "@elementor/editor": "4.2.0-839",
41
41
  "dompurify": "^3.2.6",
42
- "@elementor/editor-controls": "4.1.0-manual",
43
- "@elementor/editor-documents": "4.1.0-manual",
44
- "@elementor/editor-elements": "4.1.0-manual",
45
- "@elementor/editor-interactions": "4.1.0-manual",
46
- "@elementor/editor-mcp": "4.1.0-manual",
47
- "@elementor/editor-notifications": "4.1.0-manual",
48
- "@elementor/editor-props": "4.1.0-manual",
49
- "@elementor/editor-responsive": "4.1.0-manual",
50
- "@elementor/editor-styles": "4.1.0-manual",
51
- "@elementor/editor-styles-repository": "4.1.0-manual",
52
- "@elementor/editor-ui": "4.1.0-manual",
53
- "@elementor/editor-v1-adapters": "4.1.0-manual",
54
- "@elementor/schema": "4.1.0-manual",
55
- "@elementor/twing": "4.1.0-manual",
42
+ "@elementor/editor-controls": "4.2.0-839",
43
+ "@elementor/editor-documents": "4.2.0-839",
44
+ "@elementor/editor-elements": "4.2.0-839",
45
+ "@elementor/editor-interactions": "4.2.0-839",
46
+ "@elementor/editor-mcp": "4.2.0-839",
47
+ "@elementor/editor-notifications": "4.2.0-839",
48
+ "@elementor/editor-props": "4.2.0-839",
49
+ "@elementor/editor-responsive": "4.2.0-839",
50
+ "@elementor/editor-styles": "4.2.0-839",
51
+ "@elementor/editor-styles-repository": "4.2.0-839",
52
+ "@elementor/editor-ui": "4.2.0-839",
53
+ "@elementor/editor-v1-adapters": "4.2.0-839",
54
+ "@elementor/schema": "4.2.0-839",
55
+ "@elementor/twing": "4.2.0-839",
56
56
  "@elementor/ui": "1.37.5",
57
- "@elementor/utils": "4.1.0-manual",
58
- "@elementor/wp-media": "4.1.0-manual",
57
+ "@elementor/utils": "4.2.0-839",
58
+ "@elementor/wp-media": "4.2.0-839",
59
59
  "@floating-ui/react": "^0.27.5",
60
60
  "@wordpress/i18n": "^5.13.0"
61
61
  },
@@ -103,6 +103,7 @@ describe( 'settings props resolver', () => {
103
103
  expected: {
104
104
  image: {
105
105
  src: 'https://localhost.test/test-image.png',
106
+ alt: '',
106
107
  },
107
108
  },
108
109
  },
@@ -130,6 +131,7 @@ describe( 'settings props resolver', () => {
130
131
  src: 'large-image-url-123',
131
132
  height: 3,
132
133
  width: 3,
134
+ alt: undefined,
133
135
  },
134
136
  },
135
137
  },
@@ -157,6 +159,7 @@ describe( 'settings props resolver', () => {
157
159
  src: 'original-image-url-123',
158
160
  height: 0,
159
161
  width: 0,
162
+ alt: undefined,
160
163
  },
161
164
  },
162
165
  },
@@ -34,7 +34,7 @@ type CtorOptions = {
34
34
  export class CompositionBuilder {
35
35
  private elementConfig: Record< string, Record< string, AnyValue > > = {};
36
36
  private elementStylesConfig: Record< string, Record< string, AnyValue > > = {};
37
- private elementCusomCSS: Record< string, string > = {};
37
+ private elementCustomCSS: Record< string, string > = {};
38
38
  private rootContainers: V1Element[] = [];
39
39
  private api: API = {
40
40
  createElement,
@@ -76,7 +76,7 @@ export class CompositionBuilder {
76
76
  }
77
77
 
78
78
  setCustomCSS( config: Record< string, string > ) {
79
- this.elementCusomCSS = config;
79
+ this.elementCustomCSS = config;
80
80
  }
81
81
 
82
82
  getXML() {
@@ -176,7 +176,7 @@ export class CompositionBuilder {
176
176
  const allConfigIds = new Set( [
177
177
  ...Object.keys( this.elementConfig ),
178
178
  ...Object.keys( this.elementStylesConfig ),
179
- ...Object.keys( this.elementCusomCSS ),
179
+ ...Object.keys( this.elementCustomCSS ),
180
180
  ] );
181
181
 
182
182
  for ( const configId of allConfigIds ) {
@@ -188,7 +188,7 @@ export class CompositionBuilder {
188
188
  if ( this.elementConfig[ configId ] ) {
189
189
  configErrors.push( msg );
190
190
  }
191
- if ( this.elementStylesConfig[ configId ] || this.elementCusomCSS[ configId ] ) {
191
+ if ( this.elementStylesConfig[ configId ] || this.elementCustomCSS[ configId ] ) {
192
192
  styleErrors.push( msg );
193
193
  }
194
194
  continue;
@@ -241,7 +241,7 @@ export class CompositionBuilder {
241
241
  }
242
242
  }
243
243
 
244
- const customCSS = this.elementCusomCSS[ configId ];
244
+ const customCSS = this.elementCustomCSS[ configId ];
245
245
  if ( customCSS ) {
246
246
  try {
247
247
  this.api.doUpdateElementProperty( {
@@ -41,6 +41,10 @@ export const FORM_FIELD_ELEMENT_TYPES = new Set( [
41
41
  'e-form-checkbox',
42
42
  'e-form-submit-button',
43
43
  'e-form-select',
44
+ 'e-form-radio-button',
45
+ 'e-form-file-upload',
46
+ 'e-form-date-picker',
47
+ 'e-form-time-picker',
44
48
  ] );
45
49
 
46
50
  export function getArgsElementType( args: CreateArgs ): string | undefined {
@@ -408,6 +408,61 @@ describe( 'useStyleItems', () => {
408
408
  ] );
409
409
  } );
410
410
 
411
+ it( 'should remove cached items for breakpoints whose variants no longer exist', async () => {
412
+ // Arrange.
413
+ const renderStylesMock = jest.fn().mockImplementation( ( { styles } ) =>
414
+ Promise.resolve(
415
+ styles.map( ( style: StyleDefinition ) => ( {
416
+ id: style.id,
417
+ breakpoint: style?.variants[ 0 ]?.meta.breakpoint || 'desktop',
418
+ } ) )
419
+ )
420
+ );
421
+
422
+ jest.mocked( useStyleRenderer ).mockReturnValue( renderStylesMock );
423
+
424
+ const styleWithTwoBreakpoints = createMockStyleDefinitionWithVariants( {
425
+ id: 'style1',
426
+ variants: [
427
+ { meta: { breakpoint: null, state: null }, props: { 'font-family': 'Arial' }, custom_css: null },
428
+ { meta: { breakpoint: 'tablet', state: null }, props: { 'font-family': 'Roboto' }, custom_css: null },
429
+ ],
430
+ } );
431
+
432
+ const mockProvider = createMockStylesProvider( { key: 'provider1', priority: 1 }, [ styleWithTwoBreakpoints ] );
433
+
434
+ jest.mocked( stylesRepository ).getProviders.mockReturnValue( [ mockProvider ] );
435
+
436
+ const { result } = renderHook( () => useStyleItems() );
437
+
438
+ await act( async () => {
439
+ mockProvider.actions.updateProps?.( {
440
+ id: 'style1',
441
+ meta: { breakpoint: null, state: null },
442
+ props: {},
443
+ } );
444
+ } );
445
+
446
+ expect( result.current ).toEqual( [
447
+ { id: 'style1', breakpoint: 'desktop' },
448
+ { id: 'style1', breakpoint: 'tablet' },
449
+ ] );
450
+
451
+ // Act - simulate the tablet variant being removed (e.g. user cleared its only prop,
452
+ // global-classes store calls getNonEmptyVariants which drops empty variants).
453
+ await act( async () => {
454
+ mockProvider.actions.update?.( {
455
+ id: 'style1',
456
+ variants: [
457
+ { meta: { breakpoint: null, state: null }, props: { 'font-family': 'Arial' }, custom_css: null },
458
+ ],
459
+ } );
460
+ } );
461
+
462
+ // Assert - the tablet item must be evicted from the cache.
463
+ expect( result.current ).toEqual( [ { id: 'style1', breakpoint: 'desktop' } ] );
464
+ } );
465
+
411
466
  it( 'should only re-render changed styles on differential update', async () => {
412
467
  // Arrange.
413
468
  const renderStylesMock = jest.fn().mockImplementation( ( { styles } ) =>
@@ -200,8 +200,10 @@ function createProviderSubscriber( { provider, renderStyles, setStyleItems, getC
200
200
  cssName: provider.actions.resolveCssName( style.id ),
201
201
  } ) );
202
202
 
203
- return renderStyles( { styles: breakToBreakpoints( changedStyles ), signal } ).then( ( rendered ) => {
204
- updateCacheItems( cache, rendered );
203
+ const breakpointSplit = breakToBreakpoints( changedStyles );
204
+
205
+ return renderStyles( { styles: breakpointSplit, signal } ).then( ( rendered ) => {
206
+ updateCacheItems( cache, changedIds, rendered );
205
207
 
206
208
  return getOrderedItems( cache );
207
209
  } );
@@ -277,19 +279,15 @@ function getOrderedItems( cache: StyleItemsCache ): StyleItem[] {
277
279
  .flat();
278
280
  }
279
281
 
280
- function updateCacheItems( cache: StyleItemsCache, changedItems: StyleItem[] ): void {
282
+ function updateCacheItems( cache: StyleItemsCache, changedIds: string[], changedItems: StyleItem[] ): void {
283
+ for ( const id of changedIds ) {
284
+ cache.itemsById.delete( id );
285
+ }
286
+
281
287
  for ( const item of changedItems ) {
282
- const existing = cache.itemsById.get( item.id );
283
- if ( existing ) {
284
- const idx = existing.findIndex( ( e ) => e.breakpoint === item.breakpoint && e.state === item.state );
285
- if ( idx >= 0 ) {
286
- existing[ idx ] = item;
287
- } else {
288
- existing.push( item );
289
- }
290
- } else {
291
- cache.itemsById.set( item.id, [ item ] );
292
- }
288
+ const existing = cache.itemsById.get( item.id ) || [];
289
+ existing.push( item );
290
+ cache.itemsById.set( item.id, existing );
293
291
  }
294
292
  }
295
293
 
package/src/index.ts CHANGED
@@ -21,6 +21,7 @@ export { createPropsResolver, type PropsResolver } from './renderers/create-prop
21
21
  export { settingsTransformersRegistry } from './settings-transformers-registry';
22
22
  export { styleTransformersRegistry } from './style-transformers-registry';
23
23
  export { endDragElementFromPanel, startDragElementFromPanel } from './sync/drag-element-from-panel';
24
+ export { GLOBAL_STYLES_IMPORTED_EVENT, type ImportedGlobalStylesPayload } from './sync/global-styles-imported-event';
24
25
  export { DOCUMENT_STRUCTURE_URI } from './mcp/resources/document-structure-resource';
25
26
  export { WIDGET_SCHEMA_URI } from './mcp/resources/widgets-schema-resource';
26
27
  export * from './legacy/types';
@@ -31,3 +32,4 @@ export {
31
32
  } from './transformers/create-transformers-registry';
32
33
  export { type AnyTransformer, type TransformerOptions } from './transformers/types';
33
34
  export { UnknownStyleTypeError, UnknownStyleStateError } from './renderers/errors';
35
+ export { doAfterRender } from './utils/after-render';
@@ -1,11 +1,13 @@
1
1
  import { settingsTransformersRegistry } from './settings-transformers-registry';
2
2
  import { attributesTransformer } from './transformers/settings/attributes-transformer';
3
3
  import { createClassesTransformer } from './transformers/settings/classes-transformer';
4
+ import { dateRangeTransformer } from './transformers/settings/date-range-transformer';
4
5
  import { dateTimeTransformer } from './transformers/settings/date-time-transformer';
5
6
  import { htmlV2Transformer } from './transformers/settings/html-v2-transformer';
6
7
  import { htmlV3Transformer } from './transformers/settings/html-v3-transformer';
7
8
  import { linkTransformer } from './transformers/settings/link-transformer';
8
9
  import { queryTransformer } from './transformers/settings/query-transformer';
10
+ import { timeRangeTransformer } from './transformers/settings/time-range-transformer';
9
11
  import { imageSrcTransformer } from './transformers/shared/image-src-transformer';
10
12
  import { imageTransformer } from './transformers/shared/image-transformer';
11
13
  import { plainTransformer } from './transformers/shared/plain-transformer';
@@ -25,5 +27,7 @@ export function initSettingsTransformers() {
25
27
  .register( 'date-time', dateTimeTransformer )
26
28
  .register( 'html-v2', htmlV2Transformer )
27
29
  .register( 'html-v3', htmlV3Transformer )
30
+ .register( 'date-range', dateRangeTransformer )
31
+ .register( 'time-range', timeRangeTransformer )
28
32
  .registerFallback( plainTransformer );
29
33
  }
@@ -17,6 +17,7 @@ import { perspectiveOriginTransformer } from './transformers/styles/perspective-
17
17
  import { positionTransformer } from './transformers/styles/position-transformer';
18
18
  import { shadowTransformer } from './transformers/styles/shadow-transformer';
19
19
  import { sizeTransformer } from './transformers/styles/size-transformer';
20
+ import { spanTransformer } from './transformers/styles/span-transformer';
20
21
  import { strokeTransformer } from './transformers/styles/stroke-transformer';
21
22
  import { transformFunctionsTransformer } from './transformers/styles/transform-functions-transformer';
22
23
  import { transformMoveTransformer } from './transformers/styles/transform-move-transformer';
@@ -53,6 +54,7 @@ export function initStyleTransformers() {
53
54
  .register( 'image-src', imageSrcTransformer )
54
55
  .register( 'image', imageTransformer )
55
56
  .register( 'object-position', positionTransformer )
57
+ .register( 'span', spanTransformer )
56
58
  .register( 'transform-origin', transformOriginTransformer )
57
59
  .register( 'perspective-origin', perspectiveOriginTransformer )
58
60
  .register( 'transform-move', transformMoveTransformer )
@@ -1,7 +1,8 @@
1
- import { ELEMENT_STYLE_CHANGE_EVENT } from '@elementor/editor-elements';
1
+ import { ELEMENT_STYLE_CHANGE_EVENT, type V1ElementModelProps } from '@elementor/editor-elements';
2
2
 
3
3
  import { type DomRenderer } from '../renderers/create-dom-renderer';
4
4
  import { signalizedProcess } from '../utils/signalized-process';
5
+ import { createPendingElement } from './create-pending-element';
5
6
  import { canBeTemplated, type TemplatedElementConfig } from './create-templated-element-type';
6
7
  import {
7
8
  createAfterRender,
@@ -101,6 +102,7 @@ export function createNestedTemplatedElementView( {
101
102
  const AtomicElementBaseView = legacyWindow.elementor.modules.elements.views.createAtomicElementBase( type );
102
103
  const parentRenderChildren = AtomicElementBaseView.prototype._renderChildren;
103
104
  const parentOpenEditingPanel = AtomicElementBaseView.prototype._openEditingPanel;
105
+ const parentAddElement = AtomicElementBaseView.prototype.addElement;
104
106
 
105
107
  return AtomicElementBaseView.extend( {
106
108
  _abortController: null as AbortController | null,
@@ -254,7 +256,6 @@ export function createNestedTemplatedElementView( {
254
256
  targetEl.setAttribute( attr.name, attr.value );
255
257
  } );
256
258
 
257
- targetEl.setAttribute( 'draggable', 'true' );
258
259
  targetEl.innerHTML = overlayHTML + newEl.innerHTML;
259
260
 
260
261
  if ( needsTagSwap ) {
@@ -381,6 +382,14 @@ export function createNestedTemplatedElementView( {
381
382
  this._doAfterRender( () => parentOpenEditingPanel.call( this, options ) );
382
383
  },
383
384
 
385
+ addElement( data: Partial< V1ElementModelProps >, options?: { edit?: boolean; at?: number } ) {
386
+ if ( this.isRendered ) {
387
+ return parentAddElement.call( this, data, options );
388
+ }
389
+
390
+ return createPendingElement( this, data, options );
391
+ },
392
+
384
393
  getInteractionId() {
385
394
  const originId = this.model.get( 'originId' );
386
395
  const id = this.model.get( 'id' );
@@ -0,0 +1,74 @@
1
+ import {
2
+ addModelToParent,
3
+ findModelInDocument,
4
+ generateElementId,
5
+ getContainer,
6
+ type V1Element,
7
+ type V1ElementModelProps,
8
+ } from '@elementor/editor-elements';
9
+
10
+ import { type ElementView } from './types';
11
+
12
+ export function createPendingElement(
13
+ wrapperView: ElementView,
14
+ data: Partial< V1ElementModelProps >,
15
+ options: { edit?: boolean; at?: number } = {}
16
+ ): { getContainer: () => V1Element } | undefined {
17
+ const parentContainer = wrapperView.getContainer();
18
+ const model: Partial< V1ElementModelProps > = { ...data };
19
+
20
+ if ( ! model.id ) {
21
+ model.id = generateElementId();
22
+ }
23
+
24
+ if ( ! model.elements ) {
25
+ model.elements = [];
26
+ }
27
+
28
+ const added = addModelToParent( parentContainer.id, model as V1ElementModelProps, options );
29
+
30
+ if ( ! added ) {
31
+ return undefined;
32
+ }
33
+
34
+ const childId = model.id;
35
+ const childModel = findModelInDocument( childId );
36
+
37
+ if ( ! childModel ) {
38
+ return undefined;
39
+ }
40
+
41
+ const pendingContainer: V1Element = {
42
+ id: childId,
43
+ settings: { get: () => ( {} ), set: () => ( {} ), toJSON: () => ( {} ) } as V1Element[ 'settings' ],
44
+ parent: parentContainer,
45
+ model: childModel as V1Element[ 'model' ],
46
+ view: undefined,
47
+ lookup() {
48
+ return getContainer( childId ) ?? pendingContainer;
49
+ },
50
+ };
51
+
52
+ wrapperView.once( 'render', () => {
53
+ wrapperView.model?.trigger?.( 'navigator:add', childModel, options );
54
+ } );
55
+
56
+ if ( options.edit !== false ) {
57
+ selectChildWhenWrapperRenders( wrapperView, childId );
58
+ }
59
+
60
+ return { getContainer: () => pendingContainer };
61
+ }
62
+
63
+ function selectChildWhenWrapperRenders( wrapperView: ElementView, childId: string ): void {
64
+ wrapperView.once( 'render', () => {
65
+ const childContainer = getContainer( childId );
66
+
67
+ if ( childContainer?.model?.trigger ) {
68
+ childContainer.model.trigger( 'request:edit' );
69
+ return;
70
+ }
71
+
72
+ wrapperView.model?.trigger?.( 'request:edit' );
73
+ } );
74
+ }
@@ -13,10 +13,10 @@ import {
13
13
  } from './twig-rendering-utils';
14
14
  import {
15
15
  type ElementType,
16
- type ElementView,
17
16
  type LegacyWindow,
18
17
  type NamespacedRenderContext,
19
18
  type RenderContext,
19
+ type TemplatedElementView,
20
20
  } from './types';
21
21
 
22
22
  export type CreateTemplatedElementTypeOptions = {
@@ -66,7 +66,7 @@ export function createTemplatedElementView( {
66
66
  type,
67
67
  renderer,
68
68
  element,
69
- }: CreateTemplatedElementTypeOptions ): typeof ElementView {
69
+ }: CreateTemplatedElementTypeOptions ): typeof TemplatedElementView {
70
70
  const BaseView = createElementViewClassDeclaration();
71
71
 
72
72
  const { templateKey, baseStylesDictionary, resolveProps } = setupTwigRenderer( {
@@ -1,5 +1,5 @@
1
1
  import { type Root } from 'react-dom/client';
2
- import { type V1Element } from '@elementor/editor-elements';
2
+ import { type V1Element, type V1ElementModelProps } from '@elementor/editor-elements';
3
3
  import { type Props, type PropValue } from '@elementor/editor-props';
4
4
 
5
5
  export type RenderContext< T = unknown > = Record< string, T >;
@@ -89,6 +89,8 @@ export declare class ElementView {
89
89
 
90
90
  constructor( ...args: unknown[] );
91
91
 
92
+ addElement( data: Partial< V1ElementModelProps >, options?: object ): unknown;
93
+
92
94
  onRender( ...args: unknown[] ): void;
93
95
 
94
96
  onDestroy( ...args: unknown[] ): void;
@@ -163,6 +165,12 @@ export declare class ElementView {
163
165
  _openEditingPanel( options?: { scrollIntoView: boolean } ): void;
164
166
 
165
167
  once: ( event: string, callback: () => void ) => void;
168
+
169
+ getContainer(): V1Element;
170
+ }
171
+
172
+ export declare class TemplatedElementView extends ElementView {
173
+ _doAfterRender( callback: () => void ): void;
166
174
  }
167
175
 
168
176
  type JQueryElement = {
@@ -1,7 +1,11 @@
1
1
  import { type MCPRegistryEntry } from '@elementor/editor-mcp';
2
2
 
3
+ import { initAvailableWidgetsResource } from './resources/available-widgets-resource';
3
4
  import { initBreakpointsResource } from './resources/breakpoints-resource';
4
5
  import { initDocumentStructureResource } from './resources/document-structure-resource';
6
+ import { initEditorStateResource } from './resources/editor-state-resource';
7
+ import { initGeneralContextResource } from './resources/general-context-resource';
8
+ import { initSelectedElementResource } from './resources/selected-element-resource';
5
9
  import { initWidgetsSchemaResource } from './resources/widgets-schema-resource';
6
10
  import { initBuildCompositionsTool } from './tools/build-composition/tool';
7
11
  import { initConfigureElementTool } from './tools/configure-element/tool';
@@ -18,7 +22,11 @@ export const initCanvasMcp = ( reg: MCPRegistryEntry ) => {
18
22
  `
19
23
  );
20
24
  initWidgetsSchemaResource( reg );
25
+ initAvailableWidgetsResource( reg );
21
26
  initDocumentStructureResource( reg );
27
+ initSelectedElementResource( reg );
28
+ initEditorStateResource( reg );
29
+ initGeneralContextResource( reg );
22
30
  initBuildCompositionsTool( reg );
23
31
  initGetElementConfigTool( reg );
24
32
  initConfigureElementTool( reg );
@@ -0,0 +1,67 @@
1
+ import { type MCPRegistryEntry } from '@elementor/editor-mcp';
2
+ import { v1ReadyEvent } from '@elementor/editor-v1-adapters';
3
+
4
+ import { type AvailableWidget, getAvailableWidgets } from '../utils/element-data-util';
5
+
6
+ export const AVAILABLE_WIDGETS_URI = 'elementor://context/available-widgets';
7
+ export const AVAILABLE_WIDGETS_URI_V4 = 'elementor://context/available-widgets/v4';
8
+
9
+ export const initAvailableWidgetsResource = ( reg: MCPRegistryEntry ) => {
10
+ const { resource, sendResourceUpdated } = reg;
11
+
12
+ const buildContents = ( uri: string, filterFunction: ( x: AvailableWidget ) => boolean = () => true ) => {
13
+ const widgets = getAvailableWidgets().filter( filterFunction );
14
+ return {
15
+ contents: [
16
+ {
17
+ uri,
18
+ mimeType: 'application/json',
19
+ text: JSON.stringify( widgets, null, 2 ),
20
+ },
21
+ ],
22
+ };
23
+ };
24
+
25
+ const notifyResourcesUpdated = () => {
26
+ sendResourceUpdated( {
27
+ uri: AVAILABLE_WIDGETS_URI,
28
+ ...buildContents( AVAILABLE_WIDGETS_URI ),
29
+ } );
30
+ sendResourceUpdated( {
31
+ uri: AVAILABLE_WIDGETS_URI_V4,
32
+ ...buildContents( AVAILABLE_WIDGETS_URI_V4, ( w: AvailableWidget ) => w.version === 'v4' ),
33
+ } );
34
+ };
35
+
36
+ resource(
37
+ 'available-widgets-v4',
38
+ AVAILABLE_WIDGETS_URI_V4,
39
+ {
40
+ description: 'All registered v4 version widgets',
41
+ },
42
+ async () => buildContents( AVAILABLE_WIDGETS_URI_V4, ( w ) => w.version === 'v4' )
43
+ );
44
+
45
+ resource(
46
+ 'available-widgets',
47
+ AVAILABLE_WIDGETS_URI,
48
+ {
49
+ description: 'All registered widget types with v3/v4 version metadata and description.',
50
+ },
51
+ async () => buildContents( AVAILABLE_WIDGETS_URI )
52
+ );
53
+
54
+ const eventName = v1ReadyEvent().name;
55
+
56
+ const onV1Ready = () => {
57
+ const widgets = getAvailableWidgets();
58
+ if ( widgets.length === 0 ) {
59
+ return;
60
+ }
61
+ window.removeEventListener( eventName, onV1Ready );
62
+ notifyResourcesUpdated();
63
+ };
64
+
65
+ window.addEventListener( eventName, onV1Ready );
66
+ onV1Ready();
67
+ };