@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 +182 -49
- package/dist/index.mjs +144 -11
- package/package.json +18 -18
- 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
|
@@ -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/
|
|
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,
|
|
4005
|
-
[(0,
|
|
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
|
|
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,
|
|
4244
|
+
(0, import_editor_v1_adapters19.__privateListenTo)(
|
|
4141
4245
|
[
|
|
4142
|
-
(0,
|
|
4143
|
-
(0,
|
|
4144
|
-
(0,
|
|
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
|
|
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,
|
|
4288
|
+
(0, import_editor_v1_adapters20.__privateListenTo)(
|
|
4185
4289
|
[
|
|
4186
|
-
(0,
|
|
4187
|
-
(0,
|
|
4188
|
-
(0,
|
|
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
|
|
4410
|
+
var import_editor_props8 = require("@elementor/editor-props");
|
|
4307
4411
|
var import_editor_styles5 = require("@elementor/editor-styles");
|
|
4308
|
-
var
|
|
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
|
|
4435
|
+
return import_editor_props8.Schema.adjustLlmPropValueSchema(value, {
|
|
4332
4436
|
forceKey,
|
|
4333
|
-
transformers:
|
|
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,
|
|
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 } =
|
|
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,
|
|
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
|
|
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 (!
|
|
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 } =
|
|
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
|
|
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
|
-
|
|
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
|
|
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,
|
|
5823
|
+
(0, import_editor_v1_adapters22.blockCommand)({
|
|
5691
5824
|
command: "document/elements/paste",
|
|
5692
5825
|
condition: blockLinkInLinkPaste
|
|
5693
5826
|
});
|
|
5694
|
-
(0,
|
|
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
|
|
5770
|
-
var
|
|
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
|
|
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 ===
|
|
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
|
|
5950
|
+
var import_editor_v1_adapters23 = require("@elementor/editor-v1-adapters");
|
|
5818
5951
|
var import_i18n6 = require("@wordpress/i18n");
|
|
5819
|
-
var undoablePasteElementStyle = () => (0,
|
|
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,
|
|
6028
|
+
(0, import_editor_v1_adapters24.blockCommand)({
|
|
5896
6029
|
command: "document/elements/paste-style",
|
|
5897
6030
|
condition: hasAtomicWidgets
|
|
5898
6031
|
});
|
|
5899
|
-
(0,
|
|
5900
|
-
(0,
|
|
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 =
|
|
5942
|
-
const newClasses =
|
|
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
|
|
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
|
|
6089
|
+
var import_editor_v1_adapters25 = require("@elementor/editor-v1-adapters");
|
|
5957
6090
|
var import_i18n7 = require("@wordpress/i18n");
|
|
5958
|
-
var undoableResetElementStyle = () => (0,
|
|
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,
|
|
6135
|
+
(0, import_editor_v1_adapters26.blockCommand)({
|
|
6003
6136
|
command: "document/elements/reset-style",
|
|
6004
6137
|
condition: hasAtomicWidgets
|
|
6005
6138
|
});
|
|
6006
|
-
(0,
|
|
6007
|
-
(0,
|
|
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
|
|
6289
|
+
var import_editor_v1_adapters27 = require("@elementor/editor-v1-adapters");
|
|
6157
6290
|
function useCanvasDocument() {
|
|
6158
|
-
return (0,
|
|
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
|
|
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
|
|
4421
|
+
return Schema3.adjustLlmPropValueSchema(value, {
|
|
4318
4422
|
forceKey,
|
|
4319
|
-
transformers:
|
|
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 } =
|
|
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
|
|
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 (!
|
|
4584
|
+
} else if (!Schema4.isPropKeyConfigurable(propName, propSchema)) {
|
|
4478
4585
|
errors.push(`Property "${propName}" is not configurable.`);
|
|
4479
4586
|
} else {
|
|
4480
|
-
const { valid } =
|
|
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
|
|
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
|
-
|
|
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-
|
|
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-
|
|
40
|
+
"@elementor/editor": "4.2.0-925",
|
|
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-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-
|
|
58
|
-
"@elementor/wp-media": "4.2.0-
|
|
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
|
},
|
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
|
+
};
|