@elementor/editor-canvas 4.3.0-1004 → 4.3.0-1005
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 +30 -10
- package/dist/index.mjs +30 -10
- package/package.json +20 -20
- package/src/mcp/tools/configure-element/__tests__/tool.test.ts +130 -0
- package/src/mcp/tools/configure-element/prompt.ts +2 -3
- package/src/mcp/tools/configure-element/schema.ts +6 -0
- package/src/mcp/tools/configure-element/tool.ts +11 -1
- package/src/mcp/utils/__tests__/do-update-element-property.test.ts +27 -1
- package/src/mcp/utils/do-update-element-property.ts +18 -6
package/dist/index.js
CHANGED
|
@@ -4725,6 +4725,20 @@ var LOCAL_STYLE_META = {
|
|
|
4725
4725
|
breakpoint: "desktop",
|
|
4726
4726
|
state: null
|
|
4727
4727
|
};
|
|
4728
|
+
var UnsupportedPropertyError = class extends Error {
|
|
4729
|
+
elementType;
|
|
4730
|
+
propertyName;
|
|
4731
|
+
constructor(elementType, propertyName, availableProperties) {
|
|
4732
|
+
super(
|
|
4733
|
+
`Property "${propertyName}" does not exist on element type "${elementType}". Available properties are: ${availableProperties.join(
|
|
4734
|
+
", "
|
|
4735
|
+
)}`
|
|
4736
|
+
);
|
|
4737
|
+
this.name = "UnsupportedPropertyError";
|
|
4738
|
+
this.elementType = elementType;
|
|
4739
|
+
this.propertyName = propertyName;
|
|
4740
|
+
}
|
|
4741
|
+
};
|
|
4728
4742
|
function resolvePropValue(value, forceKey) {
|
|
4729
4743
|
const Utils = window.elementorV2.editorVariables.Utils;
|
|
4730
4744
|
return import_editor_props6.Schema.adjustLlmPropValueSchema(value, {
|
|
@@ -4830,12 +4844,7 @@ var doUpdateElementProperty = (params) => {
|
|
|
4830
4844
|
throw new Error(`No prop schema found for element type: ${elementType}`);
|
|
4831
4845
|
}
|
|
4832
4846
|
if (!elementPropSchema[propertyName]) {
|
|
4833
|
-
|
|
4834
|
-
throw new Error(
|
|
4835
|
-
`Property "${propertyName}" does not exist on element type "${elementType}". Available properties are: ${propertyNames.join(
|
|
4836
|
-
", "
|
|
4837
|
-
)}`
|
|
4838
|
-
);
|
|
4847
|
+
throw new UnsupportedPropertyError(elementType, propertyName, Object.keys(elementPropSchema));
|
|
4839
4848
|
}
|
|
4840
4849
|
const propKey = elementPropSchema[propertyName].key;
|
|
4841
4850
|
const value = resolvePropValue(propertyValue, propKey);
|
|
@@ -4965,12 +4974,12 @@ Do NOT send "group" (it is resolved automatically). Use { "settings": {} } only
|
|
|
4965
4974
|
V4 only: If MCP fails, give manual steps using V4 UI.
|
|
4966
4975
|
|
|
4967
4976
|
V4 Editor structure:
|
|
4968
|
-
Panel tabs: General (\u2192 Settings section: ID, Tag, Link), Style, Interactions.
|
|
4977
|
+
Panel tabs: General (\u2192 Settings section: ID, Tag, and Link where the widget supports it), Style, Interactions.
|
|
4969
4978
|
NO Advanced tab. Never mention Advanced tab.
|
|
4979
|
+
Note: \`link\` is valid only when the element's PropType schema (which you must already have) includes a \`link\` property. Sending \`link\` to a widget whose schema lacks it is skipped and reported in the response \`warnings\` (other changes still apply) and the link is lost.
|
|
4970
4980
|
`);
|
|
4971
4981
|
return configureElementToolPrompt.prompt();
|
|
4972
4982
|
};
|
|
4973
|
-
var CONFIGURE_ELEMENT_GUIDE_TEXT = generatePrompt();
|
|
4974
4983
|
|
|
4975
4984
|
// src/mcp/tools/configure-element/schema.ts
|
|
4976
4985
|
var import_schema2 = require("@elementor/schema");
|
|
@@ -4994,7 +5003,10 @@ var inputSchema = {
|
|
|
4994
5003
|
var outputSchema = {
|
|
4995
5004
|
success: import_schema2.z.boolean().describe(
|
|
4996
5005
|
"Whether the configuration change was successful, only if propertyName and propertyValue are provided"
|
|
4997
|
-
)
|
|
5006
|
+
),
|
|
5007
|
+
warnings: import_schema2.z.string().describe(
|
|
5008
|
+
'Non-fatal notices. Present when some props were skipped because they are not in the element schema (e.g. a "link" on a widget with no link prop). Other changes were still applied.'
|
|
5009
|
+
).optional()
|
|
4998
5010
|
};
|
|
4999
5011
|
|
|
5000
5012
|
// src/mcp/tools/configure-element/tool.ts
|
|
@@ -5044,6 +5056,7 @@ var initConfigureElementTool = (reg) => {
|
|
|
5044
5056
|
}
|
|
5045
5057
|
const propertiesToUpdate = resolveCanonicalPropKeys(elementType, propertiesToChange);
|
|
5046
5058
|
const toUpdate = Object.entries(propertiesToUpdate);
|
|
5059
|
+
const skippedProps = [];
|
|
5047
5060
|
for (const [propertyName, propertyValue] of toUpdate) {
|
|
5048
5061
|
if (!import_editor_props7.Schema.isPropKeyConfigurable(propertyName)) {
|
|
5049
5062
|
throw new Error(`Not allowed to update ${propertyName}`);
|
|
@@ -5056,6 +5069,10 @@ var initConfigureElementTool = (reg) => {
|
|
|
5056
5069
|
propertyValue
|
|
5057
5070
|
});
|
|
5058
5071
|
} catch (error) {
|
|
5072
|
+
if (error instanceof UnsupportedPropertyError) {
|
|
5073
|
+
skippedProps.push(error.propertyName);
|
|
5074
|
+
continue;
|
|
5075
|
+
}
|
|
5059
5076
|
const errorMessage = createUpdateErrorMessage({
|
|
5060
5077
|
propertyName,
|
|
5061
5078
|
elementId,
|
|
@@ -5068,7 +5085,10 @@ var initConfigureElementTool = (reg) => {
|
|
|
5068
5085
|
}
|
|
5069
5086
|
await applyStyleFromCss({ elementId, elementType, style });
|
|
5070
5087
|
return {
|
|
5071
|
-
success: true
|
|
5088
|
+
success: true,
|
|
5089
|
+
warnings: skippedProps.length ? `Skipped unsupported props (not in the "${elementType}" schema; other changes were applied): ${skippedProps.join(
|
|
5090
|
+
", "
|
|
5091
|
+
)}.` : void 0
|
|
5072
5092
|
};
|
|
5073
5093
|
}
|
|
5074
5094
|
});
|
package/dist/index.mjs
CHANGED
|
@@ -4697,6 +4697,20 @@ var LOCAL_STYLE_META = {
|
|
|
4697
4697
|
breakpoint: "desktop",
|
|
4698
4698
|
state: null
|
|
4699
4699
|
};
|
|
4700
|
+
var UnsupportedPropertyError = class extends Error {
|
|
4701
|
+
elementType;
|
|
4702
|
+
propertyName;
|
|
4703
|
+
constructor(elementType, propertyName, availableProperties) {
|
|
4704
|
+
super(
|
|
4705
|
+
`Property "${propertyName}" does not exist on element type "${elementType}". Available properties are: ${availableProperties.join(
|
|
4706
|
+
", "
|
|
4707
|
+
)}`
|
|
4708
|
+
);
|
|
4709
|
+
this.name = "UnsupportedPropertyError";
|
|
4710
|
+
this.elementType = elementType;
|
|
4711
|
+
this.propertyName = propertyName;
|
|
4712
|
+
}
|
|
4713
|
+
};
|
|
4700
4714
|
function resolvePropValue(value, forceKey) {
|
|
4701
4715
|
const Utils = window.elementorV2.editorVariables.Utils;
|
|
4702
4716
|
return Schema.adjustLlmPropValueSchema(value, {
|
|
@@ -4802,12 +4816,7 @@ var doUpdateElementProperty = (params) => {
|
|
|
4802
4816
|
throw new Error(`No prop schema found for element type: ${elementType}`);
|
|
4803
4817
|
}
|
|
4804
4818
|
if (!elementPropSchema[propertyName]) {
|
|
4805
|
-
|
|
4806
|
-
throw new Error(
|
|
4807
|
-
`Property "${propertyName}" does not exist on element type "${elementType}". Available properties are: ${propertyNames.join(
|
|
4808
|
-
", "
|
|
4809
|
-
)}`
|
|
4810
|
-
);
|
|
4819
|
+
throw new UnsupportedPropertyError(elementType, propertyName, Object.keys(elementPropSchema));
|
|
4811
4820
|
}
|
|
4812
4821
|
const propKey = elementPropSchema[propertyName].key;
|
|
4813
4822
|
const value = resolvePropValue(propertyValue, propKey);
|
|
@@ -4937,12 +4946,12 @@ Do NOT send "group" (it is resolved automatically). Use { "settings": {} } only
|
|
|
4937
4946
|
V4 only: If MCP fails, give manual steps using V4 UI.
|
|
4938
4947
|
|
|
4939
4948
|
V4 Editor structure:
|
|
4940
|
-
Panel tabs: General (\u2192 Settings section: ID, Tag, Link), Style, Interactions.
|
|
4949
|
+
Panel tabs: General (\u2192 Settings section: ID, Tag, and Link where the widget supports it), Style, Interactions.
|
|
4941
4950
|
NO Advanced tab. Never mention Advanced tab.
|
|
4951
|
+
Note: \`link\` is valid only when the element's PropType schema (which you must already have) includes a \`link\` property. Sending \`link\` to a widget whose schema lacks it is skipped and reported in the response \`warnings\` (other changes still apply) and the link is lost.
|
|
4942
4952
|
`);
|
|
4943
4953
|
return configureElementToolPrompt.prompt();
|
|
4944
4954
|
};
|
|
4945
|
-
var CONFIGURE_ELEMENT_GUIDE_TEXT = generatePrompt();
|
|
4946
4955
|
|
|
4947
4956
|
// src/mcp/tools/configure-element/schema.ts
|
|
4948
4957
|
import { z as z2 } from "@elementor/schema";
|
|
@@ -4966,7 +4975,10 @@ var inputSchema = {
|
|
|
4966
4975
|
var outputSchema = {
|
|
4967
4976
|
success: z2.boolean().describe(
|
|
4968
4977
|
"Whether the configuration change was successful, only if propertyName and propertyValue are provided"
|
|
4969
|
-
)
|
|
4978
|
+
),
|
|
4979
|
+
warnings: z2.string().describe(
|
|
4980
|
+
'Non-fatal notices. Present when some props were skipped because they are not in the element schema (e.g. a "link" on a widget with no link prop). Other changes were still applied.'
|
|
4981
|
+
).optional()
|
|
4970
4982
|
};
|
|
4971
4983
|
|
|
4972
4984
|
// src/mcp/tools/configure-element/tool.ts
|
|
@@ -5016,6 +5028,7 @@ var initConfigureElementTool = (reg) => {
|
|
|
5016
5028
|
}
|
|
5017
5029
|
const propertiesToUpdate = resolveCanonicalPropKeys(elementType, propertiesToChange);
|
|
5018
5030
|
const toUpdate = Object.entries(propertiesToUpdate);
|
|
5031
|
+
const skippedProps = [];
|
|
5019
5032
|
for (const [propertyName, propertyValue] of toUpdate) {
|
|
5020
5033
|
if (!Schema2.isPropKeyConfigurable(propertyName)) {
|
|
5021
5034
|
throw new Error(`Not allowed to update ${propertyName}`);
|
|
@@ -5028,6 +5041,10 @@ var initConfigureElementTool = (reg) => {
|
|
|
5028
5041
|
propertyValue
|
|
5029
5042
|
});
|
|
5030
5043
|
} catch (error) {
|
|
5044
|
+
if (error instanceof UnsupportedPropertyError) {
|
|
5045
|
+
skippedProps.push(error.propertyName);
|
|
5046
|
+
continue;
|
|
5047
|
+
}
|
|
5031
5048
|
const errorMessage = createUpdateErrorMessage({
|
|
5032
5049
|
propertyName,
|
|
5033
5050
|
elementId,
|
|
@@ -5040,7 +5057,10 @@ var initConfigureElementTool = (reg) => {
|
|
|
5040
5057
|
}
|
|
5041
5058
|
await applyStyleFromCss({ elementId, elementType, style });
|
|
5042
5059
|
return {
|
|
5043
|
-
success: true
|
|
5060
|
+
success: true,
|
|
5061
|
+
warnings: skippedProps.length ? `Skipped unsupported props (not in the "${elementType}" schema; other changes were applied): ${skippedProps.join(
|
|
5062
|
+
", "
|
|
5063
|
+
)}.` : void 0
|
|
5044
5064
|
};
|
|
5045
5065
|
}
|
|
5046
5066
|
});
|
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.3.0-
|
|
4
|
+
"version": "4.3.0-1005",
|
|
5
5
|
"private": false,
|
|
6
6
|
"author": "Elementor Team",
|
|
7
7
|
"homepage": "https://elementor.com/",
|
|
@@ -37,26 +37,26 @@
|
|
|
37
37
|
"react-dom": "^18.3.1"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@elementor/editor": "4.3.0-
|
|
41
|
-
"@elementor/editor-controls": "4.3.0-
|
|
42
|
-
"@elementor/editor-documents": "4.3.0-
|
|
43
|
-
"@elementor/editor-elements": "4.3.0-
|
|
44
|
-
"@elementor/editor-interactions": "4.3.0-
|
|
45
|
-
"@elementor/editor-mcp": "4.3.0-
|
|
46
|
-
"@elementor/editor-notifications": "4.3.0-
|
|
47
|
-
"@elementor/editor-props": "4.3.0-
|
|
48
|
-
"@elementor/editor-responsive": "4.3.0-
|
|
49
|
-
"@elementor/editor-styles": "4.3.0-
|
|
50
|
-
"@elementor/editor-styles-repository": "4.3.0-
|
|
51
|
-
"@elementor/editor-ui": "4.3.0-
|
|
52
|
-
"@elementor/editor-v1-adapters": "4.3.0-
|
|
53
|
-
"@elementor/events": "4.3.0-
|
|
54
|
-
"@elementor/http-client": "4.3.0-
|
|
55
|
-
"@elementor/schema": "4.3.0-
|
|
56
|
-
"@elementor/twing": "4.3.0-
|
|
40
|
+
"@elementor/editor": "4.3.0-1005",
|
|
41
|
+
"@elementor/editor-controls": "4.3.0-1005",
|
|
42
|
+
"@elementor/editor-documents": "4.3.0-1005",
|
|
43
|
+
"@elementor/editor-elements": "4.3.0-1005",
|
|
44
|
+
"@elementor/editor-interactions": "4.3.0-1005",
|
|
45
|
+
"@elementor/editor-mcp": "4.3.0-1005",
|
|
46
|
+
"@elementor/editor-notifications": "4.3.0-1005",
|
|
47
|
+
"@elementor/editor-props": "4.3.0-1005",
|
|
48
|
+
"@elementor/editor-responsive": "4.3.0-1005",
|
|
49
|
+
"@elementor/editor-styles": "4.3.0-1005",
|
|
50
|
+
"@elementor/editor-styles-repository": "4.3.0-1005",
|
|
51
|
+
"@elementor/editor-ui": "4.3.0-1005",
|
|
52
|
+
"@elementor/editor-v1-adapters": "4.3.0-1005",
|
|
53
|
+
"@elementor/events": "4.3.0-1005",
|
|
54
|
+
"@elementor/http-client": "4.3.0-1005",
|
|
55
|
+
"@elementor/schema": "4.3.0-1005",
|
|
56
|
+
"@elementor/twing": "4.3.0-1005",
|
|
57
57
|
"@elementor/ui": "1.37.5",
|
|
58
|
-
"@elementor/utils": "4.3.0-
|
|
59
|
-
"@elementor/wp-media": "4.3.0-
|
|
58
|
+
"@elementor/utils": "4.3.0-1005",
|
|
59
|
+
"@elementor/wp-media": "4.3.0-1005",
|
|
60
60
|
"@floating-ui/react": "^0.27.5",
|
|
61
61
|
"@wordpress/i18n": "^5.13.0",
|
|
62
62
|
"dompurify": "^3.2.6"
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { getContainer, getWidgetsCache } from '@elementor/editor-elements';
|
|
2
|
+
|
|
3
|
+
import { doUpdateElementProperty, UnsupportedPropertyError } from '../../../utils/do-update-element-property';
|
|
4
|
+
import { initConfigureElementTool } from '../tool';
|
|
5
|
+
|
|
6
|
+
jest.mock( '@elementor/editor-elements', () => ( {
|
|
7
|
+
getContainer: jest.fn(),
|
|
8
|
+
getWidgetsCache: jest.fn(),
|
|
9
|
+
} ) );
|
|
10
|
+
|
|
11
|
+
jest.mock( '@elementor/editor-mcp', () => ( {
|
|
12
|
+
dispatchMcpStylesAppliedEvent: jest.fn(),
|
|
13
|
+
} ) );
|
|
14
|
+
|
|
15
|
+
jest.mock( '@elementor/editor-props', () => ( {
|
|
16
|
+
Schema: { isPropKeyConfigurable: () => true },
|
|
17
|
+
} ) );
|
|
18
|
+
|
|
19
|
+
jest.mock( '../prompt', () => ( {
|
|
20
|
+
CONFIGURE_ELEMENT_GUIDE_URI: 'configure-element-guide-uri',
|
|
21
|
+
generatePrompt: () => '',
|
|
22
|
+
} ) );
|
|
23
|
+
|
|
24
|
+
jest.mock( '../../../resources/widgets-schema-resource', () => ( {
|
|
25
|
+
WIDGET_SCHEMA_URI: 'elementor://widgets/schema/{widgetType}',
|
|
26
|
+
} ) );
|
|
27
|
+
|
|
28
|
+
jest.mock( '../../../resources/dynamic-tags-resource', () => ( {
|
|
29
|
+
DYNAMIC_TAGS_URI: 'elementor://dynamic-tags',
|
|
30
|
+
} ) );
|
|
31
|
+
|
|
32
|
+
jest.mock( '../../../utils/resolve-canonical-prop-name', () => ( {
|
|
33
|
+
resolveCanonicalPropKeys: ( _elementType: string, props: Record< string, unknown > ) => props,
|
|
34
|
+
} ) );
|
|
35
|
+
|
|
36
|
+
jest.mock( '../../../utils/convert-css-to-atomic', () => ( {
|
|
37
|
+
convertCssToAtomic: jest.fn().mockResolvedValue( { props: {}, customCss: '' } ),
|
|
38
|
+
} ) );
|
|
39
|
+
|
|
40
|
+
jest.mock( '../../../utils/do-update-element-property', () => ( {
|
|
41
|
+
...jest.requireActual( '../../../utils/do-update-element-property' ),
|
|
42
|
+
doUpdateElementProperty: jest.fn(),
|
|
43
|
+
} ) );
|
|
44
|
+
|
|
45
|
+
const ELEMENT_ID = 'el-1';
|
|
46
|
+
const ELEMENT_TYPE = 'e-youtube';
|
|
47
|
+
|
|
48
|
+
type Handler = ( params: {
|
|
49
|
+
elementId: string;
|
|
50
|
+
elementType: string;
|
|
51
|
+
propertiesToChange: Record< string, unknown >;
|
|
52
|
+
style: Record< string, string | null >;
|
|
53
|
+
} ) => Promise< { success: boolean; warnings?: string } >;
|
|
54
|
+
|
|
55
|
+
const getHandler = (): Handler => {
|
|
56
|
+
const addTool = jest.fn();
|
|
57
|
+
initConfigureElementTool( { addTool, resource: jest.fn() } as never );
|
|
58
|
+
return addTool.mock.calls[ 0 ][ 0 ].handler;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
describe( 'configure-element handler unsupported props', () => {
|
|
62
|
+
beforeEach( () => {
|
|
63
|
+
jest.mocked( getWidgetsCache ).mockReturnValue( {
|
|
64
|
+
[ ELEMENT_TYPE ]: { atomic_props_schema: { title: { key: 'title' } } },
|
|
65
|
+
} as never );
|
|
66
|
+
jest.mocked( getContainer ).mockReturnValue( {
|
|
67
|
+
settings: { get: () => ELEMENT_TYPE },
|
|
68
|
+
} as never );
|
|
69
|
+
jest.mocked( doUpdateElementProperty ).mockReset();
|
|
70
|
+
} );
|
|
71
|
+
|
|
72
|
+
it( 'skips an unsupported prop, reports it in warnings, and still applies supported props', async () => {
|
|
73
|
+
// Arrange
|
|
74
|
+
jest.mocked( doUpdateElementProperty ).mockImplementation( ( { propertyName } ) => {
|
|
75
|
+
if ( 'link' === propertyName ) {
|
|
76
|
+
throw new UnsupportedPropertyError( ELEMENT_TYPE, 'link', [ 'title' ] );
|
|
77
|
+
}
|
|
78
|
+
} );
|
|
79
|
+
const handler = getHandler();
|
|
80
|
+
|
|
81
|
+
// Act
|
|
82
|
+
const result = await handler( {
|
|
83
|
+
elementId: ELEMENT_ID,
|
|
84
|
+
elementType: ELEMENT_TYPE,
|
|
85
|
+
propertiesToChange: { title: { $$type: 'string', value: 'Hi' }, link: { $$type: 'link', value: {} } },
|
|
86
|
+
style: {},
|
|
87
|
+
} );
|
|
88
|
+
|
|
89
|
+
// Assert
|
|
90
|
+
expect( result.success ).toBe( true );
|
|
91
|
+
expect( result.warnings ).toContain( 'link' );
|
|
92
|
+
expect( doUpdateElementProperty ).toHaveBeenCalledTimes( 2 );
|
|
93
|
+
} );
|
|
94
|
+
|
|
95
|
+
it( 'returns no warnings when every prop is supported', async () => {
|
|
96
|
+
// Arrange
|
|
97
|
+
jest.mocked( doUpdateElementProperty ).mockImplementation( () => undefined );
|
|
98
|
+
const handler = getHandler();
|
|
99
|
+
|
|
100
|
+
// Act
|
|
101
|
+
const result = await handler( {
|
|
102
|
+
elementId: ELEMENT_ID,
|
|
103
|
+
elementType: ELEMENT_TYPE,
|
|
104
|
+
propertiesToChange: { title: { $$type: 'string', value: 'Hi' } },
|
|
105
|
+
style: {},
|
|
106
|
+
} );
|
|
107
|
+
|
|
108
|
+
// Assert
|
|
109
|
+
expect( result.success ).toBe( true );
|
|
110
|
+
expect( result.warnings ).toBeUndefined();
|
|
111
|
+
} );
|
|
112
|
+
|
|
113
|
+
it( 'rethrows non-UnsupportedPropertyError failures', async () => {
|
|
114
|
+
// Arrange
|
|
115
|
+
jest.mocked( doUpdateElementProperty ).mockImplementation( () => {
|
|
116
|
+
throw new Error( 'Invalid PropValue' );
|
|
117
|
+
} );
|
|
118
|
+
const handler = getHandler();
|
|
119
|
+
|
|
120
|
+
// Act & Assert
|
|
121
|
+
await expect(
|
|
122
|
+
handler( {
|
|
123
|
+
elementId: ELEMENT_ID,
|
|
124
|
+
elementType: ELEMENT_TYPE,
|
|
125
|
+
propertiesToChange: { title: { $$type: 'string', value: 'Hi' } },
|
|
126
|
+
style: {},
|
|
127
|
+
} )
|
|
128
|
+
).rejects.toThrow( /Invalid PropValue/ );
|
|
129
|
+
} );
|
|
130
|
+
} );
|
|
@@ -117,11 +117,10 @@ Do NOT send "group" (it is resolved automatically). Use { "settings": {} } only
|
|
|
117
117
|
V4 only: If MCP fails, give manual steps using V4 UI.
|
|
118
118
|
|
|
119
119
|
V4 Editor structure:
|
|
120
|
-
Panel tabs: General (→ Settings section: ID, Tag, Link), Style, Interactions.
|
|
120
|
+
Panel tabs: General (→ Settings section: ID, Tag, and Link where the widget supports it), Style, Interactions.
|
|
121
121
|
NO Advanced tab. Never mention Advanced tab.
|
|
122
|
+
Note: \`link\` is valid only when the element's PropType schema (which you must already have) includes a \`link\` property. Sending \`link\` to a widget whose schema lacks it is skipped and reported in the response \`warnings\` (other changes still apply) and the link is lost.
|
|
122
123
|
` );
|
|
123
124
|
|
|
124
125
|
return configureElementToolPrompt.prompt();
|
|
125
126
|
};
|
|
126
|
-
|
|
127
|
-
export const CONFIGURE_ELEMENT_GUIDE_TEXT = generatePrompt();
|
|
@@ -36,4 +36,10 @@ export const outputSchema = {
|
|
|
36
36
|
.describe(
|
|
37
37
|
'Whether the configuration change was successful, only if propertyName and propertyValue are provided'
|
|
38
38
|
),
|
|
39
|
+
warnings: z
|
|
40
|
+
.string()
|
|
41
|
+
.describe(
|
|
42
|
+
'Non-fatal notices. Present when some props were skipped because they are not in the element schema (e.g. a "link" on a widget with no link prop). Other changes were still applied.'
|
|
43
|
+
)
|
|
44
|
+
.optional(),
|
|
39
45
|
};
|
|
@@ -6,7 +6,7 @@ import { type PropValue, Schema } from '@elementor/editor-props';
|
|
|
6
6
|
import { DYNAMIC_TAGS_URI } from '../../resources/dynamic-tags-resource';
|
|
7
7
|
import { WIDGET_SCHEMA_URI } from '../../resources/widgets-schema-resource';
|
|
8
8
|
import { convertCssToAtomic } from '../../utils/convert-css-to-atomic';
|
|
9
|
-
import { doUpdateElementProperty } from '../../utils/do-update-element-property';
|
|
9
|
+
import { doUpdateElementProperty, UnsupportedPropertyError } from '../../utils/do-update-element-property';
|
|
10
10
|
import { resolveCanonicalPropKeys } from '../../utils/resolve-canonical-prop-name';
|
|
11
11
|
import { CONFIGURE_ELEMENT_GUIDE_URI, generatePrompt } from './prompt';
|
|
12
12
|
import { inputSchema as schema, outputSchema } from './schema';
|
|
@@ -61,6 +61,7 @@ export const initConfigureElementTool = ( reg: MCPRegistryEntry ) => {
|
|
|
61
61
|
}
|
|
62
62
|
const propertiesToUpdate = resolveCanonicalPropKeys( elementType, propertiesToChange );
|
|
63
63
|
const toUpdate = Object.entries( propertiesToUpdate );
|
|
64
|
+
const skippedProps: string[] = [];
|
|
64
65
|
for ( const [ propertyName, propertyValue ] of toUpdate as [ string, PropValue ][] ) {
|
|
65
66
|
if ( ! Schema.isPropKeyConfigurable( propertyName ) ) {
|
|
66
67
|
throw new Error( `Not allowed to update ${ propertyName }` );
|
|
@@ -73,6 +74,10 @@ export const initConfigureElementTool = ( reg: MCPRegistryEntry ) => {
|
|
|
73
74
|
propertyValue,
|
|
74
75
|
} );
|
|
75
76
|
} catch ( error ) {
|
|
77
|
+
if ( error instanceof UnsupportedPropertyError ) {
|
|
78
|
+
skippedProps.push( error.propertyName );
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
76
81
|
const errorMessage = createUpdateErrorMessage( {
|
|
77
82
|
propertyName,
|
|
78
83
|
elementId,
|
|
@@ -86,6 +91,11 @@ export const initConfigureElementTool = ( reg: MCPRegistryEntry ) => {
|
|
|
86
91
|
await applyStyleFromCss( { elementId, elementType, style } );
|
|
87
92
|
return {
|
|
88
93
|
success: true,
|
|
94
|
+
warnings: skippedProps.length
|
|
95
|
+
? `Skipped unsupported props (not in the "${ elementType }" schema; other changes were applied): ${ skippedProps.join(
|
|
96
|
+
', '
|
|
97
|
+
) }.`
|
|
98
|
+
: undefined,
|
|
89
99
|
};
|
|
90
100
|
},
|
|
91
101
|
} );
|
|
@@ -8,7 +8,7 @@ import { Schema } from '@elementor/editor-props';
|
|
|
8
8
|
import { getStylesSchema, getVariantByMeta } from '@elementor/editor-styles';
|
|
9
9
|
import { __privateRunCommandSync } from '@elementor/editor-v1-adapters';
|
|
10
10
|
|
|
11
|
-
import { doUpdateElementProperty } from '../do-update-element-property';
|
|
11
|
+
import { doUpdateElementProperty, UnsupportedPropertyError } from '../do-update-element-property';
|
|
12
12
|
|
|
13
13
|
jest.mock( '@elementor/editor-elements', () => ( {
|
|
14
14
|
createElementStyle: jest.fn(),
|
|
@@ -108,6 +108,32 @@ describe( 'doUpdateElementProperty', () => {
|
|
|
108
108
|
expect( __privateRunCommandSync ).not.toHaveBeenCalled();
|
|
109
109
|
} );
|
|
110
110
|
|
|
111
|
+
it( 'throws UnsupportedPropertyError when the property is not in the element schema', () => {
|
|
112
|
+
// Arrange
|
|
113
|
+
const propertyName = 'link';
|
|
114
|
+
|
|
115
|
+
// Act
|
|
116
|
+
let thrown: unknown;
|
|
117
|
+
try {
|
|
118
|
+
doUpdateElementProperty( {
|
|
119
|
+
elementId: ELEMENT_ID,
|
|
120
|
+
elementType: ELEMENT_TYPE,
|
|
121
|
+
propertyName,
|
|
122
|
+
propertyValue: { $$type: 'link', value: {} },
|
|
123
|
+
} );
|
|
124
|
+
} catch ( error ) {
|
|
125
|
+
thrown = error;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Assert
|
|
129
|
+
expect( thrown ).toBeInstanceOf( UnsupportedPropertyError );
|
|
130
|
+
expect( ( thrown as UnsupportedPropertyError ).propertyName ).toBe( propertyName );
|
|
131
|
+
expect( ( thrown as UnsupportedPropertyError ).elementType ).toBe( ELEMENT_TYPE );
|
|
132
|
+
expect( ( thrown as Error ).message ).toContain( 'does not exist on element type' );
|
|
133
|
+
expect( Schema.validatePropValue ).not.toHaveBeenCalled();
|
|
134
|
+
expect( updateElementSettings ).not.toHaveBeenCalled();
|
|
135
|
+
} );
|
|
136
|
+
|
|
111
137
|
it( 'updates settings when Schema.validatePropValue reports valid PropValue', () => {
|
|
112
138
|
// Arrange
|
|
113
139
|
const propertyValue = 'resolved-title';
|
|
@@ -21,6 +21,23 @@ const LOCAL_STYLE_META = {
|
|
|
21
21
|
breakpoint: 'desktop',
|
|
22
22
|
state: null,
|
|
23
23
|
} as const;
|
|
24
|
+
|
|
25
|
+
export class UnsupportedPropertyError extends Error {
|
|
26
|
+
public readonly elementType: string;
|
|
27
|
+
public readonly propertyName: string;
|
|
28
|
+
|
|
29
|
+
constructor( elementType: string, propertyName: string, availableProperties: string[] ) {
|
|
30
|
+
super(
|
|
31
|
+
`Property "${ propertyName }" does not exist on element type "${ elementType }". Available properties are: ${ availableProperties.join(
|
|
32
|
+
', '
|
|
33
|
+
) }`
|
|
34
|
+
);
|
|
35
|
+
this.name = 'UnsupportedPropertyError';
|
|
36
|
+
this.elementType = elementType;
|
|
37
|
+
this.propertyName = propertyName;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
24
41
|
type CustomCssWriteMode = 'replace' | 'merge-with-stored';
|
|
25
42
|
type OwnParams = {
|
|
26
43
|
elementId: string;
|
|
@@ -151,12 +168,7 @@ export const doUpdateElementProperty = ( params: OwnParams ) => {
|
|
|
151
168
|
throw new Error( `No prop schema found for element type: ${ elementType }` );
|
|
152
169
|
}
|
|
153
170
|
if ( ! elementPropSchema[ propertyName ] ) {
|
|
154
|
-
|
|
155
|
-
throw new Error(
|
|
156
|
-
`Property "${ propertyName }" does not exist on element type "${ elementType }". Available properties are: ${ propertyNames.join(
|
|
157
|
-
', '
|
|
158
|
-
) }`
|
|
159
|
-
);
|
|
171
|
+
throw new UnsupportedPropertyError( elementType, propertyName, Object.keys( elementPropSchema ) );
|
|
160
172
|
}
|
|
161
173
|
const propKey = elementPropSchema[ propertyName ].key;
|
|
162
174
|
const value = resolvePropValue( propertyValue, propKey );
|