@elementor/editor-canvas 4.2.0-924 → 4.2.0-926
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 +186 -51
- package/dist/index.mjs +148 -13
- package/package.json +18 -18
- package/src/legacy/create-nested-templated-element-type.ts +1 -0
- package/src/legacy/create-templated-element-type.ts +1 -0
- package/src/mcp/canvas-mcp.ts +5 -0
- package/src/mcp/resources/__tests__/dynamic-tags-resource.test.ts +85 -0
- package/src/mcp/resources/dynamic-tags-resource.ts +52 -0
- package/src/mcp/tools/build-composition/prompt.ts +8 -0
- package/src/mcp/tools/build-composition/tool.ts +2 -0
- package/src/mcp/tools/configure-element/prompt.ts +16 -0
- package/src/mcp/tools/configure-element/tool.ts +2 -0
- package/src/mcp/utils/__tests__/resolve-dynamic-tag.test.ts +102 -0
- package/src/mcp/utils/do-update-element-property.ts +5 -1
- package/src/mcp/utils/resolve-dynamic-tag.ts +102 -0
package/dist/index.js
CHANGED
|
@@ -2742,7 +2742,8 @@ function createTemplatedElementView({
|
|
|
2742
2742
|
interaction_id: this.getInteractionId(),
|
|
2743
2743
|
type,
|
|
2744
2744
|
settings,
|
|
2745
|
-
base_styles: baseStylesDictionary
|
|
2745
|
+
base_styles: baseStylesDictionary,
|
|
2746
|
+
...this.getResolverRenderContext?.() ?? {}
|
|
2746
2747
|
};
|
|
2747
2748
|
return renderer.render(templateKey, context);
|
|
2748
2749
|
}).then((html) => {
|
|
@@ -2906,7 +2907,8 @@ function createNestedTemplatedElementView({
|
|
|
2906
2907
|
settings,
|
|
2907
2908
|
base_styles: baseStylesDictionary,
|
|
2908
2909
|
editor_attributes: buildEditorAttributes(model),
|
|
2909
|
-
editor_classes: buildEditorClasses(model)
|
|
2910
|
+
editor_classes: buildEditorClasses(model),
|
|
2911
|
+
...this.getResolverRenderContext?.() ?? {}
|
|
2910
2912
|
};
|
|
2911
2913
|
return renderer.render(templateKey, context);
|
|
2912
2914
|
}).then((html) => {
|
|
@@ -3830,6 +3832,9 @@ function initTabsModelExtensions() {
|
|
|
3830
3832
|
registerModelExtensions("e-tab", tabModelExtensions);
|
|
3831
3833
|
}
|
|
3832
3834
|
|
|
3835
|
+
// src/mcp/canvas-mcp.ts
|
|
3836
|
+
var import_editor_props11 = require("@elementor/editor-props");
|
|
3837
|
+
|
|
3833
3838
|
// src/mcp/resources/available-widgets-resource.ts
|
|
3834
3839
|
var import_editor_v1_adapters15 = require("@elementor/editor-v1-adapters");
|
|
3835
3840
|
var AVAILABLE_WIDGETS_URI = "elementor://context/available-widgets";
|
|
@@ -3979,8 +3984,109 @@ function extractElementData(element) {
|
|
|
3979
3984
|
return result;
|
|
3980
3985
|
}
|
|
3981
3986
|
|
|
3982
|
-
// src/mcp/resources/
|
|
3987
|
+
// src/mcp/resources/dynamic-tags-resource.ts
|
|
3988
|
+
var import_editor_props7 = require("@elementor/editor-props");
|
|
3989
|
+
|
|
3990
|
+
// src/mcp/utils/resolve-dynamic-tag.ts
|
|
3983
3991
|
var import_editor_v1_adapters17 = require("@elementor/editor-v1-adapters");
|
|
3992
|
+
var DYNAMIC_PROP_TYPE_KEY = "dynamic";
|
|
3993
|
+
var OMITTED_DYNAMIC_SETTING_KEYS = ["fallback"];
|
|
3994
|
+
var getAtomicDynamicTags = () => {
|
|
3995
|
+
const config = (0, import_editor_v1_adapters17.getElementorConfig)();
|
|
3996
|
+
return config.atomicDynamicTags?.tags ?? {};
|
|
3997
|
+
};
|
|
3998
|
+
var getDynamicTagNamesByCategories = (categories) => {
|
|
3999
|
+
if (!categories.length) {
|
|
4000
|
+
return [];
|
|
4001
|
+
}
|
|
4002
|
+
const wanted = new Set(categories);
|
|
4003
|
+
return Object.values(getAtomicDynamicTags()).filter((tag) => tag.categories?.some((category) => wanted.has(category))).map((tag) => tag.name);
|
|
4004
|
+
};
|
|
4005
|
+
var dynamicTagLLMResolver = (value) => {
|
|
4006
|
+
const input = value ?? {};
|
|
4007
|
+
const tag = input.name ? getAtomicDynamicTags()[input.name] : void 0;
|
|
4008
|
+
if (!tag) {
|
|
4009
|
+
return {
|
|
4010
|
+
$$type: DYNAMIC_PROP_TYPE_KEY,
|
|
4011
|
+
value: { name: input.name ?? "", group: "", settings: {} }
|
|
4012
|
+
};
|
|
4013
|
+
}
|
|
4014
|
+
return {
|
|
4015
|
+
$$type: DYNAMIC_PROP_TYPE_KEY,
|
|
4016
|
+
value: {
|
|
4017
|
+
name: tag.name,
|
|
4018
|
+
group: tag.group,
|
|
4019
|
+
settings: buildStrictSettings(tag.props_schema ?? {}, input.settings ?? {})
|
|
4020
|
+
}
|
|
4021
|
+
};
|
|
4022
|
+
};
|
|
4023
|
+
var buildStrictSettings = (schema2, provided) => {
|
|
4024
|
+
const settings = {};
|
|
4025
|
+
for (const [key, propType] of Object.entries(schema2)) {
|
|
4026
|
+
if (OMITTED_DYNAMIC_SETTING_KEYS.includes(key)) {
|
|
4027
|
+
continue;
|
|
4028
|
+
}
|
|
4029
|
+
const resolved = provided[key] !== void 0 ? wrapSettingValue(provided[key], propType) : defaultSettingValue(propType);
|
|
4030
|
+
if (resolved !== void 0 && resolved !== null) {
|
|
4031
|
+
settings[key] = resolved;
|
|
4032
|
+
}
|
|
4033
|
+
}
|
|
4034
|
+
return settings;
|
|
4035
|
+
};
|
|
4036
|
+
var wrapSettingValue = (raw, propType) => {
|
|
4037
|
+
if (raw !== null && typeof raw === "object") {
|
|
4038
|
+
return raw;
|
|
4039
|
+
}
|
|
4040
|
+
return propType.key ? { $$type: propType.key, value: raw } : raw;
|
|
4041
|
+
};
|
|
4042
|
+
var defaultSettingValue = (propType) => {
|
|
4043
|
+
if (propType.initial_value !== null && propType.initial_value !== void 0) {
|
|
4044
|
+
return propType.initial_value;
|
|
4045
|
+
}
|
|
4046
|
+
if (propType.default !== null && propType.default !== void 0) {
|
|
4047
|
+
return wrapSettingValue(propType.default, propType);
|
|
4048
|
+
}
|
|
4049
|
+
return void 0;
|
|
4050
|
+
};
|
|
4051
|
+
|
|
4052
|
+
// src/mcp/resources/dynamic-tags-resource.ts
|
|
4053
|
+
var DYNAMIC_TAGS_URI = "elementor://dynamic-tags";
|
|
4054
|
+
var settingsSchema = (propsSchema) => {
|
|
4055
|
+
return Object.fromEntries(
|
|
4056
|
+
Object.entries(propsSchema ?? {}).filter(([key]) => !OMITTED_DYNAMIC_SETTING_KEYS.includes(key)).map(([key, propType]) => [key, import_editor_props7.Schema.propTypeToJsonSchema(propType)])
|
|
4057
|
+
);
|
|
4058
|
+
};
|
|
4059
|
+
var buildDynamicTagsList = () => {
|
|
4060
|
+
return Object.values(getAtomicDynamicTags()).map((tag) => ({
|
|
4061
|
+
name: tag.name,
|
|
4062
|
+
label: tag.label,
|
|
4063
|
+
categories: tag.categories,
|
|
4064
|
+
settings: settingsSchema(tag.props_schema)
|
|
4065
|
+
}));
|
|
4066
|
+
};
|
|
4067
|
+
var initDynamicTagsResource = (reg) => {
|
|
4068
|
+
const { resource } = reg;
|
|
4069
|
+
resource(
|
|
4070
|
+
"dynamic-tags",
|
|
4071
|
+
DYNAMIC_TAGS_URI,
|
|
4072
|
+
{
|
|
4073
|
+
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.`,
|
|
4074
|
+
mimeType: "application/json"
|
|
4075
|
+
},
|
|
4076
|
+
async (uri) => ({
|
|
4077
|
+
contents: [
|
|
4078
|
+
{
|
|
4079
|
+
uri: uri.href,
|
|
4080
|
+
mimeType: "application/json",
|
|
4081
|
+
text: JSON.stringify(buildDynamicTagsList())
|
|
4082
|
+
}
|
|
4083
|
+
]
|
|
4084
|
+
})
|
|
4085
|
+
);
|
|
4086
|
+
};
|
|
4087
|
+
|
|
4088
|
+
// src/mcp/resources/editor-state-resource.ts
|
|
4089
|
+
var import_editor_v1_adapters18 = require("@elementor/editor-v1-adapters");
|
|
3984
4090
|
var CURRENTLY_VIEWED_SCREEN = "The user is currently viewing the Elementor editor";
|
|
3985
4091
|
var PAGE_CONTENT_CHARACTER_LIMIT = 500;
|
|
3986
4092
|
var PREVIEW_TEXT_NODE_MIN_LENGTH = 2;
|
|
@@ -4001,8 +4107,8 @@ var initEditorStateResource = (reg) => {
|
|
|
4001
4107
|
lastSerializedState = serialized;
|
|
4002
4108
|
sendResourceUpdated({ uri: EDITOR_STATE_URI });
|
|
4003
4109
|
};
|
|
4004
|
-
(0,
|
|
4005
|
-
[(0,
|
|
4110
|
+
(0, import_editor_v1_adapters18.__privateListenTo)(
|
|
4111
|
+
[(0, import_editor_v1_adapters18.commandEndEvent)("editor/documents/switch"), (0, import_editor_v1_adapters18.commandEndEvent)("editor/documents/attach-preview")],
|
|
4006
4112
|
notifyIfChanged
|
|
4007
4113
|
);
|
|
4008
4114
|
lastSerializedState = JSON.stringify(buildState());
|
|
@@ -4076,7 +4182,7 @@ function getPageTitle() {
|
|
|
4076
4182
|
}
|
|
4077
4183
|
|
|
4078
4184
|
// src/mcp/resources/general-context-resource.ts
|
|
4079
|
-
var
|
|
4185
|
+
var import_editor_v1_adapters19 = require("@elementor/editor-v1-adapters");
|
|
4080
4186
|
var GENERAL_CONTEXT_URI = "elementor://context/general";
|
|
4081
4187
|
var initGeneralContextResource = (reg) => {
|
|
4082
4188
|
const { resource, sendResourceUpdated } = reg;
|
|
@@ -4137,11 +4243,11 @@ var initGeneralContextResource = (reg) => {
|
|
|
4137
4243
|
};
|
|
4138
4244
|
}
|
|
4139
4245
|
);
|
|
4140
|
-
(0,
|
|
4246
|
+
(0, import_editor_v1_adapters19.__privateListenTo)(
|
|
4141
4247
|
[
|
|
4142
|
-
(0,
|
|
4143
|
-
(0,
|
|
4144
|
-
(0,
|
|
4248
|
+
(0, import_editor_v1_adapters19.commandEndEvent)("editor/documents/switch"),
|
|
4249
|
+
(0, import_editor_v1_adapters19.commandEndEvent)("editor/documents/attach-preview"),
|
|
4250
|
+
(0, import_editor_v1_adapters19.commandEndEvent)("document/elements/settings")
|
|
4145
4251
|
],
|
|
4146
4252
|
pushUpdateIfChanged
|
|
4147
4253
|
);
|
|
@@ -4150,7 +4256,7 @@ var initGeneralContextResource = (reg) => {
|
|
|
4150
4256
|
|
|
4151
4257
|
// src/mcp/resources/selected-element-resource.ts
|
|
4152
4258
|
var import_editor_elements12 = require("@elementor/editor-elements");
|
|
4153
|
-
var
|
|
4259
|
+
var import_editor_v1_adapters20 = require("@elementor/editor-v1-adapters");
|
|
4154
4260
|
var SELECTED_ELEMENT_URI = "elementor://context/selected-element";
|
|
4155
4261
|
var initSelectedElementResource = (reg) => {
|
|
4156
4262
|
const { resource, sendResourceUpdated } = reg;
|
|
@@ -4181,11 +4287,11 @@ var initSelectedElementResource = (reg) => {
|
|
|
4181
4287
|
}
|
|
4182
4288
|
publishIfChanged(readSelectionFromEditor());
|
|
4183
4289
|
};
|
|
4184
|
-
(0,
|
|
4290
|
+
(0, import_editor_v1_adapters20.__privateListenTo)(
|
|
4185
4291
|
[
|
|
4186
|
-
(0,
|
|
4187
|
-
(0,
|
|
4188
|
-
(0,
|
|
4292
|
+
(0, import_editor_v1_adapters20.commandEndEvent)("document/elements/select"),
|
|
4293
|
+
(0, import_editor_v1_adapters20.commandEndEvent)("document/elements/deselect-all"),
|
|
4294
|
+
(0, import_editor_v1_adapters20.commandEndEvent)("document/elements/settings")
|
|
4189
4295
|
],
|
|
4190
4296
|
onCommand
|
|
4191
4297
|
);
|
|
@@ -4303,9 +4409,9 @@ var import_editor_elements15 = require("@elementor/editor-elements");
|
|
|
4303
4409
|
|
|
4304
4410
|
// src/mcp/utils/do-update-element-property.ts
|
|
4305
4411
|
var import_editor_elements13 = require("@elementor/editor-elements");
|
|
4306
|
-
var
|
|
4412
|
+
var import_editor_props8 = require("@elementor/editor-props");
|
|
4307
4413
|
var import_editor_styles5 = require("@elementor/editor-styles");
|
|
4308
|
-
var
|
|
4414
|
+
var import_editor_v1_adapters21 = require("@elementor/editor-v1-adapters");
|
|
4309
4415
|
|
|
4310
4416
|
// src/mcp/utils/merge-custom-css.ts
|
|
4311
4417
|
var CUSTOM_CSS_SEPARATOR = "\n";
|
|
@@ -4328,9 +4434,12 @@ var LOCAL_STYLE_META = {
|
|
|
4328
4434
|
};
|
|
4329
4435
|
function resolvePropValue(value, forceKey) {
|
|
4330
4436
|
const Utils = window.elementorV2.editorVariables.Utils;
|
|
4331
|
-
return
|
|
4437
|
+
return import_editor_props8.Schema.adjustLlmPropValueSchema(value, {
|
|
4332
4438
|
forceKey,
|
|
4333
|
-
transformers:
|
|
4439
|
+
transformers: {
|
|
4440
|
+
...Utils.globalVariablesLLMResolvers,
|
|
4441
|
+
[DYNAMIC_PROP_TYPE_KEY]: dynamicTagLLMResolver
|
|
4442
|
+
}
|
|
4334
4443
|
});
|
|
4335
4444
|
}
|
|
4336
4445
|
var doUpdateElementProperty = (params) => {
|
|
@@ -4380,7 +4489,7 @@ var doUpdateElementProperty = (params) => {
|
|
|
4380
4489
|
}
|
|
4381
4490
|
if (propertyRawSchema.kind === "plain") {
|
|
4382
4491
|
if (typeof propertyMapValue[stylePropName] !== "object") {
|
|
4383
|
-
const propUtil = (0,
|
|
4492
|
+
const propUtil = (0, import_editor_props8.getPropSchemaFromCache)(propertyRawSchema.key);
|
|
4384
4493
|
if (propUtil) {
|
|
4385
4494
|
const plainValue = propUtil.create(propertyMapValue[stylePropName]);
|
|
4386
4495
|
propertyMapValue[stylePropName] = plainValue;
|
|
@@ -4433,7 +4542,7 @@ var doUpdateElementProperty = (params) => {
|
|
|
4433
4542
|
}
|
|
4434
4543
|
const propKey = elementPropSchema[propertyName].key;
|
|
4435
4544
|
const value = resolvePropValue(propertyValue, propKey);
|
|
4436
|
-
const { valid, jsonSchema } =
|
|
4545
|
+
const { valid, jsonSchema } = import_editor_props8.Schema.validatePropValue(elementPropSchema[propertyName], propertyValue);
|
|
4437
4546
|
if (!valid) {
|
|
4438
4547
|
throw new Error(
|
|
4439
4548
|
`Invalid PropValue for elementId: ${elementId}. PropKey: ${propKey}, PropValue: ${JSON.stringify(
|
|
@@ -4449,12 +4558,12 @@ Expected Schema: ${jsonSchema}`
|
|
|
4449
4558
|
},
|
|
4450
4559
|
withHistory: false
|
|
4451
4560
|
});
|
|
4452
|
-
(0,
|
|
4561
|
+
(0, import_editor_v1_adapters21.__privateRunCommandSync)("document/save/set-is-modified", { status: true }, { internal: true });
|
|
4453
4562
|
};
|
|
4454
4563
|
|
|
4455
4564
|
// src/mcp/utils/validate-input.ts
|
|
4456
4565
|
var import_editor_elements14 = require("@elementor/editor-elements");
|
|
4457
|
-
var
|
|
4566
|
+
var import_editor_props9 = require("@elementor/editor-props");
|
|
4458
4567
|
var import_editor_styles6 = require("@elementor/editor-styles");
|
|
4459
4568
|
var _widgetsSchema = null;
|
|
4460
4569
|
var validateInput = {
|
|
@@ -4488,10 +4597,10 @@ var validateInput = {
|
|
|
4488
4597
|
if (!propSchema) {
|
|
4489
4598
|
errors.push(`Property "${propName}" is not defined in the schema.`);
|
|
4490
4599
|
hasInvalidKey = true;
|
|
4491
|
-
} else if (!
|
|
4600
|
+
} else if (!import_editor_props9.Schema.isPropKeyConfigurable(propName, propSchema)) {
|
|
4492
4601
|
errors.push(`Property "${propName}" is not configurable.`);
|
|
4493
4602
|
} else {
|
|
4494
|
-
const { valid } =
|
|
4603
|
+
const { valid } = import_editor_props9.Schema.validatePropValue(propSchema, propValue);
|
|
4495
4604
|
if (!valid) {
|
|
4496
4605
|
errors.push(
|
|
4497
4606
|
`Invalid property "${propName}". Validate input with resource [${STYLE_SCHEMA_URI.replace(
|
|
@@ -4875,6 +4984,13 @@ Some elements have internal tree structures (nesting). When using these elements
|
|
|
4875
4984
|
- NO LINKS in configuration
|
|
4876
4985
|
- Retry on errors up to 10x
|
|
4877
4986
|
|
|
4987
|
+
# DYNAMIC TAGS
|
|
4988
|
+
- 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\`).
|
|
4989
|
+
- 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.
|
|
4990
|
+
- Provide at that node: \`{ "$$type": "dynamic", "value": { "name": "<allowed tag>", "settings": { ... } } }\`
|
|
4991
|
+
- Example (image): \`{ "$$type": "image", "value": { "src": { "$$type": "dynamic", "value": { "name": "<image tag>", "settings": { ... } } } } }\`
|
|
4992
|
+
- Do NOT send \`group\` (it is resolved automatically). Populate \`settings\` strictly per the tag's schema; use \`{}\` only when it has none.
|
|
4993
|
+
|
|
4878
4994
|
Note about configuration ids: These names are visible to the end-user, make sure they make sense, related and relevant.
|
|
4879
4995
|
|
|
4880
4996
|
# DESIGN PHILOSOPHY: CONTEXT-DRIVEN CREATIVITY
|
|
@@ -5096,7 +5212,8 @@ var initBuildCompositionsTool = (reg) => {
|
|
|
5096
5212
|
{ description: "Global Classes", uri: "elementor://global-classes" },
|
|
5097
5213
|
{ description: "Global Variables", uri: "elementor://global-variables" },
|
|
5098
5214
|
{ description: "Styles best practices", uri: BEST_PRACTICES_URI },
|
|
5099
|
-
{ description: "Available widgets for this tool", uri: AVAILABLE_WIDGETS_URI_V4 }
|
|
5215
|
+
{ description: "Available widgets for this tool", uri: AVAILABLE_WIDGETS_URI_V4 },
|
|
5216
|
+
{ description: "Dynamic tags catalog", uri: DYNAMIC_TAGS_URI }
|
|
5100
5217
|
],
|
|
5101
5218
|
outputSchema,
|
|
5102
5219
|
handler: async (rawParams) => {
|
|
@@ -5261,6 +5378,21 @@ For styleProperties, use the style schema provided, as it also uses the PropType
|
|
|
5261
5378
|
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
5379
|
|
|
5263
5380
|
Use the EXACT "PROP-TYPE" Schema given, and ALWAYS include the "key" property from the original configuration for every property you are changing.
|
|
5381
|
+
|
|
5382
|
+
# Dynamic tags
|
|
5383
|
+
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.
|
|
5384
|
+
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.
|
|
5385
|
+
1. Read the [${DYNAMIC_TAGS_URI}] resource for each allowed tag's settings schema.
|
|
5386
|
+
2. Provide, at that node:
|
|
5387
|
+
{
|
|
5388
|
+
"$$type": "dynamic",
|
|
5389
|
+
"value": {
|
|
5390
|
+
"name": "<allowed tag name>",
|
|
5391
|
+
"settings": { /* strictly per the tag's settings schema */ }
|
|
5392
|
+
}
|
|
5393
|
+
}
|
|
5394
|
+
Image example: { "$$type": "image", "value": { "src": { "$$type": "dynamic", "value": { "name": "<image tag>", "settings": { ... } } } } }
|
|
5395
|
+
Do NOT send "group" (it is resolved automatically). Use { "settings": {} } only when the tag has no settings.
|
|
5264
5396
|
`);
|
|
5265
5397
|
configureElementToolPrompt.parameter("elementId", "The ID of the element to configure. MANDATORY.");
|
|
5266
5398
|
configureElementToolPrompt.parameter(
|
|
@@ -5368,7 +5500,8 @@ var initConfigureElementTool = (reg) => {
|
|
|
5368
5500
|
requiredResources: [
|
|
5369
5501
|
{ description: "Widgets schema", uri: WIDGET_SCHEMA_URI },
|
|
5370
5502
|
{ description: "Styles schema", uri: STYLE_SCHEMA_URI },
|
|
5371
|
-
{ description: "Configure element guide", uri: CONFIGURE_ELEMENT_GUIDE_URI }
|
|
5503
|
+
{ description: "Configure element guide", uri: CONFIGURE_ELEMENT_GUIDE_URI },
|
|
5504
|
+
{ description: "Dynamic tags catalog", uri: DYNAMIC_TAGS_URI }
|
|
5372
5505
|
],
|
|
5373
5506
|
handler: ({ elementId, propertiesToChange, elementType, stylePropertiesToChange }) => {
|
|
5374
5507
|
const widgetData = (0, import_editor_elements17.getWidgetsCache)()?.[elementType];
|
|
@@ -5464,7 +5597,7 @@ Check the styles schema at the resource [${STYLE_SCHEMA_URI.replace(
|
|
|
5464
5597
|
|
|
5465
5598
|
// src/mcp/tools/get-element-config/tool.ts
|
|
5466
5599
|
var import_editor_elements18 = require("@elementor/editor-elements");
|
|
5467
|
-
var
|
|
5600
|
+
var import_editor_props10 = require("@elementor/editor-props");
|
|
5468
5601
|
var import_schema5 = require("@elementor/schema");
|
|
5469
5602
|
var schema = {
|
|
5470
5603
|
elementId: import_schema5.z.string()
|
|
@@ -5521,7 +5654,7 @@ var initGetElementConfigTool = (reg) => {
|
|
|
5521
5654
|
}
|
|
5522
5655
|
const propValues = {};
|
|
5523
5656
|
const stylePropValues = {};
|
|
5524
|
-
|
|
5657
|
+
import_editor_props10.Schema.configurableKeys(propSchema).forEach((key) => {
|
|
5525
5658
|
propValues[key] = structuredClone(elementRawSettings.get(key));
|
|
5526
5659
|
});
|
|
5527
5660
|
const elementStyles = (0, import_editor_elements18.getElementStyles)(elementId) || {};
|
|
@@ -5557,9 +5690,11 @@ var initGetElementConfigTool = (reg) => {
|
|
|
5557
5690
|
|
|
5558
5691
|
// src/mcp/canvas-mcp.ts
|
|
5559
5692
|
var initCanvasMcp = (reg) => {
|
|
5693
|
+
import_editor_props11.Schema.setDynamicTagNamesResolver(getDynamicTagNamesByCategories);
|
|
5560
5694
|
initWidgetsSchemaResource(reg);
|
|
5561
5695
|
initAvailableWidgetsResource(reg);
|
|
5562
5696
|
initDocumentStructureResource(reg);
|
|
5697
|
+
initDynamicTagsResource(reg);
|
|
5563
5698
|
initSelectedElementResource(reg);
|
|
5564
5699
|
initEditorStateResource(reg);
|
|
5565
5700
|
initGeneralContextResource(reg);
|
|
@@ -5684,14 +5819,14 @@ Note: The "size" property controls image resolution/loading, not visual size. Se
|
|
|
5684
5819
|
// src/prevent-link-in-link-commands.ts
|
|
5685
5820
|
var import_editor_elements19 = require("@elementor/editor-elements");
|
|
5686
5821
|
var import_editor_notifications3 = require("@elementor/editor-notifications");
|
|
5687
|
-
var
|
|
5822
|
+
var import_editor_v1_adapters22 = require("@elementor/editor-v1-adapters");
|
|
5688
5823
|
var import_i18n4 = require("@wordpress/i18n");
|
|
5689
5824
|
function initLinkInLinkPrevention() {
|
|
5690
|
-
(0,
|
|
5825
|
+
(0, import_editor_v1_adapters22.blockCommand)({
|
|
5691
5826
|
command: "document/elements/paste",
|
|
5692
5827
|
condition: blockLinkInLinkPaste
|
|
5693
5828
|
});
|
|
5694
|
-
(0,
|
|
5829
|
+
(0, import_editor_v1_adapters22.blockCommand)({
|
|
5695
5830
|
command: "document/elements/move",
|
|
5696
5831
|
condition: blockLinkInLinkMove
|
|
5697
5832
|
});
|
|
@@ -5766,12 +5901,12 @@ function shouldBlock(sourceElements, targetElements) {
|
|
|
5766
5901
|
|
|
5767
5902
|
// src/style-commands/paste-style.ts
|
|
5768
5903
|
var import_editor_elements22 = require("@elementor/editor-elements");
|
|
5769
|
-
var
|
|
5770
|
-
var
|
|
5904
|
+
var import_editor_props13 = require("@elementor/editor-props");
|
|
5905
|
+
var import_editor_v1_adapters24 = require("@elementor/editor-v1-adapters");
|
|
5771
5906
|
|
|
5772
5907
|
// src/utils/command-utils.ts
|
|
5773
5908
|
var import_editor_elements20 = require("@elementor/editor-elements");
|
|
5774
|
-
var
|
|
5909
|
+
var import_editor_props12 = require("@elementor/editor-props");
|
|
5775
5910
|
var import_i18n5 = require("@wordpress/i18n");
|
|
5776
5911
|
function hasAtomicWidgets(args) {
|
|
5777
5912
|
const { containers = [args.container] } = args;
|
|
@@ -5789,7 +5924,7 @@ function getClassesProp(container) {
|
|
|
5789
5924
|
return null;
|
|
5790
5925
|
}
|
|
5791
5926
|
const [propKey] = Object.entries(propsSchema).find(
|
|
5792
|
-
([, propType]) => propType.kind === "plain" && propType.key ===
|
|
5927
|
+
([, propType]) => propType.kind === "plain" && propType.key === import_editor_props12.CLASSES_PROP_KEY
|
|
5793
5928
|
) ?? [];
|
|
5794
5929
|
return propKey ?? null;
|
|
5795
5930
|
}
|
|
@@ -5814,9 +5949,9 @@ function getTitleForContainers(containers) {
|
|
|
5814
5949
|
// src/style-commands/undoable-actions/paste-element-style.ts
|
|
5815
5950
|
var import_editor_elements21 = require("@elementor/editor-elements");
|
|
5816
5951
|
var import_editor_styles_repository4 = require("@elementor/editor-styles-repository");
|
|
5817
|
-
var
|
|
5952
|
+
var import_editor_v1_adapters23 = require("@elementor/editor-v1-adapters");
|
|
5818
5953
|
var import_i18n6 = require("@wordpress/i18n");
|
|
5819
|
-
var undoablePasteElementStyle = () => (0,
|
|
5954
|
+
var undoablePasteElementStyle = () => (0, import_editor_v1_adapters23.undoable)(
|
|
5820
5955
|
{
|
|
5821
5956
|
do: ({ containers, newStyle }) => {
|
|
5822
5957
|
return containers.map((container) => {
|
|
@@ -5892,12 +6027,12 @@ var undoablePasteElementStyle = () => (0, import_editor_v1_adapters22.undoable)(
|
|
|
5892
6027
|
// src/style-commands/paste-style.ts
|
|
5893
6028
|
function initPasteStyleCommand() {
|
|
5894
6029
|
const pasteElementStyleCommand = undoablePasteElementStyle();
|
|
5895
|
-
(0,
|
|
6030
|
+
(0, import_editor_v1_adapters24.blockCommand)({
|
|
5896
6031
|
command: "document/elements/paste-style",
|
|
5897
6032
|
condition: hasAtomicWidgets
|
|
5898
6033
|
});
|
|
5899
|
-
(0,
|
|
5900
|
-
(0,
|
|
6034
|
+
(0, import_editor_v1_adapters24.__privateListenTo)(
|
|
6035
|
+
(0, import_editor_v1_adapters24.commandStartEvent)("document/elements/paste-style"),
|
|
5901
6036
|
(e) => pasteStyles(e.args, pasteElementStyleCommand)
|
|
5902
6037
|
);
|
|
5903
6038
|
}
|
|
@@ -5938,8 +6073,8 @@ function pasteClasses(containers, classes) {
|
|
|
5938
6073
|
return;
|
|
5939
6074
|
}
|
|
5940
6075
|
const classesSetting = (0, import_editor_elements22.getElementSetting)(container.id, classesProp);
|
|
5941
|
-
const currentClasses =
|
|
5942
|
-
const newClasses =
|
|
6076
|
+
const currentClasses = import_editor_props13.classesPropTypeUtil.extract(classesSetting) ?? [];
|
|
6077
|
+
const newClasses = import_editor_props13.classesPropTypeUtil.create(Array.from(/* @__PURE__ */ new Set([...classes, ...currentClasses])));
|
|
5943
6078
|
(0, import_editor_elements22.updateElementSettings)({
|
|
5944
6079
|
id: container.id,
|
|
5945
6080
|
props: { [classesProp]: newClasses }
|
|
@@ -5948,14 +6083,14 @@ function pasteClasses(containers, classes) {
|
|
|
5948
6083
|
}
|
|
5949
6084
|
|
|
5950
6085
|
// src/style-commands/reset-style.ts
|
|
5951
|
-
var
|
|
6086
|
+
var import_editor_v1_adapters26 = require("@elementor/editor-v1-adapters");
|
|
5952
6087
|
|
|
5953
6088
|
// src/style-commands/undoable-actions/reset-element-style.ts
|
|
5954
6089
|
var import_editor_elements23 = require("@elementor/editor-elements");
|
|
5955
6090
|
var import_editor_styles_repository5 = require("@elementor/editor-styles-repository");
|
|
5956
|
-
var
|
|
6091
|
+
var import_editor_v1_adapters25 = require("@elementor/editor-v1-adapters");
|
|
5957
6092
|
var import_i18n7 = require("@wordpress/i18n");
|
|
5958
|
-
var undoableResetElementStyle = () => (0,
|
|
6093
|
+
var undoableResetElementStyle = () => (0, import_editor_v1_adapters25.undoable)(
|
|
5959
6094
|
{
|
|
5960
6095
|
do: ({ containers }) => {
|
|
5961
6096
|
return containers.map((container) => {
|
|
@@ -5999,12 +6134,12 @@ var undoableResetElementStyle = () => (0, import_editor_v1_adapters24.undoable)(
|
|
|
5999
6134
|
// src/style-commands/reset-style.ts
|
|
6000
6135
|
function initResetStyleCommand() {
|
|
6001
6136
|
const resetElementStyles = undoableResetElementStyle();
|
|
6002
|
-
(0,
|
|
6137
|
+
(0, import_editor_v1_adapters26.blockCommand)({
|
|
6003
6138
|
command: "document/elements/reset-style",
|
|
6004
6139
|
condition: hasAtomicWidgets
|
|
6005
6140
|
});
|
|
6006
|
-
(0,
|
|
6007
|
-
(0,
|
|
6141
|
+
(0, import_editor_v1_adapters26.__privateListenTo)(
|
|
6142
|
+
(0, import_editor_v1_adapters26.commandStartEvent)("document/elements/reset-style"),
|
|
6008
6143
|
(e) => resetStyles(e.args, resetElementStyles)
|
|
6009
6144
|
);
|
|
6010
6145
|
}
|
|
@@ -6153,9 +6288,9 @@ function getRectClipPath(rect, viewport) {
|
|
|
6153
6288
|
}
|
|
6154
6289
|
|
|
6155
6290
|
// src/hooks/use-canvas-document.ts
|
|
6156
|
-
var
|
|
6291
|
+
var import_editor_v1_adapters27 = require("@elementor/editor-v1-adapters");
|
|
6157
6292
|
function useCanvasDocument() {
|
|
6158
|
-
return (0,
|
|
6293
|
+
return (0, import_editor_v1_adapters27.__privateUseListenTo)((0, import_editor_v1_adapters27.commandEndEvent)("editor/documents/attach-preview"), () => (0, import_editor_v1_adapters27.getCanvasIframeDocument)());
|
|
6159
6294
|
}
|
|
6160
6295
|
|
|
6161
6296
|
// src/hooks/use-escape-on-canvas.ts
|
package/dist/index.mjs
CHANGED
|
@@ -2702,7 +2702,8 @@ function createTemplatedElementView({
|
|
|
2702
2702
|
interaction_id: this.getInteractionId(),
|
|
2703
2703
|
type,
|
|
2704
2704
|
settings,
|
|
2705
|
-
base_styles: baseStylesDictionary
|
|
2705
|
+
base_styles: baseStylesDictionary,
|
|
2706
|
+
...this.getResolverRenderContext?.() ?? {}
|
|
2706
2707
|
};
|
|
2707
2708
|
return renderer.render(templateKey, context);
|
|
2708
2709
|
}).then((html) => {
|
|
@@ -2866,7 +2867,8 @@ function createNestedTemplatedElementView({
|
|
|
2866
2867
|
settings,
|
|
2867
2868
|
base_styles: baseStylesDictionary,
|
|
2868
2869
|
editor_attributes: buildEditorAttributes(model),
|
|
2869
|
-
editor_classes: buildEditorClasses(model)
|
|
2870
|
+
editor_classes: buildEditorClasses(model),
|
|
2871
|
+
...this.getResolverRenderContext?.() ?? {}
|
|
2870
2872
|
};
|
|
2871
2873
|
return renderer.render(templateKey, context);
|
|
2872
2874
|
}).then((html) => {
|
|
@@ -3794,6 +3796,9 @@ function initTabsModelExtensions() {
|
|
|
3794
3796
|
registerModelExtensions("e-tab", tabModelExtensions);
|
|
3795
3797
|
}
|
|
3796
3798
|
|
|
3799
|
+
// src/mcp/canvas-mcp.ts
|
|
3800
|
+
import { Schema as Schema6 } from "@elementor/editor-props";
|
|
3801
|
+
|
|
3797
3802
|
// src/mcp/resources/available-widgets-resource.ts
|
|
3798
3803
|
import { v1ReadyEvent as v1ReadyEvent3 } from "@elementor/editor-v1-adapters";
|
|
3799
3804
|
var AVAILABLE_WIDGETS_URI = "elementor://context/available-widgets";
|
|
@@ -3945,6 +3950,107 @@ function extractElementData(element) {
|
|
|
3945
3950
|
return result;
|
|
3946
3951
|
}
|
|
3947
3952
|
|
|
3953
|
+
// src/mcp/resources/dynamic-tags-resource.ts
|
|
3954
|
+
import { Schema as Schema2 } from "@elementor/editor-props";
|
|
3955
|
+
|
|
3956
|
+
// src/mcp/utils/resolve-dynamic-tag.ts
|
|
3957
|
+
import { getElementorConfig } from "@elementor/editor-v1-adapters";
|
|
3958
|
+
var DYNAMIC_PROP_TYPE_KEY = "dynamic";
|
|
3959
|
+
var OMITTED_DYNAMIC_SETTING_KEYS = ["fallback"];
|
|
3960
|
+
var getAtomicDynamicTags = () => {
|
|
3961
|
+
const config = getElementorConfig();
|
|
3962
|
+
return config.atomicDynamicTags?.tags ?? {};
|
|
3963
|
+
};
|
|
3964
|
+
var getDynamicTagNamesByCategories = (categories) => {
|
|
3965
|
+
if (!categories.length) {
|
|
3966
|
+
return [];
|
|
3967
|
+
}
|
|
3968
|
+
const wanted = new Set(categories);
|
|
3969
|
+
return Object.values(getAtomicDynamicTags()).filter((tag) => tag.categories?.some((category) => wanted.has(category))).map((tag) => tag.name);
|
|
3970
|
+
};
|
|
3971
|
+
var dynamicTagLLMResolver = (value) => {
|
|
3972
|
+
const input = value ?? {};
|
|
3973
|
+
const tag = input.name ? getAtomicDynamicTags()[input.name] : void 0;
|
|
3974
|
+
if (!tag) {
|
|
3975
|
+
return {
|
|
3976
|
+
$$type: DYNAMIC_PROP_TYPE_KEY,
|
|
3977
|
+
value: { name: input.name ?? "", group: "", settings: {} }
|
|
3978
|
+
};
|
|
3979
|
+
}
|
|
3980
|
+
return {
|
|
3981
|
+
$$type: DYNAMIC_PROP_TYPE_KEY,
|
|
3982
|
+
value: {
|
|
3983
|
+
name: tag.name,
|
|
3984
|
+
group: tag.group,
|
|
3985
|
+
settings: buildStrictSettings(tag.props_schema ?? {}, input.settings ?? {})
|
|
3986
|
+
}
|
|
3987
|
+
};
|
|
3988
|
+
};
|
|
3989
|
+
var buildStrictSettings = (schema2, provided) => {
|
|
3990
|
+
const settings = {};
|
|
3991
|
+
for (const [key, propType] of Object.entries(schema2)) {
|
|
3992
|
+
if (OMITTED_DYNAMIC_SETTING_KEYS.includes(key)) {
|
|
3993
|
+
continue;
|
|
3994
|
+
}
|
|
3995
|
+
const resolved = provided[key] !== void 0 ? wrapSettingValue(provided[key], propType) : defaultSettingValue(propType);
|
|
3996
|
+
if (resolved !== void 0 && resolved !== null) {
|
|
3997
|
+
settings[key] = resolved;
|
|
3998
|
+
}
|
|
3999
|
+
}
|
|
4000
|
+
return settings;
|
|
4001
|
+
};
|
|
4002
|
+
var wrapSettingValue = (raw, propType) => {
|
|
4003
|
+
if (raw !== null && typeof raw === "object") {
|
|
4004
|
+
return raw;
|
|
4005
|
+
}
|
|
4006
|
+
return propType.key ? { $$type: propType.key, value: raw } : raw;
|
|
4007
|
+
};
|
|
4008
|
+
var defaultSettingValue = (propType) => {
|
|
4009
|
+
if (propType.initial_value !== null && propType.initial_value !== void 0) {
|
|
4010
|
+
return propType.initial_value;
|
|
4011
|
+
}
|
|
4012
|
+
if (propType.default !== null && propType.default !== void 0) {
|
|
4013
|
+
return wrapSettingValue(propType.default, propType);
|
|
4014
|
+
}
|
|
4015
|
+
return void 0;
|
|
4016
|
+
};
|
|
4017
|
+
|
|
4018
|
+
// src/mcp/resources/dynamic-tags-resource.ts
|
|
4019
|
+
var DYNAMIC_TAGS_URI = "elementor://dynamic-tags";
|
|
4020
|
+
var settingsSchema = (propsSchema) => {
|
|
4021
|
+
return Object.fromEntries(
|
|
4022
|
+
Object.entries(propsSchema ?? {}).filter(([key]) => !OMITTED_DYNAMIC_SETTING_KEYS.includes(key)).map(([key, propType]) => [key, Schema2.propTypeToJsonSchema(propType)])
|
|
4023
|
+
);
|
|
4024
|
+
};
|
|
4025
|
+
var buildDynamicTagsList = () => {
|
|
4026
|
+
return Object.values(getAtomicDynamicTags()).map((tag) => ({
|
|
4027
|
+
name: tag.name,
|
|
4028
|
+
label: tag.label,
|
|
4029
|
+
categories: tag.categories,
|
|
4030
|
+
settings: settingsSchema(tag.props_schema)
|
|
4031
|
+
}));
|
|
4032
|
+
};
|
|
4033
|
+
var initDynamicTagsResource = (reg) => {
|
|
4034
|
+
const { resource } = reg;
|
|
4035
|
+
resource(
|
|
4036
|
+
"dynamic-tags",
|
|
4037
|
+
DYNAMIC_TAGS_URI,
|
|
4038
|
+
{
|
|
4039
|
+
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.`,
|
|
4040
|
+
mimeType: "application/json"
|
|
4041
|
+
},
|
|
4042
|
+
async (uri) => ({
|
|
4043
|
+
contents: [
|
|
4044
|
+
{
|
|
4045
|
+
uri: uri.href,
|
|
4046
|
+
mimeType: "application/json",
|
|
4047
|
+
text: JSON.stringify(buildDynamicTagsList())
|
|
4048
|
+
}
|
|
4049
|
+
]
|
|
4050
|
+
})
|
|
4051
|
+
);
|
|
4052
|
+
};
|
|
4053
|
+
|
|
3948
4054
|
// src/mcp/resources/editor-state-resource.ts
|
|
3949
4055
|
import { __privateListenTo as listenTo2, commandEndEvent as commandEndEvent5 } from "@elementor/editor-v1-adapters";
|
|
3950
4056
|
var CURRENTLY_VIEWED_SCREEN = "The user is currently viewing the Elementor editor";
|
|
@@ -4289,7 +4395,7 @@ import {
|
|
|
4289
4395
|
updateElementSettings,
|
|
4290
4396
|
updateElementStyle
|
|
4291
4397
|
} from "@elementor/editor-elements";
|
|
4292
|
-
import { getPropSchemaFromCache, Schema as
|
|
4398
|
+
import { getPropSchemaFromCache, Schema as Schema3 } from "@elementor/editor-props";
|
|
4293
4399
|
import { getStylesSchema as getStylesSchema3, getVariantByMeta } from "@elementor/editor-styles";
|
|
4294
4400
|
import { __privateRunCommandSync as runCommandSync2 } from "@elementor/editor-v1-adapters";
|
|
4295
4401
|
|
|
@@ -4314,9 +4420,12 @@ var LOCAL_STYLE_META = {
|
|
|
4314
4420
|
};
|
|
4315
4421
|
function resolvePropValue(value, forceKey) {
|
|
4316
4422
|
const Utils = window.elementorV2.editorVariables.Utils;
|
|
4317
|
-
return
|
|
4423
|
+
return Schema3.adjustLlmPropValueSchema(value, {
|
|
4318
4424
|
forceKey,
|
|
4319
|
-
transformers:
|
|
4425
|
+
transformers: {
|
|
4426
|
+
...Utils.globalVariablesLLMResolvers,
|
|
4427
|
+
[DYNAMIC_PROP_TYPE_KEY]: dynamicTagLLMResolver
|
|
4428
|
+
}
|
|
4320
4429
|
});
|
|
4321
4430
|
}
|
|
4322
4431
|
var doUpdateElementProperty = (params) => {
|
|
@@ -4419,7 +4528,7 @@ var doUpdateElementProperty = (params) => {
|
|
|
4419
4528
|
}
|
|
4420
4529
|
const propKey = elementPropSchema[propertyName].key;
|
|
4421
4530
|
const value = resolvePropValue(propertyValue, propKey);
|
|
4422
|
-
const { valid, jsonSchema } =
|
|
4531
|
+
const { valid, jsonSchema } = Schema3.validatePropValue(elementPropSchema[propertyName], propertyValue);
|
|
4423
4532
|
if (!valid) {
|
|
4424
4533
|
throw new Error(
|
|
4425
4534
|
`Invalid PropValue for elementId: ${elementId}. PropKey: ${propKey}, PropValue: ${JSON.stringify(
|
|
@@ -4440,7 +4549,7 @@ Expected Schema: ${jsonSchema}`
|
|
|
4440
4549
|
|
|
4441
4550
|
// src/mcp/utils/validate-input.ts
|
|
4442
4551
|
import { getWidgetsCache as getWidgetsCache7 } from "@elementor/editor-elements";
|
|
4443
|
-
import { Schema as
|
|
4552
|
+
import { Schema as Schema4 } from "@elementor/editor-props";
|
|
4444
4553
|
import { getStylesSchema as getStylesSchema4 } from "@elementor/editor-styles";
|
|
4445
4554
|
var _widgetsSchema = null;
|
|
4446
4555
|
var validateInput = {
|
|
@@ -4474,10 +4583,10 @@ var validateInput = {
|
|
|
4474
4583
|
if (!propSchema) {
|
|
4475
4584
|
errors.push(`Property "${propName}" is not defined in the schema.`);
|
|
4476
4585
|
hasInvalidKey = true;
|
|
4477
|
-
} else if (!
|
|
4586
|
+
} else if (!Schema4.isPropKeyConfigurable(propName, propSchema)) {
|
|
4478
4587
|
errors.push(`Property "${propName}" is not configurable.`);
|
|
4479
4588
|
} else {
|
|
4480
|
-
const { valid } =
|
|
4589
|
+
const { valid } = Schema4.validatePropValue(propSchema, propValue);
|
|
4481
4590
|
if (!valid) {
|
|
4482
4591
|
errors.push(
|
|
4483
4592
|
`Invalid property "${propName}". Validate input with resource [${STYLE_SCHEMA_URI.replace(
|
|
@@ -4861,6 +4970,13 @@ Some elements have internal tree structures (nesting). When using these elements
|
|
|
4861
4970
|
- NO LINKS in configuration
|
|
4862
4971
|
- Retry on errors up to 10x
|
|
4863
4972
|
|
|
4973
|
+
# DYNAMIC TAGS
|
|
4974
|
+
- 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\`).
|
|
4975
|
+
- 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.
|
|
4976
|
+
- Provide at that node: \`{ "$$type": "dynamic", "value": { "name": "<allowed tag>", "settings": { ... } } }\`
|
|
4977
|
+
- Example (image): \`{ "$$type": "image", "value": { "src": { "$$type": "dynamic", "value": { "name": "<image tag>", "settings": { ... } } } } }\`
|
|
4978
|
+
- Do NOT send \`group\` (it is resolved automatically). Populate \`settings\` strictly per the tag's schema; use \`{}\` only when it has none.
|
|
4979
|
+
|
|
4864
4980
|
Note about configuration ids: These names are visible to the end-user, make sure they make sense, related and relevant.
|
|
4865
4981
|
|
|
4866
4982
|
# DESIGN PHILOSOPHY: CONTEXT-DRIVEN CREATIVITY
|
|
@@ -5082,7 +5198,8 @@ var initBuildCompositionsTool = (reg) => {
|
|
|
5082
5198
|
{ description: "Global Classes", uri: "elementor://global-classes" },
|
|
5083
5199
|
{ description: "Global Variables", uri: "elementor://global-variables" },
|
|
5084
5200
|
{ description: "Styles best practices", uri: BEST_PRACTICES_URI },
|
|
5085
|
-
{ description: "Available widgets for this tool", uri: AVAILABLE_WIDGETS_URI_V4 }
|
|
5201
|
+
{ description: "Available widgets for this tool", uri: AVAILABLE_WIDGETS_URI_V4 },
|
|
5202
|
+
{ description: "Dynamic tags catalog", uri: DYNAMIC_TAGS_URI }
|
|
5086
5203
|
],
|
|
5087
5204
|
outputSchema,
|
|
5088
5205
|
handler: async (rawParams) => {
|
|
@@ -5247,6 +5364,21 @@ For styleProperties, use the style schema provided, as it also uses the PropType
|
|
|
5247
5364
|
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
5365
|
|
|
5249
5366
|
Use the EXACT "PROP-TYPE" Schema given, and ALWAYS include the "key" property from the original configuration for every property you are changing.
|
|
5367
|
+
|
|
5368
|
+
# Dynamic tags
|
|
5369
|
+
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.
|
|
5370
|
+
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.
|
|
5371
|
+
1. Read the [${DYNAMIC_TAGS_URI}] resource for each allowed tag's settings schema.
|
|
5372
|
+
2. Provide, at that node:
|
|
5373
|
+
{
|
|
5374
|
+
"$$type": "dynamic",
|
|
5375
|
+
"value": {
|
|
5376
|
+
"name": "<allowed tag name>",
|
|
5377
|
+
"settings": { /* strictly per the tag's settings schema */ }
|
|
5378
|
+
}
|
|
5379
|
+
}
|
|
5380
|
+
Image example: { "$$type": "image", "value": { "src": { "$$type": "dynamic", "value": { "name": "<image tag>", "settings": { ... } } } } }
|
|
5381
|
+
Do NOT send "group" (it is resolved automatically). Use { "settings": {} } only when the tag has no settings.
|
|
5250
5382
|
`);
|
|
5251
5383
|
configureElementToolPrompt.parameter("elementId", "The ID of the element to configure. MANDATORY.");
|
|
5252
5384
|
configureElementToolPrompt.parameter(
|
|
@@ -5354,7 +5486,8 @@ var initConfigureElementTool = (reg) => {
|
|
|
5354
5486
|
requiredResources: [
|
|
5355
5487
|
{ description: "Widgets schema", uri: WIDGET_SCHEMA_URI },
|
|
5356
5488
|
{ description: "Styles schema", uri: STYLE_SCHEMA_URI },
|
|
5357
|
-
{ description: "Configure element guide", uri: CONFIGURE_ELEMENT_GUIDE_URI }
|
|
5489
|
+
{ description: "Configure element guide", uri: CONFIGURE_ELEMENT_GUIDE_URI },
|
|
5490
|
+
{ description: "Dynamic tags catalog", uri: DYNAMIC_TAGS_URI }
|
|
5358
5491
|
],
|
|
5359
5492
|
handler: ({ elementId, propertiesToChange, elementType, stylePropertiesToChange }) => {
|
|
5360
5493
|
const widgetData = getWidgetsCache10()?.[elementType];
|
|
@@ -5450,7 +5583,7 @@ Check the styles schema at the resource [${STYLE_SCHEMA_URI.replace(
|
|
|
5450
5583
|
|
|
5451
5584
|
// src/mcp/tools/get-element-config/tool.ts
|
|
5452
5585
|
import { getContainer as getContainer6, getElementStyles as getElementStyles2, getWidgetsCache as getWidgetsCache11 } from "@elementor/editor-elements";
|
|
5453
|
-
import { Schema as
|
|
5586
|
+
import { Schema as Schema5 } from "@elementor/editor-props";
|
|
5454
5587
|
import { z as z3 } from "@elementor/schema";
|
|
5455
5588
|
var schema = {
|
|
5456
5589
|
elementId: z3.string()
|
|
@@ -5507,7 +5640,7 @@ var initGetElementConfigTool = (reg) => {
|
|
|
5507
5640
|
}
|
|
5508
5641
|
const propValues = {};
|
|
5509
5642
|
const stylePropValues = {};
|
|
5510
|
-
|
|
5643
|
+
Schema5.configurableKeys(propSchema).forEach((key) => {
|
|
5511
5644
|
propValues[key] = structuredClone(elementRawSettings.get(key));
|
|
5512
5645
|
});
|
|
5513
5646
|
const elementStyles = getElementStyles2(elementId) || {};
|
|
@@ -5543,9 +5676,11 @@ var initGetElementConfigTool = (reg) => {
|
|
|
5543
5676
|
|
|
5544
5677
|
// src/mcp/canvas-mcp.ts
|
|
5545
5678
|
var initCanvasMcp = (reg) => {
|
|
5679
|
+
Schema6.setDynamicTagNamesResolver(getDynamicTagNamesByCategories);
|
|
5546
5680
|
initWidgetsSchemaResource(reg);
|
|
5547
5681
|
initAvailableWidgetsResource(reg);
|
|
5548
5682
|
initDocumentStructureResource(reg);
|
|
5683
|
+
initDynamicTagsResource(reg);
|
|
5549
5684
|
initSelectedElementResource(reg);
|
|
5550
5685
|
initEditorStateResource(reg);
|
|
5551
5686
|
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-
|
|
4
|
+
"version": "4.2.0-926",
|
|
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-
|
|
40
|
+
"@elementor/editor": "4.2.0-926",
|
|
41
41
|
"dompurify": "^3.2.6",
|
|
42
|
-
"@elementor/editor-controls": "4.2.0-
|
|
43
|
-
"@elementor/editor-documents": "4.2.0-
|
|
44
|
-
"@elementor/editor-elements": "4.2.0-
|
|
45
|
-
"@elementor/editor-interactions": "4.2.0-
|
|
46
|
-
"@elementor/editor-mcp": "4.2.0-
|
|
47
|
-
"@elementor/editor-notifications": "4.2.0-
|
|
48
|
-
"@elementor/editor-props": "4.2.0-
|
|
49
|
-
"@elementor/editor-responsive": "4.2.0-
|
|
50
|
-
"@elementor/editor-styles": "4.2.0-
|
|
51
|
-
"@elementor/editor-styles-repository": "4.2.0-
|
|
52
|
-
"@elementor/editor-ui": "4.2.0-
|
|
53
|
-
"@elementor/editor-v1-adapters": "4.2.0-
|
|
54
|
-
"@elementor/schema": "4.2.0-
|
|
55
|
-
"@elementor/twing": "4.2.0-
|
|
42
|
+
"@elementor/editor-controls": "4.2.0-926",
|
|
43
|
+
"@elementor/editor-documents": "4.2.0-926",
|
|
44
|
+
"@elementor/editor-elements": "4.2.0-926",
|
|
45
|
+
"@elementor/editor-interactions": "4.2.0-926",
|
|
46
|
+
"@elementor/editor-mcp": "4.2.0-926",
|
|
47
|
+
"@elementor/editor-notifications": "4.2.0-926",
|
|
48
|
+
"@elementor/editor-props": "4.2.0-926",
|
|
49
|
+
"@elementor/editor-responsive": "4.2.0-926",
|
|
50
|
+
"@elementor/editor-styles": "4.2.0-926",
|
|
51
|
+
"@elementor/editor-styles-repository": "4.2.0-926",
|
|
52
|
+
"@elementor/editor-ui": "4.2.0-926",
|
|
53
|
+
"@elementor/editor-v1-adapters": "4.2.0-926",
|
|
54
|
+
"@elementor/schema": "4.2.0-926",
|
|
55
|
+
"@elementor/twing": "4.2.0-926",
|
|
56
56
|
"@elementor/ui": "1.37.5",
|
|
57
|
-
"@elementor/utils": "4.2.0-
|
|
58
|
-
"@elementor/wp-media": "4.2.0-
|
|
57
|
+
"@elementor/utils": "4.2.0-926",
|
|
58
|
+
"@elementor/wp-media": "4.2.0-926",
|
|
59
59
|
"@floating-ui/react": "^0.27.5",
|
|
60
60
|
"@wordpress/i18n": "^5.13.0"
|
|
61
61
|
},
|
|
@@ -200,6 +200,7 @@ export function createNestedTemplatedElementView( {
|
|
|
200
200
|
base_styles: baseStylesDictionary,
|
|
201
201
|
editor_attributes: buildEditorAttributes( model ),
|
|
202
202
|
editor_classes: buildEditorClasses( model ),
|
|
203
|
+
...( this.getResolverRenderContext?.() ?? {} ),
|
|
203
204
|
};
|
|
204
205
|
|
|
205
206
|
return renderer.render( templateKey, context );
|
package/src/mcp/canvas-mcp.ts
CHANGED
|
@@ -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:
|
|
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
|
+
};
|