@elementor/editor-canvas 4.2.0-924 → 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.js CHANGED
@@ -3830,6 +3830,9 @@ function initTabsModelExtensions() {
3830
3830
  registerModelExtensions("e-tab", tabModelExtensions);
3831
3831
  }
3832
3832
 
3833
+ // src/mcp/canvas-mcp.ts
3834
+ var import_editor_props11 = require("@elementor/editor-props");
3835
+
3833
3836
  // src/mcp/resources/available-widgets-resource.ts
3834
3837
  var import_editor_v1_adapters15 = require("@elementor/editor-v1-adapters");
3835
3838
  var AVAILABLE_WIDGETS_URI = "elementor://context/available-widgets";
@@ -3979,8 +3982,109 @@ function extractElementData(element) {
3979
3982
  return result;
3980
3983
  }
3981
3984
 
3982
- // src/mcp/resources/editor-state-resource.ts
3985
+ // src/mcp/resources/dynamic-tags-resource.ts
3986
+ var import_editor_props7 = require("@elementor/editor-props");
3987
+
3988
+ // src/mcp/utils/resolve-dynamic-tag.ts
3983
3989
  var import_editor_v1_adapters17 = require("@elementor/editor-v1-adapters");
3990
+ var DYNAMIC_PROP_TYPE_KEY = "dynamic";
3991
+ var OMITTED_DYNAMIC_SETTING_KEYS = ["fallback"];
3992
+ var getAtomicDynamicTags = () => {
3993
+ const config = (0, import_editor_v1_adapters17.getElementorConfig)();
3994
+ return config.atomicDynamicTags?.tags ?? {};
3995
+ };
3996
+ var getDynamicTagNamesByCategories = (categories) => {
3997
+ if (!categories.length) {
3998
+ return [];
3999
+ }
4000
+ const wanted = new Set(categories);
4001
+ return Object.values(getAtomicDynamicTags()).filter((tag) => tag.categories?.some((category) => wanted.has(category))).map((tag) => tag.name);
4002
+ };
4003
+ var dynamicTagLLMResolver = (value) => {
4004
+ const input = value ?? {};
4005
+ const tag = input.name ? getAtomicDynamicTags()[input.name] : void 0;
4006
+ if (!tag) {
4007
+ return {
4008
+ $$type: DYNAMIC_PROP_TYPE_KEY,
4009
+ value: { name: input.name ?? "", group: "", settings: {} }
4010
+ };
4011
+ }
4012
+ return {
4013
+ $$type: DYNAMIC_PROP_TYPE_KEY,
4014
+ value: {
4015
+ name: tag.name,
4016
+ group: tag.group,
4017
+ settings: buildStrictSettings(tag.props_schema ?? {}, input.settings ?? {})
4018
+ }
4019
+ };
4020
+ };
4021
+ var buildStrictSettings = (schema2, provided) => {
4022
+ const settings = {};
4023
+ for (const [key, propType] of Object.entries(schema2)) {
4024
+ if (OMITTED_DYNAMIC_SETTING_KEYS.includes(key)) {
4025
+ continue;
4026
+ }
4027
+ const resolved = provided[key] !== void 0 ? wrapSettingValue(provided[key], propType) : defaultSettingValue(propType);
4028
+ if (resolved !== void 0 && resolved !== null) {
4029
+ settings[key] = resolved;
4030
+ }
4031
+ }
4032
+ return settings;
4033
+ };
4034
+ var wrapSettingValue = (raw, propType) => {
4035
+ if (raw !== null && typeof raw === "object") {
4036
+ return raw;
4037
+ }
4038
+ return propType.key ? { $$type: propType.key, value: raw } : raw;
4039
+ };
4040
+ var defaultSettingValue = (propType) => {
4041
+ if (propType.initial_value !== null && propType.initial_value !== void 0) {
4042
+ return propType.initial_value;
4043
+ }
4044
+ if (propType.default !== null && propType.default !== void 0) {
4045
+ return wrapSettingValue(propType.default, propType);
4046
+ }
4047
+ return void 0;
4048
+ };
4049
+
4050
+ // src/mcp/resources/dynamic-tags-resource.ts
4051
+ var DYNAMIC_TAGS_URI = "elementor://dynamic-tags";
4052
+ var settingsSchema = (propsSchema) => {
4053
+ return Object.fromEntries(
4054
+ Object.entries(propsSchema ?? {}).filter(([key]) => !OMITTED_DYNAMIC_SETTING_KEYS.includes(key)).map(([key, propType]) => [key, import_editor_props7.Schema.propTypeToJsonSchema(propType)])
4055
+ );
4056
+ };
4057
+ var buildDynamicTagsList = () => {
4058
+ return Object.values(getAtomicDynamicTags()).map((tag) => ({
4059
+ name: tag.name,
4060
+ label: tag.label,
4061
+ categories: tag.categories,
4062
+ settings: settingsSchema(tag.props_schema)
4063
+ }));
4064
+ };
4065
+ var initDynamicTagsResource = (reg) => {
4066
+ const { resource } = reg;
4067
+ resource(
4068
+ "dynamic-tags",
4069
+ DYNAMIC_TAGS_URI,
4070
+ {
4071
+ 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.`,
4072
+ mimeType: "application/json"
4073
+ },
4074
+ async (uri) => ({
4075
+ contents: [
4076
+ {
4077
+ uri: uri.href,
4078
+ mimeType: "application/json",
4079
+ text: JSON.stringify(buildDynamicTagsList())
4080
+ }
4081
+ ]
4082
+ })
4083
+ );
4084
+ };
4085
+
4086
+ // src/mcp/resources/editor-state-resource.ts
4087
+ var import_editor_v1_adapters18 = require("@elementor/editor-v1-adapters");
3984
4088
  var CURRENTLY_VIEWED_SCREEN = "The user is currently viewing the Elementor editor";
3985
4089
  var PAGE_CONTENT_CHARACTER_LIMIT = 500;
3986
4090
  var PREVIEW_TEXT_NODE_MIN_LENGTH = 2;
@@ -4001,8 +4105,8 @@ var initEditorStateResource = (reg) => {
4001
4105
  lastSerializedState = serialized;
4002
4106
  sendResourceUpdated({ uri: EDITOR_STATE_URI });
4003
4107
  };
4004
- (0, import_editor_v1_adapters17.__privateListenTo)(
4005
- [(0, import_editor_v1_adapters17.commandEndEvent)("editor/documents/switch"), (0, import_editor_v1_adapters17.commandEndEvent)("editor/documents/attach-preview")],
4108
+ (0, import_editor_v1_adapters18.__privateListenTo)(
4109
+ [(0, import_editor_v1_adapters18.commandEndEvent)("editor/documents/switch"), (0, import_editor_v1_adapters18.commandEndEvent)("editor/documents/attach-preview")],
4006
4110
  notifyIfChanged
4007
4111
  );
4008
4112
  lastSerializedState = JSON.stringify(buildState());
@@ -4076,7 +4180,7 @@ function getPageTitle() {
4076
4180
  }
4077
4181
 
4078
4182
  // src/mcp/resources/general-context-resource.ts
4079
- var import_editor_v1_adapters18 = require("@elementor/editor-v1-adapters");
4183
+ var import_editor_v1_adapters19 = require("@elementor/editor-v1-adapters");
4080
4184
  var GENERAL_CONTEXT_URI = "elementor://context/general";
4081
4185
  var initGeneralContextResource = (reg) => {
4082
4186
  const { resource, sendResourceUpdated } = reg;
@@ -4137,11 +4241,11 @@ var initGeneralContextResource = (reg) => {
4137
4241
  };
4138
4242
  }
4139
4243
  );
4140
- (0, import_editor_v1_adapters18.__privateListenTo)(
4244
+ (0, import_editor_v1_adapters19.__privateListenTo)(
4141
4245
  [
4142
- (0, import_editor_v1_adapters18.commandEndEvent)("editor/documents/switch"),
4143
- (0, import_editor_v1_adapters18.commandEndEvent)("editor/documents/attach-preview"),
4144
- (0, import_editor_v1_adapters18.commandEndEvent)("document/elements/settings")
4246
+ (0, import_editor_v1_adapters19.commandEndEvent)("editor/documents/switch"),
4247
+ (0, import_editor_v1_adapters19.commandEndEvent)("editor/documents/attach-preview"),
4248
+ (0, import_editor_v1_adapters19.commandEndEvent)("document/elements/settings")
4145
4249
  ],
4146
4250
  pushUpdateIfChanged
4147
4251
  );
@@ -4150,7 +4254,7 @@ var initGeneralContextResource = (reg) => {
4150
4254
 
4151
4255
  // src/mcp/resources/selected-element-resource.ts
4152
4256
  var import_editor_elements12 = require("@elementor/editor-elements");
4153
- var import_editor_v1_adapters19 = require("@elementor/editor-v1-adapters");
4257
+ var import_editor_v1_adapters20 = require("@elementor/editor-v1-adapters");
4154
4258
  var SELECTED_ELEMENT_URI = "elementor://context/selected-element";
4155
4259
  var initSelectedElementResource = (reg) => {
4156
4260
  const { resource, sendResourceUpdated } = reg;
@@ -4181,11 +4285,11 @@ var initSelectedElementResource = (reg) => {
4181
4285
  }
4182
4286
  publishIfChanged(readSelectionFromEditor());
4183
4287
  };
4184
- (0, import_editor_v1_adapters19.__privateListenTo)(
4288
+ (0, import_editor_v1_adapters20.__privateListenTo)(
4185
4289
  [
4186
- (0, import_editor_v1_adapters19.commandEndEvent)("document/elements/select"),
4187
- (0, import_editor_v1_adapters19.commandEndEvent)("document/elements/deselect-all"),
4188
- (0, import_editor_v1_adapters19.commandEndEvent)("document/elements/settings")
4290
+ (0, import_editor_v1_adapters20.commandEndEvent)("document/elements/select"),
4291
+ (0, import_editor_v1_adapters20.commandEndEvent)("document/elements/deselect-all"),
4292
+ (0, import_editor_v1_adapters20.commandEndEvent)("document/elements/settings")
4189
4293
  ],
4190
4294
  onCommand
4191
4295
  );
@@ -4303,9 +4407,9 @@ var import_editor_elements15 = require("@elementor/editor-elements");
4303
4407
 
4304
4408
  // src/mcp/utils/do-update-element-property.ts
4305
4409
  var import_editor_elements13 = require("@elementor/editor-elements");
4306
- var import_editor_props7 = require("@elementor/editor-props");
4410
+ var import_editor_props8 = require("@elementor/editor-props");
4307
4411
  var import_editor_styles5 = require("@elementor/editor-styles");
4308
- var import_editor_v1_adapters20 = require("@elementor/editor-v1-adapters");
4412
+ var import_editor_v1_adapters21 = require("@elementor/editor-v1-adapters");
4309
4413
 
4310
4414
  // src/mcp/utils/merge-custom-css.ts
4311
4415
  var CUSTOM_CSS_SEPARATOR = "\n";
@@ -4328,9 +4432,12 @@ var LOCAL_STYLE_META = {
4328
4432
  };
4329
4433
  function resolvePropValue(value, forceKey) {
4330
4434
  const Utils = window.elementorV2.editorVariables.Utils;
4331
- return import_editor_props7.Schema.adjustLlmPropValueSchema(value, {
4435
+ return import_editor_props8.Schema.adjustLlmPropValueSchema(value, {
4332
4436
  forceKey,
4333
- transformers: Utils.globalVariablesLLMResolvers
4437
+ transformers: {
4438
+ ...Utils.globalVariablesLLMResolvers,
4439
+ [DYNAMIC_PROP_TYPE_KEY]: dynamicTagLLMResolver
4440
+ }
4334
4441
  });
4335
4442
  }
4336
4443
  var doUpdateElementProperty = (params) => {
@@ -4380,7 +4487,7 @@ var doUpdateElementProperty = (params) => {
4380
4487
  }
4381
4488
  if (propertyRawSchema.kind === "plain") {
4382
4489
  if (typeof propertyMapValue[stylePropName] !== "object") {
4383
- const propUtil = (0, import_editor_props7.getPropSchemaFromCache)(propertyRawSchema.key);
4490
+ const propUtil = (0, import_editor_props8.getPropSchemaFromCache)(propertyRawSchema.key);
4384
4491
  if (propUtil) {
4385
4492
  const plainValue = propUtil.create(propertyMapValue[stylePropName]);
4386
4493
  propertyMapValue[stylePropName] = plainValue;
@@ -4433,7 +4540,7 @@ var doUpdateElementProperty = (params) => {
4433
4540
  }
4434
4541
  const propKey = elementPropSchema[propertyName].key;
4435
4542
  const value = resolvePropValue(propertyValue, propKey);
4436
- const { valid, jsonSchema } = import_editor_props7.Schema.validatePropValue(elementPropSchema[propertyName], propertyValue);
4543
+ const { valid, jsonSchema } = import_editor_props8.Schema.validatePropValue(elementPropSchema[propertyName], propertyValue);
4437
4544
  if (!valid) {
4438
4545
  throw new Error(
4439
4546
  `Invalid PropValue for elementId: ${elementId}. PropKey: ${propKey}, PropValue: ${JSON.stringify(
@@ -4449,12 +4556,12 @@ Expected Schema: ${jsonSchema}`
4449
4556
  },
4450
4557
  withHistory: false
4451
4558
  });
4452
- (0, import_editor_v1_adapters20.__privateRunCommandSync)("document/save/set-is-modified", { status: true }, { internal: true });
4559
+ (0, import_editor_v1_adapters21.__privateRunCommandSync)("document/save/set-is-modified", { status: true }, { internal: true });
4453
4560
  };
4454
4561
 
4455
4562
  // src/mcp/utils/validate-input.ts
4456
4563
  var import_editor_elements14 = require("@elementor/editor-elements");
4457
- var import_editor_props8 = require("@elementor/editor-props");
4564
+ var import_editor_props9 = require("@elementor/editor-props");
4458
4565
  var import_editor_styles6 = require("@elementor/editor-styles");
4459
4566
  var _widgetsSchema = null;
4460
4567
  var validateInput = {
@@ -4488,10 +4595,10 @@ var validateInput = {
4488
4595
  if (!propSchema) {
4489
4596
  errors.push(`Property "${propName}" is not defined in the schema.`);
4490
4597
  hasInvalidKey = true;
4491
- } else if (!import_editor_props8.Schema.isPropKeyConfigurable(propName, propSchema)) {
4598
+ } else if (!import_editor_props9.Schema.isPropKeyConfigurable(propName, propSchema)) {
4492
4599
  errors.push(`Property "${propName}" is not configurable.`);
4493
4600
  } else {
4494
- const { valid } = import_editor_props8.Schema.validatePropValue(propSchema, propValue);
4601
+ const { valid } = import_editor_props9.Schema.validatePropValue(propSchema, propValue);
4495
4602
  if (!valid) {
4496
4603
  errors.push(
4497
4604
  `Invalid property "${propName}". Validate input with resource [${STYLE_SCHEMA_URI.replace(
@@ -4875,6 +4982,13 @@ Some elements have internal tree structures (nesting). When using these elements
4875
4982
  - NO LINKS in configuration
4876
4983
  - Retry on errors up to 10x
4877
4984
 
4985
+ # DYNAMIC TAGS
4986
+ - 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\`).
4987
+ - 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.
4988
+ - Provide at that node: \`{ "$$type": "dynamic", "value": { "name": "<allowed tag>", "settings": { ... } } }\`
4989
+ - Example (image): \`{ "$$type": "image", "value": { "src": { "$$type": "dynamic", "value": { "name": "<image tag>", "settings": { ... } } } } }\`
4990
+ - Do NOT send \`group\` (it is resolved automatically). Populate \`settings\` strictly per the tag's schema; use \`{}\` only when it has none.
4991
+
4878
4992
  Note about configuration ids: These names are visible to the end-user, make sure they make sense, related and relevant.
4879
4993
 
4880
4994
  # DESIGN PHILOSOPHY: CONTEXT-DRIVEN CREATIVITY
@@ -5096,7 +5210,8 @@ var initBuildCompositionsTool = (reg) => {
5096
5210
  { description: "Global Classes", uri: "elementor://global-classes" },
5097
5211
  { description: "Global Variables", uri: "elementor://global-variables" },
5098
5212
  { description: "Styles best practices", uri: BEST_PRACTICES_URI },
5099
- { description: "Available widgets for this tool", uri: AVAILABLE_WIDGETS_URI_V4 }
5213
+ { description: "Available widgets for this tool", uri: AVAILABLE_WIDGETS_URI_V4 },
5214
+ { description: "Dynamic tags catalog", uri: DYNAMIC_TAGS_URI }
5100
5215
  ],
5101
5216
  outputSchema,
5102
5217
  handler: async (rawParams) => {
@@ -5261,6 +5376,21 @@ For styleProperties, use the style schema provided, as it also uses the PropType
5261
5376
  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.
5262
5377
 
5263
5378
  Use the EXACT "PROP-TYPE" Schema given, and ALWAYS include the "key" property from the original configuration for every property you are changing.
5379
+
5380
+ # Dynamic tags
5381
+ 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.
5382
+ 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.
5383
+ 1. Read the [${DYNAMIC_TAGS_URI}] resource for each allowed tag's settings schema.
5384
+ 2. Provide, at that node:
5385
+ {
5386
+ "$$type": "dynamic",
5387
+ "value": {
5388
+ "name": "<allowed tag name>",
5389
+ "settings": { /* strictly per the tag's settings schema */ }
5390
+ }
5391
+ }
5392
+ Image example: { "$$type": "image", "value": { "src": { "$$type": "dynamic", "value": { "name": "<image tag>", "settings": { ... } } } } }
5393
+ Do NOT send "group" (it is resolved automatically). Use { "settings": {} } only when the tag has no settings.
5264
5394
  `);
5265
5395
  configureElementToolPrompt.parameter("elementId", "The ID of the element to configure. MANDATORY.");
5266
5396
  configureElementToolPrompt.parameter(
@@ -5368,7 +5498,8 @@ var initConfigureElementTool = (reg) => {
5368
5498
  requiredResources: [
5369
5499
  { description: "Widgets schema", uri: WIDGET_SCHEMA_URI },
5370
5500
  { description: "Styles schema", uri: STYLE_SCHEMA_URI },
5371
- { description: "Configure element guide", uri: CONFIGURE_ELEMENT_GUIDE_URI }
5501
+ { description: "Configure element guide", uri: CONFIGURE_ELEMENT_GUIDE_URI },
5502
+ { description: "Dynamic tags catalog", uri: DYNAMIC_TAGS_URI }
5372
5503
  ],
5373
5504
  handler: ({ elementId, propertiesToChange, elementType, stylePropertiesToChange }) => {
5374
5505
  const widgetData = (0, import_editor_elements17.getWidgetsCache)()?.[elementType];
@@ -5464,7 +5595,7 @@ Check the styles schema at the resource [${STYLE_SCHEMA_URI.replace(
5464
5595
 
5465
5596
  // src/mcp/tools/get-element-config/tool.ts
5466
5597
  var import_editor_elements18 = require("@elementor/editor-elements");
5467
- var import_editor_props9 = require("@elementor/editor-props");
5598
+ var import_editor_props10 = require("@elementor/editor-props");
5468
5599
  var import_schema5 = require("@elementor/schema");
5469
5600
  var schema = {
5470
5601
  elementId: import_schema5.z.string()
@@ -5521,7 +5652,7 @@ var initGetElementConfigTool = (reg) => {
5521
5652
  }
5522
5653
  const propValues = {};
5523
5654
  const stylePropValues = {};
5524
- import_editor_props9.Schema.configurableKeys(propSchema).forEach((key) => {
5655
+ import_editor_props10.Schema.configurableKeys(propSchema).forEach((key) => {
5525
5656
  propValues[key] = structuredClone(elementRawSettings.get(key));
5526
5657
  });
5527
5658
  const elementStyles = (0, import_editor_elements18.getElementStyles)(elementId) || {};
@@ -5557,9 +5688,11 @@ var initGetElementConfigTool = (reg) => {
5557
5688
 
5558
5689
  // src/mcp/canvas-mcp.ts
5559
5690
  var initCanvasMcp = (reg) => {
5691
+ import_editor_props11.Schema.setDynamicTagNamesResolver(getDynamicTagNamesByCategories);
5560
5692
  initWidgetsSchemaResource(reg);
5561
5693
  initAvailableWidgetsResource(reg);
5562
5694
  initDocumentStructureResource(reg);
5695
+ initDynamicTagsResource(reg);
5563
5696
  initSelectedElementResource(reg);
5564
5697
  initEditorStateResource(reg);
5565
5698
  initGeneralContextResource(reg);
@@ -5684,14 +5817,14 @@ Note: The "size" property controls image resolution/loading, not visual size. Se
5684
5817
  // src/prevent-link-in-link-commands.ts
5685
5818
  var import_editor_elements19 = require("@elementor/editor-elements");
5686
5819
  var import_editor_notifications3 = require("@elementor/editor-notifications");
5687
- var import_editor_v1_adapters21 = require("@elementor/editor-v1-adapters");
5820
+ var import_editor_v1_adapters22 = require("@elementor/editor-v1-adapters");
5688
5821
  var import_i18n4 = require("@wordpress/i18n");
5689
5822
  function initLinkInLinkPrevention() {
5690
- (0, import_editor_v1_adapters21.blockCommand)({
5823
+ (0, import_editor_v1_adapters22.blockCommand)({
5691
5824
  command: "document/elements/paste",
5692
5825
  condition: blockLinkInLinkPaste
5693
5826
  });
5694
- (0, import_editor_v1_adapters21.blockCommand)({
5827
+ (0, import_editor_v1_adapters22.blockCommand)({
5695
5828
  command: "document/elements/move",
5696
5829
  condition: blockLinkInLinkMove
5697
5830
  });
@@ -5766,12 +5899,12 @@ function shouldBlock(sourceElements, targetElements) {
5766
5899
 
5767
5900
  // src/style-commands/paste-style.ts
5768
5901
  var import_editor_elements22 = require("@elementor/editor-elements");
5769
- var import_editor_props11 = require("@elementor/editor-props");
5770
- var import_editor_v1_adapters23 = require("@elementor/editor-v1-adapters");
5902
+ var import_editor_props13 = require("@elementor/editor-props");
5903
+ var import_editor_v1_adapters24 = require("@elementor/editor-v1-adapters");
5771
5904
 
5772
5905
  // src/utils/command-utils.ts
5773
5906
  var import_editor_elements20 = require("@elementor/editor-elements");
5774
- var import_editor_props10 = require("@elementor/editor-props");
5907
+ var import_editor_props12 = require("@elementor/editor-props");
5775
5908
  var import_i18n5 = require("@wordpress/i18n");
5776
5909
  function hasAtomicWidgets(args) {
5777
5910
  const { containers = [args.container] } = args;
@@ -5789,7 +5922,7 @@ function getClassesProp(container) {
5789
5922
  return null;
5790
5923
  }
5791
5924
  const [propKey] = Object.entries(propsSchema).find(
5792
- ([, propType]) => propType.kind === "plain" && propType.key === import_editor_props10.CLASSES_PROP_KEY
5925
+ ([, propType]) => propType.kind === "plain" && propType.key === import_editor_props12.CLASSES_PROP_KEY
5793
5926
  ) ?? [];
5794
5927
  return propKey ?? null;
5795
5928
  }
@@ -5814,9 +5947,9 @@ function getTitleForContainers(containers) {
5814
5947
  // src/style-commands/undoable-actions/paste-element-style.ts
5815
5948
  var import_editor_elements21 = require("@elementor/editor-elements");
5816
5949
  var import_editor_styles_repository4 = require("@elementor/editor-styles-repository");
5817
- var import_editor_v1_adapters22 = require("@elementor/editor-v1-adapters");
5950
+ var import_editor_v1_adapters23 = require("@elementor/editor-v1-adapters");
5818
5951
  var import_i18n6 = require("@wordpress/i18n");
5819
- var undoablePasteElementStyle = () => (0, import_editor_v1_adapters22.undoable)(
5952
+ var undoablePasteElementStyle = () => (0, import_editor_v1_adapters23.undoable)(
5820
5953
  {
5821
5954
  do: ({ containers, newStyle }) => {
5822
5955
  return containers.map((container) => {
@@ -5892,12 +6025,12 @@ var undoablePasteElementStyle = () => (0, import_editor_v1_adapters22.undoable)(
5892
6025
  // src/style-commands/paste-style.ts
5893
6026
  function initPasteStyleCommand() {
5894
6027
  const pasteElementStyleCommand = undoablePasteElementStyle();
5895
- (0, import_editor_v1_adapters23.blockCommand)({
6028
+ (0, import_editor_v1_adapters24.blockCommand)({
5896
6029
  command: "document/elements/paste-style",
5897
6030
  condition: hasAtomicWidgets
5898
6031
  });
5899
- (0, import_editor_v1_adapters23.__privateListenTo)(
5900
- (0, import_editor_v1_adapters23.commandStartEvent)("document/elements/paste-style"),
6032
+ (0, import_editor_v1_adapters24.__privateListenTo)(
6033
+ (0, import_editor_v1_adapters24.commandStartEvent)("document/elements/paste-style"),
5901
6034
  (e) => pasteStyles(e.args, pasteElementStyleCommand)
5902
6035
  );
5903
6036
  }
@@ -5938,8 +6071,8 @@ function pasteClasses(containers, classes) {
5938
6071
  return;
5939
6072
  }
5940
6073
  const classesSetting = (0, import_editor_elements22.getElementSetting)(container.id, classesProp);
5941
- const currentClasses = import_editor_props11.classesPropTypeUtil.extract(classesSetting) ?? [];
5942
- const newClasses = import_editor_props11.classesPropTypeUtil.create(Array.from(/* @__PURE__ */ new Set([...classes, ...currentClasses])));
6074
+ const currentClasses = import_editor_props13.classesPropTypeUtil.extract(classesSetting) ?? [];
6075
+ const newClasses = import_editor_props13.classesPropTypeUtil.create(Array.from(/* @__PURE__ */ new Set([...classes, ...currentClasses])));
5943
6076
  (0, import_editor_elements22.updateElementSettings)({
5944
6077
  id: container.id,
5945
6078
  props: { [classesProp]: newClasses }
@@ -5948,14 +6081,14 @@ function pasteClasses(containers, classes) {
5948
6081
  }
5949
6082
 
5950
6083
  // src/style-commands/reset-style.ts
5951
- var import_editor_v1_adapters25 = require("@elementor/editor-v1-adapters");
6084
+ var import_editor_v1_adapters26 = require("@elementor/editor-v1-adapters");
5952
6085
 
5953
6086
  // src/style-commands/undoable-actions/reset-element-style.ts
5954
6087
  var import_editor_elements23 = require("@elementor/editor-elements");
5955
6088
  var import_editor_styles_repository5 = require("@elementor/editor-styles-repository");
5956
- var import_editor_v1_adapters24 = require("@elementor/editor-v1-adapters");
6089
+ var import_editor_v1_adapters25 = require("@elementor/editor-v1-adapters");
5957
6090
  var import_i18n7 = require("@wordpress/i18n");
5958
- var undoableResetElementStyle = () => (0, import_editor_v1_adapters24.undoable)(
6091
+ var undoableResetElementStyle = () => (0, import_editor_v1_adapters25.undoable)(
5959
6092
  {
5960
6093
  do: ({ containers }) => {
5961
6094
  return containers.map((container) => {
@@ -5999,12 +6132,12 @@ var undoableResetElementStyle = () => (0, import_editor_v1_adapters24.undoable)(
5999
6132
  // src/style-commands/reset-style.ts
6000
6133
  function initResetStyleCommand() {
6001
6134
  const resetElementStyles = undoableResetElementStyle();
6002
- (0, import_editor_v1_adapters25.blockCommand)({
6135
+ (0, import_editor_v1_adapters26.blockCommand)({
6003
6136
  command: "document/elements/reset-style",
6004
6137
  condition: hasAtomicWidgets
6005
6138
  });
6006
- (0, import_editor_v1_adapters25.__privateListenTo)(
6007
- (0, import_editor_v1_adapters25.commandStartEvent)("document/elements/reset-style"),
6139
+ (0, import_editor_v1_adapters26.__privateListenTo)(
6140
+ (0, import_editor_v1_adapters26.commandStartEvent)("document/elements/reset-style"),
6008
6141
  (e) => resetStyles(e.args, resetElementStyles)
6009
6142
  );
6010
6143
  }
@@ -6153,9 +6286,9 @@ function getRectClipPath(rect, viewport) {
6153
6286
  }
6154
6287
 
6155
6288
  // src/hooks/use-canvas-document.ts
6156
- var import_editor_v1_adapters26 = require("@elementor/editor-v1-adapters");
6289
+ var import_editor_v1_adapters27 = require("@elementor/editor-v1-adapters");
6157
6290
  function useCanvasDocument() {
6158
- return (0, import_editor_v1_adapters26.__privateUseListenTo)((0, import_editor_v1_adapters26.commandEndEvent)("editor/documents/attach-preview"), () => (0, import_editor_v1_adapters26.getCanvasIframeDocument)());
6291
+ return (0, import_editor_v1_adapters27.__privateUseListenTo)((0, import_editor_v1_adapters27.commandEndEvent)("editor/documents/attach-preview"), () => (0, import_editor_v1_adapters27.getCanvasIframeDocument)());
6159
6292
  }
6160
6293
 
6161
6294
  // src/hooks/use-escape-on-canvas.ts
package/dist/index.mjs CHANGED
@@ -3794,6 +3794,9 @@ function initTabsModelExtensions() {
3794
3794
  registerModelExtensions("e-tab", tabModelExtensions);
3795
3795
  }
3796
3796
 
3797
+ // src/mcp/canvas-mcp.ts
3798
+ import { Schema as Schema6 } from "@elementor/editor-props";
3799
+
3797
3800
  // src/mcp/resources/available-widgets-resource.ts
3798
3801
  import { v1ReadyEvent as v1ReadyEvent3 } from "@elementor/editor-v1-adapters";
3799
3802
  var AVAILABLE_WIDGETS_URI = "elementor://context/available-widgets";
@@ -3945,6 +3948,107 @@ function extractElementData(element) {
3945
3948
  return result;
3946
3949
  }
3947
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
+
3948
4052
  // src/mcp/resources/editor-state-resource.ts
3949
4053
  import { __privateListenTo as listenTo2, commandEndEvent as commandEndEvent5 } from "@elementor/editor-v1-adapters";
3950
4054
  var CURRENTLY_VIEWED_SCREEN = "The user is currently viewing the Elementor editor";
@@ -4289,7 +4393,7 @@ import {
4289
4393
  updateElementSettings,
4290
4394
  updateElementStyle
4291
4395
  } from "@elementor/editor-elements";
4292
- import { getPropSchemaFromCache, Schema as Schema2 } from "@elementor/editor-props";
4396
+ import { getPropSchemaFromCache, Schema as Schema3 } from "@elementor/editor-props";
4293
4397
  import { getStylesSchema as getStylesSchema3, getVariantByMeta } from "@elementor/editor-styles";
4294
4398
  import { __privateRunCommandSync as runCommandSync2 } from "@elementor/editor-v1-adapters";
4295
4399
 
@@ -4314,9 +4418,12 @@ var LOCAL_STYLE_META = {
4314
4418
  };
4315
4419
  function resolvePropValue(value, forceKey) {
4316
4420
  const Utils = window.elementorV2.editorVariables.Utils;
4317
- return Schema2.adjustLlmPropValueSchema(value, {
4421
+ return Schema3.adjustLlmPropValueSchema(value, {
4318
4422
  forceKey,
4319
- transformers: Utils.globalVariablesLLMResolvers
4423
+ transformers: {
4424
+ ...Utils.globalVariablesLLMResolvers,
4425
+ [DYNAMIC_PROP_TYPE_KEY]: dynamicTagLLMResolver
4426
+ }
4320
4427
  });
4321
4428
  }
4322
4429
  var doUpdateElementProperty = (params) => {
@@ -4419,7 +4526,7 @@ var doUpdateElementProperty = (params) => {
4419
4526
  }
4420
4527
  const propKey = elementPropSchema[propertyName].key;
4421
4528
  const value = resolvePropValue(propertyValue, propKey);
4422
- const { valid, jsonSchema } = Schema2.validatePropValue(elementPropSchema[propertyName], propertyValue);
4529
+ const { valid, jsonSchema } = Schema3.validatePropValue(elementPropSchema[propertyName], propertyValue);
4423
4530
  if (!valid) {
4424
4531
  throw new Error(
4425
4532
  `Invalid PropValue for elementId: ${elementId}. PropKey: ${propKey}, PropValue: ${JSON.stringify(
@@ -4440,7 +4547,7 @@ Expected Schema: ${jsonSchema}`
4440
4547
 
4441
4548
  // src/mcp/utils/validate-input.ts
4442
4549
  import { getWidgetsCache as getWidgetsCache7 } from "@elementor/editor-elements";
4443
- import { Schema as Schema3 } from "@elementor/editor-props";
4550
+ import { Schema as Schema4 } from "@elementor/editor-props";
4444
4551
  import { getStylesSchema as getStylesSchema4 } from "@elementor/editor-styles";
4445
4552
  var _widgetsSchema = null;
4446
4553
  var validateInput = {
@@ -4474,10 +4581,10 @@ var validateInput = {
4474
4581
  if (!propSchema) {
4475
4582
  errors.push(`Property "${propName}" is not defined in the schema.`);
4476
4583
  hasInvalidKey = true;
4477
- } else if (!Schema3.isPropKeyConfigurable(propName, propSchema)) {
4584
+ } else if (!Schema4.isPropKeyConfigurable(propName, propSchema)) {
4478
4585
  errors.push(`Property "${propName}" is not configurable.`);
4479
4586
  } else {
4480
- const { valid } = Schema3.validatePropValue(propSchema, propValue);
4587
+ const { valid } = Schema4.validatePropValue(propSchema, propValue);
4481
4588
  if (!valid) {
4482
4589
  errors.push(
4483
4590
  `Invalid property "${propName}". Validate input with resource [${STYLE_SCHEMA_URI.replace(
@@ -4861,6 +4968,13 @@ Some elements have internal tree structures (nesting). When using these elements
4861
4968
  - NO LINKS in configuration
4862
4969
  - Retry on errors up to 10x
4863
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
+
4864
4978
  Note about configuration ids: These names are visible to the end-user, make sure they make sense, related and relevant.
4865
4979
 
4866
4980
  # DESIGN PHILOSOPHY: CONTEXT-DRIVEN CREATIVITY
@@ -5082,7 +5196,8 @@ var initBuildCompositionsTool = (reg) => {
5082
5196
  { description: "Global Classes", uri: "elementor://global-classes" },
5083
5197
  { description: "Global Variables", uri: "elementor://global-variables" },
5084
5198
  { description: "Styles best practices", uri: BEST_PRACTICES_URI },
5085
- { 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 }
5086
5201
  ],
5087
5202
  outputSchema,
5088
5203
  handler: async (rawParams) => {
@@ -5247,6 +5362,21 @@ For styleProperties, use the style schema provided, as it also uses the PropType
5247
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.
5248
5363
 
5249
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.
5250
5380
  `);
5251
5381
  configureElementToolPrompt.parameter("elementId", "The ID of the element to configure. MANDATORY.");
5252
5382
  configureElementToolPrompt.parameter(
@@ -5354,7 +5484,8 @@ var initConfigureElementTool = (reg) => {
5354
5484
  requiredResources: [
5355
5485
  { description: "Widgets schema", uri: WIDGET_SCHEMA_URI },
5356
5486
  { description: "Styles schema", uri: STYLE_SCHEMA_URI },
5357
- { 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 }
5358
5489
  ],
5359
5490
  handler: ({ elementId, propertiesToChange, elementType, stylePropertiesToChange }) => {
5360
5491
  const widgetData = getWidgetsCache10()?.[elementType];
@@ -5450,7 +5581,7 @@ Check the styles schema at the resource [${STYLE_SCHEMA_URI.replace(
5450
5581
 
5451
5582
  // src/mcp/tools/get-element-config/tool.ts
5452
5583
  import { getContainer as getContainer6, getElementStyles as getElementStyles2, getWidgetsCache as getWidgetsCache11 } from "@elementor/editor-elements";
5453
- import { Schema as Schema4 } from "@elementor/editor-props";
5584
+ import { Schema as Schema5 } from "@elementor/editor-props";
5454
5585
  import { z as z3 } from "@elementor/schema";
5455
5586
  var schema = {
5456
5587
  elementId: z3.string()
@@ -5507,7 +5638,7 @@ var initGetElementConfigTool = (reg) => {
5507
5638
  }
5508
5639
  const propValues = {};
5509
5640
  const stylePropValues = {};
5510
- Schema4.configurableKeys(propSchema).forEach((key) => {
5641
+ Schema5.configurableKeys(propSchema).forEach((key) => {
5511
5642
  propValues[key] = structuredClone(elementRawSettings.get(key));
5512
5643
  });
5513
5644
  const elementStyles = getElementStyles2(elementId) || {};
@@ -5543,9 +5674,11 @@ var initGetElementConfigTool = (reg) => {
5543
5674
 
5544
5675
  // src/mcp/canvas-mcp.ts
5545
5676
  var initCanvasMcp = (reg) => {
5677
+ Schema6.setDynamicTagNamesResolver(getDynamicTagNamesByCategories);
5546
5678
  initWidgetsSchemaResource(reg);
5547
5679
  initAvailableWidgetsResource(reg);
5548
5680
  initDocumentStructureResource(reg);
5681
+ initDynamicTagsResource(reg);
5549
5682
  initSelectedElementResource(reg);
5550
5683
  initEditorStateResource(reg);
5551
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-924",
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-924",
40
+ "@elementor/editor": "4.2.0-925",
41
41
  "dompurify": "^3.2.6",
42
- "@elementor/editor-controls": "4.2.0-924",
43
- "@elementor/editor-documents": "4.2.0-924",
44
- "@elementor/editor-elements": "4.2.0-924",
45
- "@elementor/editor-interactions": "4.2.0-924",
46
- "@elementor/editor-mcp": "4.2.0-924",
47
- "@elementor/editor-notifications": "4.2.0-924",
48
- "@elementor/editor-props": "4.2.0-924",
49
- "@elementor/editor-responsive": "4.2.0-924",
50
- "@elementor/editor-styles": "4.2.0-924",
51
- "@elementor/editor-styles-repository": "4.2.0-924",
52
- "@elementor/editor-ui": "4.2.0-924",
53
- "@elementor/editor-v1-adapters": "4.2.0-924",
54
- "@elementor/schema": "4.2.0-924",
55
- "@elementor/twing": "4.2.0-924",
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-924",
58
- "@elementor/wp-media": "4.2.0-924",
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
  },
@@ -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
@@ -10,6 +10,7 @@ import { type MCPRegistryEntry } from '@elementor/editor-mcp';
10
10
 
11
11
  import { CompositionBuilder } from '../../../composition-builder/composition-builder';
12
12
  import { AVAILABLE_WIDGETS_URI_V4 } from '../../resources/available-widgets-resource';
13
+ import { DYNAMIC_TAGS_URI } from '../../resources/dynamic-tags-resource';
13
14
  import { BEST_PRACTICES_URI, STYLE_SCHEMA_URI, WIDGET_SCHEMA_URI } from '../../resources/widgets-schema-resource';
14
15
  import { isWidgetAvailableForLLM } from '../../utils/element-data-util';
15
16
  import { getCompositionTargetContainer } from '../../utils/get-composition-target-container';
@@ -45,6 +46,7 @@ export const initBuildCompositionsTool = ( reg: MCPRegistryEntry ) => {
45
46
  { description: 'Global Variables', uri: 'elementor://global-variables' },
46
47
  { description: 'Styles best practices', uri: BEST_PRACTICES_URI },
47
48
  { description: 'Available widgets for this tool', uri: AVAILABLE_WIDGETS_URI_V4 },
49
+ { description: 'Dynamic tags catalog', uri: DYNAMIC_TAGS_URI },
48
50
  ],
49
51
  outputSchema,
50
52
  handler: async ( rawParams ) => {
@@ -1,5 +1,6 @@
1
1
  import { toolPrompts } from '@elementor/editor-mcp';
2
2
 
3
+ import { DYNAMIC_TAGS_URI } from '../../resources/dynamic-tags-resource';
3
4
  import { STYLE_SCHEMA_URI, WIDGET_SCHEMA_URI } from '../../resources/widgets-schema-resource';
4
5
 
5
6
  export const CONFIGURE_ELEMENT_GUIDE_URI = 'elementor://canvas/tools/configure-element-guide';
@@ -59,6 +60,21 @@ For styleProperties, use the style schema provided, as it also uses the PropType
59
60
  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.
60
61
 
61
62
  Use the EXACT "PROP-TYPE" Schema given, and ALWAYS include the "key" property from the original configuration for every property you are changing.
63
+
64
+ # Dynamic tags
65
+ 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.
66
+ 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.
67
+ 1. Read the [${ DYNAMIC_TAGS_URI }] resource for each allowed tag's settings schema.
68
+ 2. Provide, at that node:
69
+ {
70
+ "$$type": "dynamic",
71
+ "value": {
72
+ "name": "<allowed tag name>",
73
+ "settings": { /* strictly per the tag's settings schema */ }
74
+ }
75
+ }
76
+ Image example: { "$$type": "image", "value": { "src": { "$$type": "dynamic", "value": { "name": "<image tag>", "settings": { ... } } } } }
77
+ Do NOT send "group" (it is resolved automatically). Use { "settings": {} } only when the tag has no settings.
62
78
  ` );
63
79
 
64
80
  configureElementToolPrompt.parameter( 'elementId', 'The ID of the element to configure. MANDATORY.' );
@@ -1,6 +1,7 @@
1
1
  import { getWidgetsCache } from '@elementor/editor-elements';
2
2
  import { type MCPRegistryEntry } from '@elementor/editor-mcp';
3
3
 
4
+ import { DYNAMIC_TAGS_URI } from '../../resources/dynamic-tags-resource';
4
5
  import { STYLE_SCHEMA_URI, WIDGET_SCHEMA_URI } from '../../resources/widgets-schema-resource';
5
6
  import { doUpdateElementProperty } from '../../utils/do-update-element-property';
6
7
  import { validateInput } from '../../utils/validate-input';
@@ -32,6 +33,7 @@ export const initConfigureElementTool = ( reg: MCPRegistryEntry ) => {
32
33
  { description: 'Widgets schema', uri: WIDGET_SCHEMA_URI },
33
34
  { description: 'Styles schema', uri: STYLE_SCHEMA_URI },
34
35
  { description: 'Configure element guide', uri: CONFIGURE_ELEMENT_GUIDE_URI },
36
+ { description: 'Dynamic tags catalog', uri: DYNAMIC_TAGS_URI },
35
37
  ],
36
38
  handler: ( { elementId, propertiesToChange, elementType, stylePropertiesToChange } ) => {
37
39
  const widgetData = getWidgetsCache()?.[ elementType ];
@@ -0,0 +1,102 @@
1
+ import { getElementorConfig } from '@elementor/editor-v1-adapters';
2
+
3
+ import { dynamicTagLLMResolver, getDynamicTagNamesByCategories } from '../resolve-dynamic-tag';
4
+
5
+ jest.mock( '@elementor/editor-v1-adapters', () => ( {
6
+ getElementorConfig: jest.fn(),
7
+ } ) );
8
+
9
+ const mockedGetElementorConfig = getElementorConfig as jest.MockedFunction< typeof getElementorConfig >;
10
+
11
+ const givenTags = () =>
12
+ mockedGetElementorConfig.mockReturnValue( {
13
+ atomicDynamicTags: {
14
+ tags: {
15
+ 'post-custom-field': {
16
+ name: 'post-custom-field',
17
+ label: 'Post Custom Field',
18
+ group: 'post',
19
+ categories: [ 'text', 'url' ],
20
+ props_schema: {
21
+ key: { kind: 'plain', key: 'string', default: '' },
22
+ before: { kind: 'plain', key: 'string', default: 'prefix' },
23
+ fallback: { kind: 'plain', key: 'string', default: '' },
24
+ },
25
+ },
26
+ 'site-title': {
27
+ name: 'site-title',
28
+ label: 'Site Title',
29
+ group: 'site',
30
+ categories: [ 'text' ],
31
+ props_schema: {},
32
+ },
33
+ },
34
+ },
35
+ } as never );
36
+
37
+ describe( 'resolve-dynamic-tag', () => {
38
+ beforeEach( () => {
39
+ jest.clearAllMocks();
40
+ givenTags();
41
+ } );
42
+
43
+ describe( 'getDynamicTagNamesByCategories', () => {
44
+ it( 'returns only the tags whose categories intersect the prop categories', () => {
45
+ // Act
46
+ const names = getDynamicTagNamesByCategories( [ 'url' ] );
47
+
48
+ // Assert
49
+ expect( names ).toEqual( [ 'post-custom-field' ] );
50
+ } );
51
+
52
+ it( 'returns an empty list when no categories are requested', () => {
53
+ // Act & Assert
54
+ expect( getDynamicTagNamesByCategories( [] ) ).toEqual( [] );
55
+ } );
56
+ } );
57
+
58
+ describe( 'dynamicTagLLMResolver', () => {
59
+ it( 'fills the group from the registry and wraps provided scalar settings', () => {
60
+ // Act
61
+ const resolved = dynamicTagLLMResolver( {
62
+ name: 'post-custom-field',
63
+ settings: { key: 'price' },
64
+ } );
65
+
66
+ // Assert
67
+ expect( resolved ).toEqual( {
68
+ $$type: 'dynamic',
69
+ value: {
70
+ name: 'post-custom-field',
71
+ group: 'post',
72
+ settings: {
73
+ key: { $$type: 'string', value: 'price' },
74
+ before: { $$type: 'string', value: 'prefix' },
75
+ },
76
+ },
77
+ } );
78
+ } );
79
+
80
+ it( 'keeps already-wrapped settings values untouched', () => {
81
+ // Act
82
+ const resolved = dynamicTagLLMResolver( {
83
+ name: 'post-custom-field',
84
+ settings: { key: { $$type: 'string', value: 'price' } },
85
+ } ) as { value: { settings: Record< string, unknown > } };
86
+
87
+ // Assert
88
+ expect( resolved.value.settings.key ).toEqual( { $$type: 'string', value: 'price' } );
89
+ } );
90
+
91
+ it( 'returns a structurally complete value for unknown tags', () => {
92
+ // Act
93
+ const resolved = dynamicTagLLMResolver( { name: 'does-not-exist' } );
94
+
95
+ // Assert
96
+ expect( resolved ).toEqual( {
97
+ $$type: 'dynamic',
98
+ value: { name: 'does-not-exist', group: '', settings: {} },
99
+ } );
100
+ } );
101
+ } );
102
+ } );
@@ -12,6 +12,7 @@ import { type Utils as IUtils } from '@elementor/editor-variables';
12
12
  import { type z } from '@elementor/schema';
13
13
 
14
14
  import { mergeCustomCssText, readStoredCustomCssText } from './merge-custom-css';
15
+ import { DYNAMIC_PROP_TYPE_KEY, dynamicTagLLMResolver } from './resolve-dynamic-tag';
15
16
 
16
17
  // TODO: see https://elementor.atlassian.net/browse/ED-22513 for better cross-module access
17
18
  type XElementor = z.infer< z.ZodAny >;
@@ -34,7 +35,10 @@ export function resolvePropValue( value: unknown, forceKey?: string ): PropValue
34
35
  .Utils as typeof IUtils;
35
36
  return Schema.adjustLlmPropValueSchema( value as PropValue, {
36
37
  forceKey,
37
- transformers: Utils.globalVariablesLLMResolvers,
38
+ transformers: {
39
+ ...Utils.globalVariablesLLMResolvers,
40
+ [ DYNAMIC_PROP_TYPE_KEY ]: dynamicTagLLMResolver,
41
+ },
38
42
  } );
39
43
  }
40
44
 
@@ -0,0 +1,102 @@
1
+ import { type PropType, type PropValue } from '@elementor/editor-props';
2
+ import { getElementorConfig } from '@elementor/editor-v1-adapters';
3
+
4
+ export const DYNAMIC_PROP_TYPE_KEY = 'dynamic';
5
+
6
+ // `fallback` is a generic render-time default added to every tag; it is noise for configuration.
7
+ export const OMITTED_DYNAMIC_SETTING_KEYS = [ 'fallback' ] as const;
8
+
9
+ type SettingPropType = PropType & { key?: string };
10
+
11
+ export type AtomicDynamicTag = {
12
+ name: string;
13
+ label: string;
14
+ group: string;
15
+ categories: string[];
16
+ props_schema: Record< string, SettingPropType >;
17
+ };
18
+
19
+ type LlmDynamicValue = {
20
+ name?: string;
21
+ settings?: Record< string, unknown >;
22
+ };
23
+
24
+ export const getAtomicDynamicTags = (): Record< string, AtomicDynamicTag > => {
25
+ const config = getElementorConfig() as { atomicDynamicTags?: { tags?: Record< string, AtomicDynamicTag > } };
26
+ return config.atomicDynamicTags?.tags ?? {};
27
+ };
28
+
29
+ export const getDynamicTagNamesByCategories = ( categories: string[] ): string[] => {
30
+ if ( ! categories.length ) {
31
+ return [];
32
+ }
33
+ const wanted = new Set( categories );
34
+ return Object.values( getAtomicDynamicTags() )
35
+ .filter( ( tag ) => tag.categories?.some( ( category ) => wanted.has( category ) ) )
36
+ .map( ( tag ) => tag.name );
37
+ };
38
+
39
+ // Reconstructs an intact dynamic PropValue from whatever the LLM produced: the authoritative `group`
40
+ // is taken from the registry (never trusted from the model) and `settings` are rebuilt strictly from
41
+ // the tag's props schema (provided values are wrapped, omitted ones fall back to their defaults).
42
+ export const dynamicTagLLMResolver = ( value: unknown ): PropValue => {
43
+ const input = ( value ?? {} ) as LlmDynamicValue;
44
+ const tag = input.name ? getAtomicDynamicTags()[ input.name ] : undefined;
45
+
46
+ if ( ! tag ) {
47
+ return {
48
+ $$type: DYNAMIC_PROP_TYPE_KEY,
49
+ value: { name: input.name ?? '', group: '', settings: {} },
50
+ } as PropValue;
51
+ }
52
+
53
+ return {
54
+ $$type: DYNAMIC_PROP_TYPE_KEY,
55
+ value: {
56
+ name: tag.name,
57
+ group: tag.group,
58
+ settings: buildStrictSettings( tag.props_schema ?? {}, input.settings ?? {} ),
59
+ },
60
+ } as PropValue;
61
+ };
62
+
63
+ const buildStrictSettings = (
64
+ schema: Record< string, SettingPropType >,
65
+ provided: Record< string, unknown >
66
+ ): Record< string, unknown > => {
67
+ const settings: Record< string, unknown > = {};
68
+
69
+ for ( const [ key, propType ] of Object.entries( schema ) ) {
70
+ if ( ( OMITTED_DYNAMIC_SETTING_KEYS as readonly string[] ).includes( key ) ) {
71
+ continue;
72
+ }
73
+
74
+ const resolved =
75
+ provided[ key ] !== undefined
76
+ ? wrapSettingValue( provided[ key ], propType )
77
+ : defaultSettingValue( propType );
78
+
79
+ if ( resolved !== undefined && resolved !== null ) {
80
+ settings[ key ] = resolved;
81
+ }
82
+ }
83
+
84
+ return settings;
85
+ };
86
+
87
+ const wrapSettingValue = ( raw: unknown, propType: SettingPropType ): unknown => {
88
+ if ( raw !== null && typeof raw === 'object' ) {
89
+ return raw;
90
+ }
91
+ return propType.key ? { $$type: propType.key, value: raw } : raw;
92
+ };
93
+
94
+ const defaultSettingValue = ( propType: SettingPropType ): unknown => {
95
+ if ( propType.initial_value !== null && propType.initial_value !== undefined ) {
96
+ return propType.initial_value;
97
+ }
98
+ if ( propType.default !== null && propType.default !== undefined ) {
99
+ return wrapSettingValue( propType.default, propType );
100
+ }
101
+ return undefined;
102
+ };