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