@elementor/editor-canvas 4.2.0-923 → 4.2.0-925

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.mjs CHANGED
@@ -2397,7 +2397,7 @@ function initStyleTransformers() {
2397
2397
 
2398
2398
  // src/legacy/init-legacy-views.ts
2399
2399
  import { getWidgetsCache as getWidgetsCache3 } from "@elementor/editor-elements";
2400
- import { __privateListenTo, v1ReadyEvent as v1ReadyEvent2 } from "@elementor/editor-v1-adapters";
2400
+ import { __privateIsReady as isV1Ready, __privateListenTo, v1ReadyEvent as v1ReadyEvent2 } from "@elementor/editor-v1-adapters";
2401
2401
 
2402
2402
  // src/renderers/create-dom-renderer.ts
2403
2403
  import { createArrayLoader, createEnvironment } from "@elementor/twing";
@@ -3698,22 +3698,29 @@ function registerModelExtensions(type, extensions) {
3698
3698
  }
3699
3699
  function registerElementType(type, elementTypeGenerator) {
3700
3700
  elementsLegacyTypes[type] = elementTypeGenerator;
3701
+ if (isV1Ready()) {
3702
+ registerElementInLegacyManager(type, createDomRenderer());
3703
+ }
3701
3704
  }
3702
3705
  function initLegacyViews() {
3703
3706
  __privateListenTo(v1ReadyEvent2(), () => {
3704
3707
  const widgetsCache = getWidgetsCache3() ?? {};
3705
- const legacyWindow = window;
3706
3708
  const renderer = createDomRenderer();
3707
3709
  registerProPromotionTypes(widgetsCache);
3708
- Object.entries(widgetsCache).forEach(([type, element]) => {
3709
- if (!element.atomic) {
3710
- return;
3711
- }
3712
- const ResolvedElementType = resolveElementType(type, renderer, element);
3713
- tryRegisterElement(legacyWindow, type, element, ResolvedElementType);
3710
+ Object.keys(widgetsCache).forEach((type) => {
3711
+ registerElementInLegacyManager(type, renderer);
3714
3712
  });
3715
3713
  });
3716
3714
  }
3715
+ function registerElementInLegacyManager(type, renderer) {
3716
+ const element = (getWidgetsCache3() ?? {})[type];
3717
+ if (!element?.atomic) {
3718
+ return;
3719
+ }
3720
+ const legacyWindow = window;
3721
+ const ResolvedElementType = resolveElementType(type, renderer, element);
3722
+ tryRegisterElement(legacyWindow, type, element, ResolvedElementType);
3723
+ }
3717
3724
  function registerProPromotionTypes(widgetsCache) {
3718
3725
  Object.entries(widgetsCache).forEach(([type, element]) => {
3719
3726
  if (element.meta?.is_pro_promotion) {
@@ -3787,6 +3794,9 @@ function initTabsModelExtensions() {
3787
3794
  registerModelExtensions("e-tab", tabModelExtensions);
3788
3795
  }
3789
3796
 
3797
+ // src/mcp/canvas-mcp.ts
3798
+ import { Schema as Schema6 } from "@elementor/editor-props";
3799
+
3790
3800
  // src/mcp/resources/available-widgets-resource.ts
3791
3801
  import { v1ReadyEvent as v1ReadyEvent3 } from "@elementor/editor-v1-adapters";
3792
3802
  var AVAILABLE_WIDGETS_URI = "elementor://context/available-widgets";
@@ -3938,6 +3948,107 @@ function extractElementData(element) {
3938
3948
  return result;
3939
3949
  }
3940
3950
 
3951
+ // src/mcp/resources/dynamic-tags-resource.ts
3952
+ import { Schema as Schema2 } from "@elementor/editor-props";
3953
+
3954
+ // src/mcp/utils/resolve-dynamic-tag.ts
3955
+ import { getElementorConfig } from "@elementor/editor-v1-adapters";
3956
+ var DYNAMIC_PROP_TYPE_KEY = "dynamic";
3957
+ var OMITTED_DYNAMIC_SETTING_KEYS = ["fallback"];
3958
+ var getAtomicDynamicTags = () => {
3959
+ const config = getElementorConfig();
3960
+ return config.atomicDynamicTags?.tags ?? {};
3961
+ };
3962
+ var getDynamicTagNamesByCategories = (categories) => {
3963
+ if (!categories.length) {
3964
+ return [];
3965
+ }
3966
+ const wanted = new Set(categories);
3967
+ return Object.values(getAtomicDynamicTags()).filter((tag) => tag.categories?.some((category) => wanted.has(category))).map((tag) => tag.name);
3968
+ };
3969
+ var dynamicTagLLMResolver = (value) => {
3970
+ const input = value ?? {};
3971
+ const tag = input.name ? getAtomicDynamicTags()[input.name] : void 0;
3972
+ if (!tag) {
3973
+ return {
3974
+ $$type: DYNAMIC_PROP_TYPE_KEY,
3975
+ value: { name: input.name ?? "", group: "", settings: {} }
3976
+ };
3977
+ }
3978
+ return {
3979
+ $$type: DYNAMIC_PROP_TYPE_KEY,
3980
+ value: {
3981
+ name: tag.name,
3982
+ group: tag.group,
3983
+ settings: buildStrictSettings(tag.props_schema ?? {}, input.settings ?? {})
3984
+ }
3985
+ };
3986
+ };
3987
+ var buildStrictSettings = (schema2, provided) => {
3988
+ const settings = {};
3989
+ for (const [key, propType] of Object.entries(schema2)) {
3990
+ if (OMITTED_DYNAMIC_SETTING_KEYS.includes(key)) {
3991
+ continue;
3992
+ }
3993
+ const resolved = provided[key] !== void 0 ? wrapSettingValue(provided[key], propType) : defaultSettingValue(propType);
3994
+ if (resolved !== void 0 && resolved !== null) {
3995
+ settings[key] = resolved;
3996
+ }
3997
+ }
3998
+ return settings;
3999
+ };
4000
+ var wrapSettingValue = (raw, propType) => {
4001
+ if (raw !== null && typeof raw === "object") {
4002
+ return raw;
4003
+ }
4004
+ return propType.key ? { $$type: propType.key, value: raw } : raw;
4005
+ };
4006
+ var defaultSettingValue = (propType) => {
4007
+ if (propType.initial_value !== null && propType.initial_value !== void 0) {
4008
+ return propType.initial_value;
4009
+ }
4010
+ if (propType.default !== null && propType.default !== void 0) {
4011
+ return wrapSettingValue(propType.default, propType);
4012
+ }
4013
+ return void 0;
4014
+ };
4015
+
4016
+ // src/mcp/resources/dynamic-tags-resource.ts
4017
+ var DYNAMIC_TAGS_URI = "elementor://dynamic-tags";
4018
+ var settingsSchema = (propsSchema) => {
4019
+ return Object.fromEntries(
4020
+ Object.entries(propsSchema ?? {}).filter(([key]) => !OMITTED_DYNAMIC_SETTING_KEYS.includes(key)).map(([key, propType]) => [key, Schema2.propTypeToJsonSchema(propType)])
4021
+ );
4022
+ };
4023
+ var buildDynamicTagsList = () => {
4024
+ return Object.values(getAtomicDynamicTags()).map((tag) => ({
4025
+ name: tag.name,
4026
+ label: tag.label,
4027
+ categories: tag.categories,
4028
+ settings: settingsSchema(tag.props_schema)
4029
+ }));
4030
+ };
4031
+ var initDynamicTagsResource = (reg) => {
4032
+ const { resource } = reg;
4033
+ resource(
4034
+ "dynamic-tags",
4035
+ DYNAMIC_TAGS_URI,
4036
+ {
4037
+ description: `List of available dynamic tags. To bind a property to a dynamic source, set its value to { "$$type": "dynamic", "value": { "name": <tag name>, "settings": { ... } } } using a tag whose name appears in that property's allowed list, and populate "settings" per the tag entry here.`,
4038
+ mimeType: "application/json"
4039
+ },
4040
+ async (uri) => ({
4041
+ contents: [
4042
+ {
4043
+ uri: uri.href,
4044
+ mimeType: "application/json",
4045
+ text: JSON.stringify(buildDynamicTagsList())
4046
+ }
4047
+ ]
4048
+ })
4049
+ );
4050
+ };
4051
+
3941
4052
  // src/mcp/resources/editor-state-resource.ts
3942
4053
  import { __privateListenTo as listenTo2, commandEndEvent as commandEndEvent5 } from "@elementor/editor-v1-adapters";
3943
4054
  var CURRENTLY_VIEWED_SCREEN = "The user is currently viewing the Elementor editor";
@@ -4282,7 +4393,7 @@ import {
4282
4393
  updateElementSettings,
4283
4394
  updateElementStyle
4284
4395
  } from "@elementor/editor-elements";
4285
- import { getPropSchemaFromCache, Schema as Schema2 } from "@elementor/editor-props";
4396
+ import { getPropSchemaFromCache, Schema as Schema3 } from "@elementor/editor-props";
4286
4397
  import { getStylesSchema as getStylesSchema3, getVariantByMeta } from "@elementor/editor-styles";
4287
4398
  import { __privateRunCommandSync as runCommandSync2 } from "@elementor/editor-v1-adapters";
4288
4399
 
@@ -4307,9 +4418,12 @@ var LOCAL_STYLE_META = {
4307
4418
  };
4308
4419
  function resolvePropValue(value, forceKey) {
4309
4420
  const Utils = window.elementorV2.editorVariables.Utils;
4310
- return Schema2.adjustLlmPropValueSchema(value, {
4421
+ return Schema3.adjustLlmPropValueSchema(value, {
4311
4422
  forceKey,
4312
- transformers: Utils.globalVariablesLLMResolvers
4423
+ transformers: {
4424
+ ...Utils.globalVariablesLLMResolvers,
4425
+ [DYNAMIC_PROP_TYPE_KEY]: dynamicTagLLMResolver
4426
+ }
4313
4427
  });
4314
4428
  }
4315
4429
  var doUpdateElementProperty = (params) => {
@@ -4412,7 +4526,7 @@ var doUpdateElementProperty = (params) => {
4412
4526
  }
4413
4527
  const propKey = elementPropSchema[propertyName].key;
4414
4528
  const value = resolvePropValue(propertyValue, propKey);
4415
- const { valid, jsonSchema } = Schema2.validatePropValue(elementPropSchema[propertyName], propertyValue);
4529
+ const { valid, jsonSchema } = Schema3.validatePropValue(elementPropSchema[propertyName], propertyValue);
4416
4530
  if (!valid) {
4417
4531
  throw new Error(
4418
4532
  `Invalid PropValue for elementId: ${elementId}. PropKey: ${propKey}, PropValue: ${JSON.stringify(
@@ -4433,7 +4547,7 @@ Expected Schema: ${jsonSchema}`
4433
4547
 
4434
4548
  // src/mcp/utils/validate-input.ts
4435
4549
  import { getWidgetsCache as getWidgetsCache7 } from "@elementor/editor-elements";
4436
- import { Schema as Schema3 } from "@elementor/editor-props";
4550
+ import { Schema as Schema4 } from "@elementor/editor-props";
4437
4551
  import { getStylesSchema as getStylesSchema4 } from "@elementor/editor-styles";
4438
4552
  var _widgetsSchema = null;
4439
4553
  var validateInput = {
@@ -4467,10 +4581,10 @@ var validateInput = {
4467
4581
  if (!propSchema) {
4468
4582
  errors.push(`Property "${propName}" is not defined in the schema.`);
4469
4583
  hasInvalidKey = true;
4470
- } else if (!Schema3.isPropKeyConfigurable(propName, propSchema)) {
4584
+ } else if (!Schema4.isPropKeyConfigurable(propName, propSchema)) {
4471
4585
  errors.push(`Property "${propName}" is not configurable.`);
4472
4586
  } else {
4473
- const { valid } = Schema3.validatePropValue(propSchema, propValue);
4587
+ const { valid } = Schema4.validatePropValue(propSchema, propValue);
4474
4588
  if (!valid) {
4475
4589
  errors.push(
4476
4590
  `Invalid property "${propName}". Validate input with resource [${STYLE_SCHEMA_URI.replace(
@@ -4854,6 +4968,13 @@ Some elements have internal tree structures (nesting). When using these elements
4854
4968
  - NO LINKS in configuration
4855
4969
  - Retry on errors up to 10x
4856
4970
 
4971
+ # DYNAMIC TAGS
4972
+ - A value can be made dynamic wherever its schema exposes a \`"$$type": "dynamic"\` variant. This may be the property root OR a NESTED field (e.g. an image's \`src\`, not the whole \`image\`).
4973
+ - Put the dynamic object EXACTLY at that node, in place of the static variant. The variant's \`name\` lists the allowed tags; read [${DYNAMIC_TAGS_URI}] for each tag's settings schema.
4974
+ - Provide at that node: \`{ "$$type": "dynamic", "value": { "name": "<allowed tag>", "settings": { ... } } }\`
4975
+ - Example (image): \`{ "$$type": "image", "value": { "src": { "$$type": "dynamic", "value": { "name": "<image tag>", "settings": { ... } } } } }\`
4976
+ - Do NOT send \`group\` (it is resolved automatically). Populate \`settings\` strictly per the tag's schema; use \`{}\` only when it has none.
4977
+
4857
4978
  Note about configuration ids: These names are visible to the end-user, make sure they make sense, related and relevant.
4858
4979
 
4859
4980
  # DESIGN PHILOSOPHY: CONTEXT-DRIVEN CREATIVITY
@@ -5075,7 +5196,8 @@ var initBuildCompositionsTool = (reg) => {
5075
5196
  { description: "Global Classes", uri: "elementor://global-classes" },
5076
5197
  { description: "Global Variables", uri: "elementor://global-variables" },
5077
5198
  { description: "Styles best practices", uri: BEST_PRACTICES_URI },
5078
- { description: "Available widgets for this tool", uri: AVAILABLE_WIDGETS_URI_V4 }
5199
+ { description: "Available widgets for this tool", uri: AVAILABLE_WIDGETS_URI_V4 },
5200
+ { description: "Dynamic tags catalog", uri: DYNAMIC_TAGS_URI }
5079
5201
  ],
5080
5202
  outputSchema,
5081
5203
  handler: async (rawParams) => {
@@ -5240,6 +5362,21 @@ For styleProperties, use the style schema provided, as it also uses the PropType
5240
5362
  For all non-primitive types, provide the key property as defined in the schema as $$type in the generated object, as it is MANDATORY for parsing.
5241
5363
 
5242
5364
  Use the EXACT "PROP-TYPE" Schema given, and ALWAYS include the "key" property from the original configuration for every property you are changing.
5365
+
5366
+ # Dynamic tags
5367
+ A value can be made dynamic wherever its schema exposes a variant with "$$type": "dynamic". This may be the property root OR a NESTED field: for example an image is made dynamic on its "src" (the root stays "image"), NOT on the whole "image" value.
5368
+ Put the dynamic object EXACTLY at the node whose schema offers the "dynamic" variant, in place of the static variant. The variant's "name" enumerates the tags allowed at that node.
5369
+ 1. Read the [${DYNAMIC_TAGS_URI}] resource for each allowed tag's settings schema.
5370
+ 2. Provide, at that node:
5371
+ {
5372
+ "$$type": "dynamic",
5373
+ "value": {
5374
+ "name": "<allowed tag name>",
5375
+ "settings": { /* strictly per the tag's settings schema */ }
5376
+ }
5377
+ }
5378
+ Image example: { "$$type": "image", "value": { "src": { "$$type": "dynamic", "value": { "name": "<image tag>", "settings": { ... } } } } }
5379
+ Do NOT send "group" (it is resolved automatically). Use { "settings": {} } only when the tag has no settings.
5243
5380
  `);
5244
5381
  configureElementToolPrompt.parameter("elementId", "The ID of the element to configure. MANDATORY.");
5245
5382
  configureElementToolPrompt.parameter(
@@ -5347,7 +5484,8 @@ var initConfigureElementTool = (reg) => {
5347
5484
  requiredResources: [
5348
5485
  { description: "Widgets schema", uri: WIDGET_SCHEMA_URI },
5349
5486
  { description: "Styles schema", uri: STYLE_SCHEMA_URI },
5350
- { description: "Configure element guide", uri: CONFIGURE_ELEMENT_GUIDE_URI }
5487
+ { description: "Configure element guide", uri: CONFIGURE_ELEMENT_GUIDE_URI },
5488
+ { description: "Dynamic tags catalog", uri: DYNAMIC_TAGS_URI }
5351
5489
  ],
5352
5490
  handler: ({ elementId, propertiesToChange, elementType, stylePropertiesToChange }) => {
5353
5491
  const widgetData = getWidgetsCache10()?.[elementType];
@@ -5443,7 +5581,7 @@ Check the styles schema at the resource [${STYLE_SCHEMA_URI.replace(
5443
5581
 
5444
5582
  // src/mcp/tools/get-element-config/tool.ts
5445
5583
  import { getContainer as getContainer6, getElementStyles as getElementStyles2, getWidgetsCache as getWidgetsCache11 } from "@elementor/editor-elements";
5446
- import { Schema as Schema4 } from "@elementor/editor-props";
5584
+ import { Schema as Schema5 } from "@elementor/editor-props";
5447
5585
  import { z as z3 } from "@elementor/schema";
5448
5586
  var schema = {
5449
5587
  elementId: z3.string()
@@ -5500,7 +5638,7 @@ var initGetElementConfigTool = (reg) => {
5500
5638
  }
5501
5639
  const propValues = {};
5502
5640
  const stylePropValues = {};
5503
- Schema4.configurableKeys(propSchema).forEach((key) => {
5641
+ Schema5.configurableKeys(propSchema).forEach((key) => {
5504
5642
  propValues[key] = structuredClone(elementRawSettings.get(key));
5505
5643
  });
5506
5644
  const elementStyles = getElementStyles2(elementId) || {};
@@ -5536,9 +5674,11 @@ var initGetElementConfigTool = (reg) => {
5536
5674
 
5537
5675
  // src/mcp/canvas-mcp.ts
5538
5676
  var initCanvasMcp = (reg) => {
5677
+ Schema6.setDynamicTagNamesResolver(getDynamicTagNamesByCategories);
5539
5678
  initWidgetsSchemaResource(reg);
5540
5679
  initAvailableWidgetsResource(reg);
5541
5680
  initDocumentStructureResource(reg);
5681
+ initDynamicTagsResource(reg);
5542
5682
  initSelectedElementResource(reg);
5543
5683
  initEditorStateResource(reg);
5544
5684
  initGeneralContextResource(reg);
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.2.0-923",
4
+ "version": "4.2.0-925",
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.2.0-923",
40
+ "@elementor/editor": "4.2.0-925",
41
41
  "dompurify": "^3.2.6",
42
- "@elementor/editor-controls": "4.2.0-923",
43
- "@elementor/editor-documents": "4.2.0-923",
44
- "@elementor/editor-elements": "4.2.0-923",
45
- "@elementor/editor-interactions": "4.2.0-923",
46
- "@elementor/editor-mcp": "4.2.0-923",
47
- "@elementor/editor-notifications": "4.2.0-923",
48
- "@elementor/editor-props": "4.2.0-923",
49
- "@elementor/editor-responsive": "4.2.0-923",
50
- "@elementor/editor-styles": "4.2.0-923",
51
- "@elementor/editor-styles-repository": "4.2.0-923",
52
- "@elementor/editor-ui": "4.2.0-923",
53
- "@elementor/editor-v1-adapters": "4.2.0-923",
54
- "@elementor/schema": "4.2.0-923",
55
- "@elementor/twing": "4.2.0-923",
42
+ "@elementor/editor-controls": "4.2.0-925",
43
+ "@elementor/editor-documents": "4.2.0-925",
44
+ "@elementor/editor-elements": "4.2.0-925",
45
+ "@elementor/editor-interactions": "4.2.0-925",
46
+ "@elementor/editor-mcp": "4.2.0-925",
47
+ "@elementor/editor-notifications": "4.2.0-925",
48
+ "@elementor/editor-props": "4.2.0-925",
49
+ "@elementor/editor-responsive": "4.2.0-925",
50
+ "@elementor/editor-styles": "4.2.0-925",
51
+ "@elementor/editor-styles-repository": "4.2.0-925",
52
+ "@elementor/editor-ui": "4.2.0-925",
53
+ "@elementor/editor-v1-adapters": "4.2.0-925",
54
+ "@elementor/schema": "4.2.0-925",
55
+ "@elementor/twing": "4.2.0-925",
56
56
  "@elementor/ui": "1.37.5",
57
- "@elementor/utils": "4.2.0-923",
58
- "@elementor/wp-media": "4.2.0-923",
57
+ "@elementor/utils": "4.2.0-925",
58
+ "@elementor/wp-media": "4.2.0-925",
59
59
  "@floating-ui/react": "^0.27.5",
60
60
  "@wordpress/i18n": "^5.13.0"
61
61
  },
@@ -0,0 +1,66 @@
1
+ import { mockLegacyElementor } from 'test-utils';
2
+ import { getWidgetsCache } from '@elementor/editor-elements';
3
+
4
+ import { createTemplatedElementType } from '../create-templated-element-type';
5
+ import { initLegacyViews, registerElementType } from '../init-legacy-views';
6
+ import type { LegacyWindow } from '../types';
7
+
8
+ jest.mock( '@elementor/editor-elements', () => ( {
9
+ getWidgetsCache: jest.fn(),
10
+ } ) );
11
+
12
+ jest.mock( '../../renderers/create-dom-renderer', () => ( {
13
+ createDomRenderer: jest.fn( () => ( {
14
+ register: jest.fn(),
15
+ render: jest.fn(),
16
+ } ) ),
17
+ } ) );
18
+
19
+ const MOCK_TYPE = 'test-loop-type';
20
+
21
+ const createMockAtomicWidget = () => ( {
22
+ atomic: true,
23
+ controls: {},
24
+ title: MOCK_TYPE,
25
+ twig_templates: {},
26
+ twig_main_template: 'main',
27
+ atomic_props_schema: {},
28
+ base_styles_dictionary: {},
29
+ } );
30
+
31
+ const createMockElementsManager = () => ( {
32
+ getElementTypeClass: jest.fn().mockReturnValue( null ),
33
+ registerElementType: jest.fn(),
34
+ elementTypes: {} as Record< string, unknown >,
35
+ } );
36
+
37
+ const attachMockElementsManager = ( elementsManager: ReturnType< typeof createMockElementsManager > ) => {
38
+ const legacyWindow = window as unknown as LegacyWindow;
39
+ legacyWindow.elementor.elementsManager =
40
+ elementsManager as unknown as LegacyWindow[ 'elementor' ][ 'elementsManager' ];
41
+ };
42
+
43
+ describe( 'initLegacyViews', () => {
44
+ beforeEach( () => {
45
+ mockLegacyElementor();
46
+ } );
47
+
48
+ it( 'should register a late element type via the legacy manager without leaking state', () => {
49
+ // Arrange
50
+ const elementsManager = createMockElementsManager();
51
+ attachMockElementsManager( elementsManager );
52
+
53
+ jest.mocked( getWidgetsCache ).mockReturnValue( { [ MOCK_TYPE ]: createMockAtomicWidget() } );
54
+
55
+ initLegacyViews();
56
+ elementsManager.registerElementType.mockClear();
57
+
58
+ // Act
59
+ registerElementType( MOCK_TYPE, ( options ) => createTemplatedElementType( options ) );
60
+
61
+ // Assert
62
+ expect( elementsManager.registerElementType ).toHaveBeenCalledTimes( 1 );
63
+ const [ registeredInstance ] = elementsManager.registerElementType.mock.calls[ 0 ];
64
+ expect( registeredInstance.getType() ).toBe( MOCK_TYPE );
65
+ } );
66
+ } );
@@ -1,5 +1,5 @@
1
1
  import { getWidgetsCache, type V1ElementConfig } from '@elementor/editor-elements';
2
- import { __privateListenTo, v1ReadyEvent } from '@elementor/editor-v1-adapters';
2
+ import { __privateIsReady as isV1Ready, __privateListenTo, v1ReadyEvent } from '@elementor/editor-v1-adapters';
3
3
 
4
4
  import { createDomRenderer, type DomRenderer } from '../renderers/create-dom-renderer';
5
5
  import { createElementType } from './create-element-type';
@@ -30,28 +30,38 @@ export function registerElementType(
30
30
  elementTypeGenerator: ElementLegacyType[ keyof ElementLegacyType ]
31
31
  ) {
32
32
  elementsLegacyTypes[ type ] = elementTypeGenerator;
33
+
34
+ if ( isV1Ready() ) {
35
+ registerElementInLegacyManager( type, createDomRenderer() );
36
+ }
33
37
  }
34
38
 
35
39
  export function initLegacyViews() {
36
40
  __privateListenTo( v1ReadyEvent(), () => {
37
41
  const widgetsCache = getWidgetsCache() ?? {};
38
- const legacyWindow = window as unknown as LegacyWindow;
39
42
  const renderer = createDomRenderer();
40
43
 
41
44
  registerProPromotionTypes( widgetsCache );
42
45
 
43
- Object.entries( widgetsCache ).forEach( ( [ type, element ] ) => {
44
- if ( ! element.atomic ) {
45
- return;
46
- }
47
-
48
- const ResolvedElementType = resolveElementType( type, renderer, element );
49
-
50
- tryRegisterElement( legacyWindow, type, element, ResolvedElementType );
46
+ Object.keys( widgetsCache ).forEach( ( type ) => {
47
+ registerElementInLegacyManager( type, renderer );
51
48
  } );
52
49
  } );
53
50
  }
54
51
 
52
+ function registerElementInLegacyManager( type: string, renderer: DomRenderer ) {
53
+ const element = ( getWidgetsCache() ?? {} )[ type ];
54
+
55
+ if ( ! element?.atomic ) {
56
+ return;
57
+ }
58
+
59
+ const legacyWindow = window as unknown as LegacyWindow;
60
+ const ResolvedElementType = resolveElementType( type, renderer, element );
61
+
62
+ tryRegisterElement( legacyWindow, type, element, ResolvedElementType );
63
+ }
64
+
55
65
  function registerProPromotionTypes( widgetsCache: Record< string, V1ElementConfig > ) {
56
66
  Object.entries( widgetsCache ).forEach( ( [ type, element ] ) => {
57
67
  if ( element.meta?.is_pro_promotion ) {
@@ -1,8 +1,10 @@
1
1
  import { type MCPRegistryEntry } from '@elementor/editor-mcp';
2
+ import { Schema } from '@elementor/editor-props';
2
3
 
3
4
  import { initAvailableWidgetsResource } from './resources/available-widgets-resource';
4
5
  import { initBreakpointsResource } from './resources/breakpoints-resource';
5
6
  import { initDocumentStructureResource } from './resources/document-structure-resource';
7
+ import { initDynamicTagsResource } from './resources/dynamic-tags-resource';
6
8
  import { initEditorStateResource } from './resources/editor-state-resource';
7
9
  import { initGeneralContextResource } from './resources/general-context-resource';
8
10
  import { initSelectedElementResource } from './resources/selected-element-resource';
@@ -10,11 +12,14 @@ import { initWidgetsSchemaResource } from './resources/widgets-schema-resource';
10
12
  import { initBuildCompositionsTool } from './tools/build-composition/tool';
11
13
  import { initConfigureElementTool } from './tools/configure-element/tool';
12
14
  import { initGetElementConfigTool } from './tools/get-element-config/tool';
15
+ import { getDynamicTagNamesByCategories } from './utils/resolve-dynamic-tag';
13
16
 
14
17
  export const initCanvasMcp = ( reg: MCPRegistryEntry ) => {
18
+ Schema.setDynamicTagNamesResolver( getDynamicTagNamesByCategories );
15
19
  initWidgetsSchemaResource( reg );
16
20
  initAvailableWidgetsResource( reg );
17
21
  initDocumentStructureResource( reg );
22
+ initDynamicTagsResource( reg );
18
23
  initSelectedElementResource( reg );
19
24
  initEditorStateResource( reg );
20
25
  initGeneralContextResource( reg );
@@ -0,0 +1,85 @@
1
+ import { Schema } from '@elementor/editor-props';
2
+ import { getElementorConfig } from '@elementor/editor-v1-adapters';
3
+
4
+ import { DYNAMIC_TAGS_URI, initDynamicTagsResource } from '../dynamic-tags-resource';
5
+
6
+ jest.mock( '@elementor/editor-props', () => ( {
7
+ Schema: {
8
+ propTypeToJsonSchema: jest.fn( () => ( { mocked: true } ) ),
9
+ },
10
+ } ) );
11
+
12
+ jest.mock( '@elementor/editor-v1-adapters', () => ( {
13
+ getElementorConfig: jest.fn(),
14
+ } ) );
15
+
16
+ const mockedGetElementorConfig = getElementorConfig as jest.MockedFunction< typeof getElementorConfig >;
17
+
18
+ type ResourceHandler = ( uri: URL ) => Promise< { contents: { text: string }[] } >;
19
+
20
+ const captureHandler = (): ResourceHandler => {
21
+ const resource = jest.fn();
22
+ initDynamicTagsResource( { resource } as never );
23
+ return resource.mock.calls[ 0 ][ 3 ] as ResourceHandler;
24
+ };
25
+
26
+ const readCatalog = async () => {
27
+ const handler = captureHandler();
28
+ const result = await handler( new URL( DYNAMIC_TAGS_URI ) );
29
+ return JSON.parse( result.contents[ 0 ].text );
30
+ };
31
+
32
+ describe( 'dynamic-tags-resource', () => {
33
+ beforeEach( () => {
34
+ jest.clearAllMocks();
35
+ } );
36
+
37
+ it( 'returns a flat tag list with categories and omits the fallback setting', async () => {
38
+ // Arrange
39
+ mockedGetElementorConfig.mockReturnValue( {
40
+ atomicDynamicTags: {
41
+ tags: {
42
+ 'post-custom-field': {
43
+ name: 'post-custom-field',
44
+ label: 'Post Custom Field',
45
+ group: 'post',
46
+ categories: [ 'text', 'url' ],
47
+ props_schema: {
48
+ key: { kind: 'string', key: 'string' },
49
+ before: { kind: 'string', key: 'string' },
50
+ fallback: { kind: 'string', key: 'string' },
51
+ },
52
+ },
53
+ },
54
+ groups: { post: { title: 'Post' } },
55
+ },
56
+ } as never );
57
+
58
+ // Act
59
+ const catalog = await readCatalog();
60
+
61
+ // Assert
62
+ expect( Array.isArray( catalog ) ).toBe( true );
63
+ expect( catalog ).toHaveLength( 1 );
64
+ expect( catalog[ 0 ] ).toMatchObject( {
65
+ name: 'post-custom-field',
66
+ label: 'Post Custom Field',
67
+ categories: [ 'text', 'url' ],
68
+ } );
69
+ expect( catalog[ 0 ] ).not.toHaveProperty( 'group' );
70
+ expect( Object.keys( catalog[ 0 ].settings ) ).toEqual( [ 'key', 'before' ] );
71
+ expect( catalog[ 0 ].settings ).not.toHaveProperty( 'fallback' );
72
+ } );
73
+
74
+ it( 'returns an empty list when dynamic tags are unavailable', async () => {
75
+ // Arrange
76
+ mockedGetElementorConfig.mockReturnValue( {} as never );
77
+
78
+ // Act
79
+ const catalog = await readCatalog();
80
+
81
+ // Assert
82
+ expect( catalog ).toEqual( [] );
83
+ expect( Schema.propTypeToJsonSchema ).not.toHaveBeenCalled();
84
+ } );
85
+ } );
@@ -0,0 +1,52 @@
1
+ import { type MCPRegistryEntry } from '@elementor/editor-mcp';
2
+ import { type PropType, Schema } from '@elementor/editor-props';
3
+
4
+ import {
5
+ type AtomicDynamicTag,
6
+ getAtomicDynamicTags,
7
+ OMITTED_DYNAMIC_SETTING_KEYS,
8
+ } from '../utils/resolve-dynamic-tag';
9
+
10
+ export const DYNAMIC_TAGS_URI = 'elementor://dynamic-tags';
11
+
12
+ const settingsSchema = ( propsSchema: AtomicDynamicTag[ 'props_schema' ] ): Record< string, unknown > => {
13
+ return Object.fromEntries(
14
+ Object.entries( propsSchema ?? {} )
15
+ .filter( ( [ key ] ) => ! ( OMITTED_DYNAMIC_SETTING_KEYS as readonly string[] ).includes( key ) )
16
+ .map( ( [ key, propType ] ) => [ key, Schema.propTypeToJsonSchema( propType as PropType ) ] )
17
+ );
18
+ };
19
+
20
+ const buildDynamicTagsList = () => {
21
+ return Object.values( getAtomicDynamicTags() ).map( ( tag ) => ( {
22
+ name: tag.name,
23
+ label: tag.label,
24
+ categories: tag.categories,
25
+ settings: settingsSchema( tag.props_schema ),
26
+ } ) );
27
+ };
28
+
29
+ export const initDynamicTagsResource = ( reg: MCPRegistryEntry ) => {
30
+ const { resource } = reg;
31
+
32
+ resource(
33
+ 'dynamic-tags',
34
+ DYNAMIC_TAGS_URI,
35
+ {
36
+ description:
37
+ 'List of available dynamic tags. To bind a property to a dynamic source, set its value to ' +
38
+ '{ "$$type": "dynamic", "value": { "name": <tag name>, "settings": { ... } } } using a tag whose ' +
39
+ 'name appears in that property\'s allowed list, and populate "settings" per the tag entry here.',
40
+ mimeType: 'application/json',
41
+ },
42
+ async ( uri: URL ) => ( {
43
+ contents: [
44
+ {
45
+ uri: uri.href,
46
+ mimeType: 'application/json',
47
+ text: JSON.stringify( buildDynamicTagsList() ),
48
+ },
49
+ ],
50
+ } )
51
+ );
52
+ };
@@ -1,6 +1,7 @@
1
1
  import { toolPrompts } from '@elementor/editor-mcp';
2
2
 
3
3
  import { AVAILABLE_WIDGETS_URI } from '../../resources/available-widgets-resource';
4
+ import { DYNAMIC_TAGS_URI } from '../../resources/dynamic-tags-resource';
4
5
 
5
6
  export const BUILD_COMPOSITIONS_GUIDE_URI = 'elementor://canvas/tools/build-compositions-guide';
6
7
 
@@ -40,6 +41,13 @@ Some elements have internal tree structures (nesting). When using these elements
40
41
  - NO LINKS in configuration
41
42
  - Retry on errors up to 10x
42
43
 
44
+ # DYNAMIC TAGS
45
+ - A value can be made dynamic wherever its schema exposes a \`"$$type": "dynamic"\` variant. This may be the property root OR a NESTED field (e.g. an image's \`src\`, not the whole \`image\`).
46
+ - Put the dynamic object EXACTLY at that node, in place of the static variant. The variant's \`name\` lists the allowed tags; read [${ DYNAMIC_TAGS_URI }] for each tag's settings schema.
47
+ - Provide at that node: \`{ "$$type": "dynamic", "value": { "name": "<allowed tag>", "settings": { ... } } }\`
48
+ - Example (image): \`{ "$$type": "image", "value": { "src": { "$$type": "dynamic", "value": { "name": "<image tag>", "settings": { ... } } } } }\`
49
+ - Do NOT send \`group\` (it is resolved automatically). Populate \`settings\` strictly per the tag's schema; use \`{}\` only when it has none.
50
+
43
51
  Note about configuration ids: These names are visible to the end-user, make sure they make sense, related and relevant.
44
52
 
45
53
  # DESIGN PHILOSOPHY: CONTEXT-DRIVEN CREATIVITY