@elementor/editor-canvas 4.2.0-877 → 4.2.0-878
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 +0 -70
- package/dist/index.mjs +0 -70
- package/package.json +18 -18
- package/src/composition-builder/__tests__/composition-builder.test.ts +1 -187
- package/src/composition-builder/composition-builder.ts +0 -6
- package/src/mcp/resources/widgets-schema-resource.ts +0 -6
- package/src/mcp/tools/build-composition/prompt.ts +0 -1
- package/src/composition-builder/utils/__tests__/required-children-enforcer.test.ts +0 -79
- package/src/composition-builder/utils/__tests__/required-default-child-tags.test.ts +0 -46
- package/src/composition-builder/utils/required-children-enforcer.ts +0 -60
- package/src/composition-builder/utils/required-default-child-tags.ts +0 -31
package/dist/index.js
CHANGED
|
@@ -109,27 +109,6 @@ var import_editor_mcp = require("@elementor/editor-mcp");
|
|
|
109
109
|
var import_editor_props = require("@elementor/editor-props");
|
|
110
110
|
var import_editor_styles = require("@elementor/editor-styles");
|
|
111
111
|
|
|
112
|
-
// src/composition-builder/utils/required-default-child-tags.ts
|
|
113
|
-
function resolveDefaultChildTemplateTagName(template) {
|
|
114
|
-
const elementType = template.elType;
|
|
115
|
-
if (elementType === "widget") {
|
|
116
|
-
return typeof template.widgetType === "string" ? template.widgetType : "";
|
|
117
|
-
}
|
|
118
|
-
return typeof elementType === "string" ? elementType : "";
|
|
119
|
-
}
|
|
120
|
-
function getRequiredDefaultChildTemplates(elementConfig) {
|
|
121
|
-
const defaultChildren = elementConfig?.default_children;
|
|
122
|
-
if (!Array.isArray(defaultChildren)) {
|
|
123
|
-
return [];
|
|
124
|
-
}
|
|
125
|
-
return defaultChildren.filter(
|
|
126
|
-
(child) => !!child?.meta?.required
|
|
127
|
-
);
|
|
128
|
-
}
|
|
129
|
-
function getRequiredDefaultChildTagNames(elementConfig) {
|
|
130
|
-
return getRequiredDefaultChildTemplates(elementConfig).map(resolveDefaultChildTemplateTagName).filter((tag) => Boolean(tag));
|
|
131
|
-
}
|
|
132
|
-
|
|
133
112
|
// src/mcp/utils/element-data-util.ts
|
|
134
113
|
var import_editor_elements = require("@elementor/editor-elements");
|
|
135
114
|
function hasV3Controls(controls) {
|
|
@@ -346,10 +325,6 @@ Variables from the user context ARE NOT SUPPORTED AND WILL RESOLVE IN ERROR.
|
|
|
346
325
|
...allowedParents.length ? { allowed_parents: allowedParents } : {}
|
|
347
326
|
};
|
|
348
327
|
}
|
|
349
|
-
const requiredDirectChildTags = getRequiredDefaultChildTagNames(widgetData);
|
|
350
|
-
if (requiredDirectChildTags.length) {
|
|
351
|
-
llmGuidance.required_direct_children = requiredDirectChildTags;
|
|
352
|
-
}
|
|
353
328
|
return {
|
|
354
329
|
contents: [
|
|
355
330
|
{
|
|
@@ -4116,46 +4091,6 @@ var validateInput = {
|
|
|
4116
4091
|
}
|
|
4117
4092
|
};
|
|
4118
4093
|
|
|
4119
|
-
// src/composition-builder/utils/required-children-enforcer.ts
|
|
4120
|
-
var REQUIRED_CHILD_SCHEMA_HINT = "Use the widget schema resource; under llm_guidance.required_direct_children for V4 widgets.";
|
|
4121
|
-
var RequiredChildrenEnforcer = class {
|
|
4122
|
-
elementType;
|
|
4123
|
-
requiredTemplates;
|
|
4124
|
-
constructor(elementType, widgetsCache) {
|
|
4125
|
-
this.elementType = elementType;
|
|
4126
|
-
this.requiredTemplates = getRequiredDefaultChildTemplates(widgetsCache[elementType]);
|
|
4127
|
-
}
|
|
4128
|
-
enforce(xml) {
|
|
4129
|
-
if (this.requiredTemplates.length === 0) {
|
|
4130
|
-
return;
|
|
4131
|
-
}
|
|
4132
|
-
const errors = [];
|
|
4133
|
-
for (const rootNode of Array.from(xml.children)) {
|
|
4134
|
-
this.collectMissingRequiredErrors(rootNode, errors);
|
|
4135
|
-
}
|
|
4136
|
-
if (errors.length) {
|
|
4137
|
-
throw new Error(`${errors.join("\n")}
|
|
4138
|
-
${REQUIRED_CHILD_SCHEMA_HINT}`);
|
|
4139
|
-
}
|
|
4140
|
-
}
|
|
4141
|
-
collectMissingRequiredErrors(node, errors) {
|
|
4142
|
-
if (node.tagName === this.elementType) {
|
|
4143
|
-
const existingChildTags = new Set(Array.from(node.children).map((child) => child.tagName));
|
|
4144
|
-
const missingTags = this.requiredTemplates.map(resolveDefaultChildTemplateTagName).filter((tag) => tag && !existingChildTags.has(tag));
|
|
4145
|
-
if (missingTags.length) {
|
|
4146
|
-
const configurationId = node.getAttribute("configuration-id");
|
|
4147
|
-
const location2 = configurationId ? `<${node.tagName} configuration-id="${configurationId}">` : `<${node.tagName}>`;
|
|
4148
|
-
errors.push(
|
|
4149
|
-
`${location2} Missing required direct child element tag(s): ${missingTags.join(", ")}.`
|
|
4150
|
-
);
|
|
4151
|
-
}
|
|
4152
|
-
}
|
|
4153
|
-
for (const childNode of Array.from(node.children)) {
|
|
4154
|
-
this.collectMissingRequiredErrors(childNode, errors);
|
|
4155
|
-
}
|
|
4156
|
-
}
|
|
4157
|
-
};
|
|
4158
|
-
|
|
4159
4094
|
// src/composition-builder/composition-builder.ts
|
|
4160
4095
|
var CREATE_ELEMENT_INVALID_CONTAINER_MESSAGE = "createElement did not return an element container with a model.";
|
|
4161
4096
|
var CompositionBuilder = class _CompositionBuilder {
|
|
@@ -4359,10 +4294,6 @@ var CompositionBuilder = class _CompositionBuilder {
|
|
|
4359
4294
|
throw new Error(`Unknown widget type: ${node.tagName}`);
|
|
4360
4295
|
}
|
|
4361
4296
|
});
|
|
4362
|
-
Object.keys(widgetsCache).forEach((elementType) => {
|
|
4363
|
-
const requiredChildrenEnforcer = new RequiredChildrenEnforcer(elementType, widgetsCache);
|
|
4364
|
-
requiredChildrenEnforcer.enforce(this.xml);
|
|
4365
|
-
});
|
|
4366
4297
|
const childTypeErrors = [];
|
|
4367
4298
|
for (const rootChild of Array.from(this.xml.children)) {
|
|
4368
4299
|
childTypeErrors.push(...this.validateChildTypes(rootChild, widgetsCache));
|
|
@@ -4441,7 +4372,6 @@ This tool support v4 elements only
|
|
|
4441
4372
|
## NESTED ELEMENTS
|
|
4442
4373
|
Some elements have internal tree structures (nesting). When using these elements, you MUST build the FULL tree in XML.
|
|
4443
4374
|
- Check \`llm_guidance.nesting\` in widget schemas for structure requirements
|
|
4444
|
-
- \`llm_guidance.required_direct_children\` lists element types that must appear as direct child tags in XML (from widget defaults)
|
|
4445
4375
|
- \`allowed_child_types\` lists which element types can be nested inside
|
|
4446
4376
|
- \`allowed_parents\` lists which element types this element can be placed inside
|
|
4447
4377
|
|
package/dist/index.mjs
CHANGED
|
@@ -52,27 +52,6 @@ import {
|
|
|
52
52
|
} from "@elementor/editor-props";
|
|
53
53
|
import { getStylesSchema } from "@elementor/editor-styles";
|
|
54
54
|
|
|
55
|
-
// src/composition-builder/utils/required-default-child-tags.ts
|
|
56
|
-
function resolveDefaultChildTemplateTagName(template) {
|
|
57
|
-
const elementType = template.elType;
|
|
58
|
-
if (elementType === "widget") {
|
|
59
|
-
return typeof template.widgetType === "string" ? template.widgetType : "";
|
|
60
|
-
}
|
|
61
|
-
return typeof elementType === "string" ? elementType : "";
|
|
62
|
-
}
|
|
63
|
-
function getRequiredDefaultChildTemplates(elementConfig) {
|
|
64
|
-
const defaultChildren = elementConfig?.default_children;
|
|
65
|
-
if (!Array.isArray(defaultChildren)) {
|
|
66
|
-
return [];
|
|
67
|
-
}
|
|
68
|
-
return defaultChildren.filter(
|
|
69
|
-
(child) => !!child?.meta?.required
|
|
70
|
-
);
|
|
71
|
-
}
|
|
72
|
-
function getRequiredDefaultChildTagNames(elementConfig) {
|
|
73
|
-
return getRequiredDefaultChildTemplates(elementConfig).map(resolveDefaultChildTemplateTagName).filter((tag) => Boolean(tag));
|
|
74
|
-
}
|
|
75
|
-
|
|
76
55
|
// src/mcp/utils/element-data-util.ts
|
|
77
56
|
import { getWidgetsCache } from "@elementor/editor-elements";
|
|
78
57
|
function hasV3Controls(controls) {
|
|
@@ -289,10 +268,6 @@ Variables from the user context ARE NOT SUPPORTED AND WILL RESOLVE IN ERROR.
|
|
|
289
268
|
...allowedParents.length ? { allowed_parents: allowedParents } : {}
|
|
290
269
|
};
|
|
291
270
|
}
|
|
292
|
-
const requiredDirectChildTags = getRequiredDefaultChildTagNames(widgetData);
|
|
293
|
-
if (requiredDirectChildTags.length) {
|
|
294
|
-
llmGuidance.required_direct_children = requiredDirectChildTags;
|
|
295
|
-
}
|
|
296
271
|
return {
|
|
297
272
|
contents: [
|
|
298
273
|
{
|
|
@@ -4111,46 +4086,6 @@ var validateInput = {
|
|
|
4111
4086
|
}
|
|
4112
4087
|
};
|
|
4113
4088
|
|
|
4114
|
-
// src/composition-builder/utils/required-children-enforcer.ts
|
|
4115
|
-
var REQUIRED_CHILD_SCHEMA_HINT = "Use the widget schema resource; under llm_guidance.required_direct_children for V4 widgets.";
|
|
4116
|
-
var RequiredChildrenEnforcer = class {
|
|
4117
|
-
elementType;
|
|
4118
|
-
requiredTemplates;
|
|
4119
|
-
constructor(elementType, widgetsCache) {
|
|
4120
|
-
this.elementType = elementType;
|
|
4121
|
-
this.requiredTemplates = getRequiredDefaultChildTemplates(widgetsCache[elementType]);
|
|
4122
|
-
}
|
|
4123
|
-
enforce(xml) {
|
|
4124
|
-
if (this.requiredTemplates.length === 0) {
|
|
4125
|
-
return;
|
|
4126
|
-
}
|
|
4127
|
-
const errors = [];
|
|
4128
|
-
for (const rootNode of Array.from(xml.children)) {
|
|
4129
|
-
this.collectMissingRequiredErrors(rootNode, errors);
|
|
4130
|
-
}
|
|
4131
|
-
if (errors.length) {
|
|
4132
|
-
throw new Error(`${errors.join("\n")}
|
|
4133
|
-
${REQUIRED_CHILD_SCHEMA_HINT}`);
|
|
4134
|
-
}
|
|
4135
|
-
}
|
|
4136
|
-
collectMissingRequiredErrors(node, errors) {
|
|
4137
|
-
if (node.tagName === this.elementType) {
|
|
4138
|
-
const existingChildTags = new Set(Array.from(node.children).map((child) => child.tagName));
|
|
4139
|
-
const missingTags = this.requiredTemplates.map(resolveDefaultChildTemplateTagName).filter((tag) => tag && !existingChildTags.has(tag));
|
|
4140
|
-
if (missingTags.length) {
|
|
4141
|
-
const configurationId = node.getAttribute("configuration-id");
|
|
4142
|
-
const location2 = configurationId ? `<${node.tagName} configuration-id="${configurationId}">` : `<${node.tagName}>`;
|
|
4143
|
-
errors.push(
|
|
4144
|
-
`${location2} Missing required direct child element tag(s): ${missingTags.join(", ")}.`
|
|
4145
|
-
);
|
|
4146
|
-
}
|
|
4147
|
-
}
|
|
4148
|
-
for (const childNode of Array.from(node.children)) {
|
|
4149
|
-
this.collectMissingRequiredErrors(childNode, errors);
|
|
4150
|
-
}
|
|
4151
|
-
}
|
|
4152
|
-
};
|
|
4153
|
-
|
|
4154
4089
|
// src/composition-builder/composition-builder.ts
|
|
4155
4090
|
var CREATE_ELEMENT_INVALID_CONTAINER_MESSAGE = "createElement did not return an element container with a model.";
|
|
4156
4091
|
var CompositionBuilder = class _CompositionBuilder {
|
|
@@ -4354,10 +4289,6 @@ var CompositionBuilder = class _CompositionBuilder {
|
|
|
4354
4289
|
throw new Error(`Unknown widget type: ${node.tagName}`);
|
|
4355
4290
|
}
|
|
4356
4291
|
});
|
|
4357
|
-
Object.keys(widgetsCache).forEach((elementType) => {
|
|
4358
|
-
const requiredChildrenEnforcer = new RequiredChildrenEnforcer(elementType, widgetsCache);
|
|
4359
|
-
requiredChildrenEnforcer.enforce(this.xml);
|
|
4360
|
-
});
|
|
4361
4292
|
const childTypeErrors = [];
|
|
4362
4293
|
for (const rootChild of Array.from(this.xml.children)) {
|
|
4363
4294
|
childTypeErrors.push(...this.validateChildTypes(rootChild, widgetsCache));
|
|
@@ -4436,7 +4367,6 @@ This tool support v4 elements only
|
|
|
4436
4367
|
## NESTED ELEMENTS
|
|
4437
4368
|
Some elements have internal tree structures (nesting). When using these elements, you MUST build the FULL tree in XML.
|
|
4438
4369
|
- Check \`llm_guidance.nesting\` in widget schemas for structure requirements
|
|
4439
|
-
- \`llm_guidance.required_direct_children\` lists element types that must appear as direct child tags in XML (from widget defaults)
|
|
4440
4370
|
- \`allowed_child_types\` lists which element types can be nested inside
|
|
4441
4371
|
- \`allowed_parents\` lists which element types this element can be placed inside
|
|
4442
4372
|
|
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-878",
|
|
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-878",
|
|
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-878",
|
|
43
|
+
"@elementor/editor-documents": "4.2.0-878",
|
|
44
|
+
"@elementor/editor-elements": "4.2.0-878",
|
|
45
|
+
"@elementor/editor-interactions": "4.2.0-878",
|
|
46
|
+
"@elementor/editor-mcp": "4.2.0-878",
|
|
47
|
+
"@elementor/editor-notifications": "4.2.0-878",
|
|
48
|
+
"@elementor/editor-props": "4.2.0-878",
|
|
49
|
+
"@elementor/editor-responsive": "4.2.0-878",
|
|
50
|
+
"@elementor/editor-styles": "4.2.0-878",
|
|
51
|
+
"@elementor/editor-styles-repository": "4.2.0-878",
|
|
52
|
+
"@elementor/editor-ui": "4.2.0-878",
|
|
53
|
+
"@elementor/editor-v1-adapters": "4.2.0-878",
|
|
54
|
+
"@elementor/schema": "4.2.0-878",
|
|
55
|
+
"@elementor/twing": "4.2.0-878",
|
|
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-878",
|
|
58
|
+
"@elementor/wp-media": "4.2.0-878",
|
|
59
59
|
"@floating-ui/react": "^0.27.5",
|
|
60
60
|
"@wordpress/i18n": "^5.13.0"
|
|
61
61
|
},
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type V1Element } from '@elementor/editor-elements';
|
|
2
2
|
|
|
3
3
|
import { CompositionBuilder } from '../composition-builder';
|
|
4
4
|
|
|
@@ -188,189 +188,3 @@ describe( 'CompositionBuilder.build applyProperties after create', () => {
|
|
|
188
188
|
expect( doUpdateElementProperty ).not.toHaveBeenCalled();
|
|
189
189
|
} );
|
|
190
190
|
} );
|
|
191
|
-
|
|
192
|
-
describe( 'CompositionBuilder.build required children', () => {
|
|
193
|
-
it( 'rejects build when required direct children are absent from XML', async () => {
|
|
194
|
-
// Arrange
|
|
195
|
-
let elementIdSequence = 0;
|
|
196
|
-
const createdElement = createMockPartialContainer( GENERATED_ELEMENT_ID );
|
|
197
|
-
const createElementMock = jest.fn().mockReturnValue( createdElement );
|
|
198
|
-
const formWidgetsCache = {
|
|
199
|
-
'e-form': {
|
|
200
|
-
title: 'Form',
|
|
201
|
-
controls: {},
|
|
202
|
-
elType: 'widget',
|
|
203
|
-
default_children: [
|
|
204
|
-
{
|
|
205
|
-
elType: 'e-form-success-message',
|
|
206
|
-
meta: { required: true },
|
|
207
|
-
elements: [],
|
|
208
|
-
},
|
|
209
|
-
{
|
|
210
|
-
elType: 'e-form-error-message',
|
|
211
|
-
meta: { required: true },
|
|
212
|
-
elements: [],
|
|
213
|
-
},
|
|
214
|
-
{
|
|
215
|
-
elType: 'widget',
|
|
216
|
-
widgetType: 'e-form-input',
|
|
217
|
-
elements: [],
|
|
218
|
-
},
|
|
219
|
-
],
|
|
220
|
-
},
|
|
221
|
-
'e-form-error-message': {
|
|
222
|
-
title: 'Error',
|
|
223
|
-
controls: {},
|
|
224
|
-
elType: 'e-form-error-message',
|
|
225
|
-
},
|
|
226
|
-
'e-form-input': { title: 'Input', controls: {}, elType: 'widget' },
|
|
227
|
-
'e-form-success-message': {
|
|
228
|
-
title: 'Success',
|
|
229
|
-
controls: {},
|
|
230
|
-
elType: 'e-form-success-message',
|
|
231
|
-
},
|
|
232
|
-
} as Record< string, V1ElementConfig >;
|
|
233
|
-
const builder = CompositionBuilder.fromXMLString(
|
|
234
|
-
'<e-form configuration-id="form-1"><e-form-input /></e-form>',
|
|
235
|
-
{
|
|
236
|
-
createElement: createElementMock,
|
|
237
|
-
deleteElement: jest.fn(),
|
|
238
|
-
getContainer: jest.fn(),
|
|
239
|
-
generateElementId: jest.fn().mockImplementation( () => `form-comp-${ ++elementIdSequence }` ),
|
|
240
|
-
getWidgetsCache: jest.fn().mockReturnValue( formWidgetsCache ),
|
|
241
|
-
doUpdateElementProperty: jest.fn(),
|
|
242
|
-
}
|
|
243
|
-
);
|
|
244
|
-
|
|
245
|
-
// Act & Assert
|
|
246
|
-
await expect( builder.build( createMockRootContainer() ) ).rejects.toThrow(
|
|
247
|
-
/Missing required direct child element tag\(s\): e-form-success-message, e-form-error-message/
|
|
248
|
-
);
|
|
249
|
-
expect( createElementMock ).not.toHaveBeenCalled();
|
|
250
|
-
} );
|
|
251
|
-
|
|
252
|
-
it( 'rejects build when only some required direct children exist', async () => {
|
|
253
|
-
// Arrange
|
|
254
|
-
let elementIdSequence = 0;
|
|
255
|
-
const createdElement = createMockPartialContainer( GENERATED_ELEMENT_ID );
|
|
256
|
-
const createElementMock = jest.fn().mockReturnValue( createdElement );
|
|
257
|
-
const formWidgetsCache = {
|
|
258
|
-
'e-form': {
|
|
259
|
-
title: 'Form',
|
|
260
|
-
controls: {},
|
|
261
|
-
elType: 'widget',
|
|
262
|
-
default_children: [
|
|
263
|
-
{
|
|
264
|
-
elType: 'e-form-success-message',
|
|
265
|
-
meta: { required: true },
|
|
266
|
-
elements: [],
|
|
267
|
-
},
|
|
268
|
-
{
|
|
269
|
-
elType: 'e-form-error-message',
|
|
270
|
-
meta: { required: true },
|
|
271
|
-
elements: [],
|
|
272
|
-
},
|
|
273
|
-
{
|
|
274
|
-
elType: 'widget',
|
|
275
|
-
widgetType: 'e-form-input',
|
|
276
|
-
elements: [],
|
|
277
|
-
},
|
|
278
|
-
],
|
|
279
|
-
},
|
|
280
|
-
'e-form-error-message': {
|
|
281
|
-
title: 'Error',
|
|
282
|
-
controls: {},
|
|
283
|
-
elType: 'e-form-error-message',
|
|
284
|
-
},
|
|
285
|
-
'e-form-input': { title: 'Input', controls: {}, elType: 'widget' },
|
|
286
|
-
'e-form-success-message': {
|
|
287
|
-
title: 'Success',
|
|
288
|
-
controls: {},
|
|
289
|
-
elType: 'e-form-success-message',
|
|
290
|
-
},
|
|
291
|
-
} as Record< string, V1ElementConfig >;
|
|
292
|
-
const builder = CompositionBuilder.fromXMLString(
|
|
293
|
-
'<e-form configuration-id="form-1"><e-form-success-message /><e-form-input /></e-form>',
|
|
294
|
-
{
|
|
295
|
-
createElement: createElementMock,
|
|
296
|
-
deleteElement: jest.fn(),
|
|
297
|
-
getContainer: jest.fn(),
|
|
298
|
-
generateElementId: jest.fn().mockImplementation( () => `form-comp-${ ++elementIdSequence }` ),
|
|
299
|
-
getWidgetsCache: jest.fn().mockReturnValue( formWidgetsCache ),
|
|
300
|
-
doUpdateElementProperty: jest.fn(),
|
|
301
|
-
}
|
|
302
|
-
);
|
|
303
|
-
|
|
304
|
-
// Act & Assert
|
|
305
|
-
await expect( builder.build( createMockRootContainer() ) ).rejects.toThrow(
|
|
306
|
-
/Missing required direct child element tag\(s\): e-form-error-message/
|
|
307
|
-
);
|
|
308
|
-
expect( createElementMock ).not.toHaveBeenCalled();
|
|
309
|
-
} );
|
|
310
|
-
|
|
311
|
-
it( 'creates elements when XML includes all required direct children', async () => {
|
|
312
|
-
// Arrange
|
|
313
|
-
let elementIdSequence = 0;
|
|
314
|
-
const createdElement = createMockPartialContainer( GENERATED_ELEMENT_ID );
|
|
315
|
-
const createElementMock = jest.fn().mockReturnValue( createdElement );
|
|
316
|
-
const formWidgetsCache = {
|
|
317
|
-
'e-form': {
|
|
318
|
-
title: 'Form',
|
|
319
|
-
controls: {},
|
|
320
|
-
elType: 'widget',
|
|
321
|
-
default_children: [
|
|
322
|
-
{
|
|
323
|
-
elType: 'e-form-success-message',
|
|
324
|
-
meta: { required: true },
|
|
325
|
-
elements: [],
|
|
326
|
-
},
|
|
327
|
-
{
|
|
328
|
-
elType: 'e-form-error-message',
|
|
329
|
-
meta: { required: true },
|
|
330
|
-
elements: [],
|
|
331
|
-
},
|
|
332
|
-
{
|
|
333
|
-
elType: 'widget',
|
|
334
|
-
widgetType: 'e-form-input',
|
|
335
|
-
elements: [],
|
|
336
|
-
},
|
|
337
|
-
],
|
|
338
|
-
},
|
|
339
|
-
'e-form-error-message': {
|
|
340
|
-
title: 'Error',
|
|
341
|
-
controls: {},
|
|
342
|
-
elType: 'e-form-error-message',
|
|
343
|
-
},
|
|
344
|
-
'e-form-input': { title: 'Input', controls: {}, elType: 'widget' },
|
|
345
|
-
'e-form-success-message': {
|
|
346
|
-
title: 'Success',
|
|
347
|
-
controls: {},
|
|
348
|
-
elType: 'e-form-success-message',
|
|
349
|
-
},
|
|
350
|
-
} as Record< string, V1ElementConfig >;
|
|
351
|
-
const builder = CompositionBuilder.fromXMLString(
|
|
352
|
-
'<e-form configuration-id="form-1">' +
|
|
353
|
-
'<e-form-success-message /><e-form-error-message /><e-form-input />' +
|
|
354
|
-
'</e-form>',
|
|
355
|
-
{
|
|
356
|
-
createElement: createElementMock,
|
|
357
|
-
deleteElement: jest.fn(),
|
|
358
|
-
getContainer: jest.fn(),
|
|
359
|
-
generateElementId: jest.fn().mockImplementation( () => `form-comp-${ ++elementIdSequence }` ),
|
|
360
|
-
getWidgetsCache: jest.fn().mockReturnValue( formWidgetsCache ),
|
|
361
|
-
doUpdateElementProperty: jest.fn(),
|
|
362
|
-
}
|
|
363
|
-
);
|
|
364
|
-
|
|
365
|
-
// Act
|
|
366
|
-
await builder.build( createMockRootContainer() );
|
|
367
|
-
|
|
368
|
-
// Assert
|
|
369
|
-
const createArgs = createElementMock.mock.calls[ 0 ]?.[ 0 ] as CreateElementParams;
|
|
370
|
-
const childElements = ( createArgs.model?.elements || [] ) as Array< { elType?: string; widgetType?: string } >;
|
|
371
|
-
|
|
372
|
-
expect( childElements.filter( ( child ) => child.elType === 'e-form-success-message' ).length ).toBe( 1 );
|
|
373
|
-
expect( childElements.filter( ( child ) => child.elType === 'e-form-error-message' ).length ).toBe( 1 );
|
|
374
|
-
expect( childElements.some( ( child ) => child.widgetType === 'e-form-input' ) ).toBe( true );
|
|
375
|
-
} );
|
|
376
|
-
} );
|
|
@@ -13,7 +13,6 @@ import { type z } from '@elementor/schema';
|
|
|
13
13
|
|
|
14
14
|
import { doUpdateElementProperty } from '../mcp/utils/do-update-element-property';
|
|
15
15
|
import { validateInput } from '../mcp/utils/validate-input';
|
|
16
|
-
import { RequiredChildrenEnforcer } from './utils/required-children-enforcer';
|
|
17
16
|
|
|
18
17
|
type AnyValue = z.infer< z.ZodTypeAny >;
|
|
19
18
|
type AnyConfig = Record< string, Record< string, AnyValue > >;
|
|
@@ -281,11 +280,6 @@ export class CompositionBuilder {
|
|
|
281
280
|
}
|
|
282
281
|
} );
|
|
283
282
|
|
|
284
|
-
Object.keys( widgetsCache ).forEach( ( elementType ) => {
|
|
285
|
-
const requiredChildrenEnforcer = new RequiredChildrenEnforcer( elementType, widgetsCache );
|
|
286
|
-
requiredChildrenEnforcer.enforce( this.xml );
|
|
287
|
-
} );
|
|
288
|
-
|
|
289
283
|
const childTypeErrors: string[] = [];
|
|
290
284
|
for ( const rootChild of Array.from( this.xml.children ) ) {
|
|
291
285
|
childTypeErrors.push( ...this.validateChildTypes( rootChild, widgetsCache ) );
|
|
@@ -11,7 +11,6 @@ import {
|
|
|
11
11
|
} from '@elementor/editor-props';
|
|
12
12
|
import { getStylesSchema } from '@elementor/editor-styles';
|
|
13
13
|
|
|
14
|
-
import { getRequiredDefaultChildTagNames } from '../../composition-builder/utils/required-default-child-tags';
|
|
15
14
|
import { hasV3Controls, isWidgetAvailableForLLM } from '../utils/element-data-util';
|
|
16
15
|
|
|
17
16
|
const V3_LAYOUT_CONTROL_TYPES = new Set( [ 'section', 'tab', 'tabs' ] );
|
|
@@ -223,11 +222,6 @@ Variables from the user context ARE NOT SUPPORTED AND WILL RESOLVE IN ERROR.
|
|
|
223
222
|
};
|
|
224
223
|
}
|
|
225
224
|
|
|
226
|
-
const requiredDirectChildTags = getRequiredDefaultChildTagNames( widgetData );
|
|
227
|
-
if ( requiredDirectChildTags.length ) {
|
|
228
|
-
llmGuidance.required_direct_children = requiredDirectChildTags;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
225
|
return {
|
|
232
226
|
contents: [
|
|
233
227
|
{
|
|
@@ -30,7 +30,6 @@ This tool support v4 elements only
|
|
|
30
30
|
## NESTED ELEMENTS
|
|
31
31
|
Some elements have internal tree structures (nesting). When using these elements, you MUST build the FULL tree in XML.
|
|
32
32
|
- Check \`llm_guidance.nesting\` in widget schemas for structure requirements
|
|
33
|
-
- \`llm_guidance.required_direct_children\` lists element types that must appear as direct child tags in XML (from widget defaults)
|
|
34
33
|
- \`allowed_child_types\` lists which element types can be nested inside
|
|
35
34
|
- \`allowed_parents\` lists which element types this element can be placed inside
|
|
36
35
|
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
import { type V1ElementConfig } from '@elementor/editor-elements';
|
|
2
|
-
|
|
3
|
-
import { RequiredChildrenEnforcer } from '../required-children-enforcer';
|
|
4
|
-
|
|
5
|
-
describe( 'RequiredChildrenEnforcer', () => {
|
|
6
|
-
const FULL_FORM_DIRECT_CHILD_COUNT = 3;
|
|
7
|
-
|
|
8
|
-
const createWidgetsCache = (): Record< string, V1ElementConfig > => ( {
|
|
9
|
-
'e-form': {
|
|
10
|
-
title: 'Form',
|
|
11
|
-
controls: {},
|
|
12
|
-
elType: 'widget',
|
|
13
|
-
default_children: [
|
|
14
|
-
{
|
|
15
|
-
elType: 'e-form-success-message',
|
|
16
|
-
meta: { required: true },
|
|
17
|
-
elements: [],
|
|
18
|
-
},
|
|
19
|
-
{
|
|
20
|
-
elType: 'e-form-error-message',
|
|
21
|
-
meta: { required: true },
|
|
22
|
-
elements: [],
|
|
23
|
-
},
|
|
24
|
-
{
|
|
25
|
-
elType: 'widget',
|
|
26
|
-
widgetType: 'e-form-input',
|
|
27
|
-
elements: [],
|
|
28
|
-
},
|
|
29
|
-
],
|
|
30
|
-
} as V1ElementConfig,
|
|
31
|
-
'e-form-input': { title: 'Input', controls: {}, elType: 'widget' } as V1ElementConfig,
|
|
32
|
-
'e-form-success-message': {
|
|
33
|
-
title: 'Success',
|
|
34
|
-
controls: {},
|
|
35
|
-
elType: 'e-form-success-message',
|
|
36
|
-
} as V1ElementConfig,
|
|
37
|
-
'e-form-error-message': {
|
|
38
|
-
title: 'Error',
|
|
39
|
-
controls: {},
|
|
40
|
-
elType: 'e-form-error-message',
|
|
41
|
-
} as V1ElementConfig,
|
|
42
|
-
} );
|
|
43
|
-
|
|
44
|
-
it( 'throws when required direct children are missing', () => {
|
|
45
|
-
// Arrange
|
|
46
|
-
const xml = new DOMParser().parseFromString( '<e-form><e-form-input /></e-form>', 'application/xml' );
|
|
47
|
-
const enforcer = new RequiredChildrenEnforcer( 'e-form', createWidgetsCache() );
|
|
48
|
-
|
|
49
|
-
// Act & Assert
|
|
50
|
-
expect( () => enforcer.enforce( xml ) ).toThrow(
|
|
51
|
-
/Missing required direct child element tag\(s\): e-form-success-message, e-form-error-message/
|
|
52
|
-
);
|
|
53
|
-
} );
|
|
54
|
-
|
|
55
|
-
it( 'throws when only some required direct children exist', () => {
|
|
56
|
-
// Arrange
|
|
57
|
-
const xml = new DOMParser().parseFromString( '<e-form><e-form-success-message /></e-form>', 'application/xml' );
|
|
58
|
-
const enforcer = new RequiredChildrenEnforcer( 'e-form', createWidgetsCache() );
|
|
59
|
-
|
|
60
|
-
// Act & Assert
|
|
61
|
-
expect( () => enforcer.enforce( xml ) ).toThrow(
|
|
62
|
-
/Missing required direct child element tag\(s\): e-form-error-message/
|
|
63
|
-
);
|
|
64
|
-
} );
|
|
65
|
-
|
|
66
|
-
it( 'does not throw when all required direct children exist', () => {
|
|
67
|
-
// Arrange
|
|
68
|
-
const xmlStr = '<e-form><e-form-success-message /><e-form-error-message /><e-form-input /></e-form>';
|
|
69
|
-
const xml = new DOMParser().parseFromString( xmlStr, 'application/xml' );
|
|
70
|
-
const enforcer = new RequiredChildrenEnforcer( 'e-form', createWidgetsCache() );
|
|
71
|
-
|
|
72
|
-
// Act
|
|
73
|
-
expect( () => enforcer.enforce( xml ) ).not.toThrow();
|
|
74
|
-
|
|
75
|
-
// Assert
|
|
76
|
-
const form = xml.querySelector( 'e-form' );
|
|
77
|
-
expect( form?.children.length ).toBe( FULL_FORM_DIRECT_CHILD_COUNT );
|
|
78
|
-
} );
|
|
79
|
-
} );
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import { type V1ElementConfig } from '@elementor/editor-elements';
|
|
2
|
-
|
|
3
|
-
import { getRequiredDefaultChildTagNames, resolveDefaultChildTemplateTagName } from '../required-default-child-tags';
|
|
4
|
-
|
|
5
|
-
describe( 'required-default-child-tags', () => {
|
|
6
|
-
it( 'returns XML tag names for default children marked meta.required', () => {
|
|
7
|
-
// Arrange
|
|
8
|
-
const config = {
|
|
9
|
-
title: 'Form',
|
|
10
|
-
controls: {},
|
|
11
|
-
default_children: [
|
|
12
|
-
{
|
|
13
|
-
elType: 'e-form-success-message',
|
|
14
|
-
meta: { required: true },
|
|
15
|
-
elements: [],
|
|
16
|
-
},
|
|
17
|
-
{
|
|
18
|
-
elType: 'widget',
|
|
19
|
-
widgetType: 'e-form-input',
|
|
20
|
-
meta: { required: true },
|
|
21
|
-
},
|
|
22
|
-
{
|
|
23
|
-
elType: 'e-form-label',
|
|
24
|
-
elements: [],
|
|
25
|
-
},
|
|
26
|
-
],
|
|
27
|
-
} as unknown as V1ElementConfig;
|
|
28
|
-
|
|
29
|
-
// Act
|
|
30
|
-
const tags = getRequiredDefaultChildTagNames( config );
|
|
31
|
-
|
|
32
|
-
// Assert
|
|
33
|
-
expect( tags ).toEqual( [ 'e-form-success-message', 'e-form-input' ] );
|
|
34
|
-
} );
|
|
35
|
-
|
|
36
|
-
it( 'resolveDefaultChildTemplateTagName uses widgetType when elType is widget', () => {
|
|
37
|
-
// Arrange
|
|
38
|
-
const template = { elType: 'widget', widgetType: 'e-form-submit-button' };
|
|
39
|
-
|
|
40
|
-
// Act
|
|
41
|
-
const tag = resolveDefaultChildTemplateTagName( template );
|
|
42
|
-
|
|
43
|
-
// Assert
|
|
44
|
-
expect( tag ).toBe( 'e-form-submit-button' );
|
|
45
|
-
} );
|
|
46
|
-
} );
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import { type V1ElementConfig } from '@elementor/editor-elements';
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
type DefaultChildTemplate,
|
|
5
|
-
getRequiredDefaultChildTemplates,
|
|
6
|
-
resolveDefaultChildTemplateTagName,
|
|
7
|
-
} from './required-default-child-tags';
|
|
8
|
-
|
|
9
|
-
const REQUIRED_CHILD_SCHEMA_HINT =
|
|
10
|
-
'Use the widget schema resource; under llm_guidance.required_direct_children for V4 widgets.';
|
|
11
|
-
|
|
12
|
-
export class RequiredChildrenEnforcer {
|
|
13
|
-
private readonly elementType: string;
|
|
14
|
-
private readonly requiredTemplates: DefaultChildTemplate[];
|
|
15
|
-
|
|
16
|
-
constructor( elementType: string, widgetsCache: Record< string, V1ElementConfig > ) {
|
|
17
|
-
this.elementType = elementType;
|
|
18
|
-
this.requiredTemplates = getRequiredDefaultChildTemplates( widgetsCache[ elementType ] );
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
enforce( xml: Document ) {
|
|
22
|
-
if ( this.requiredTemplates.length === 0 ) {
|
|
23
|
-
return;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const errors: string[] = [];
|
|
27
|
-
|
|
28
|
-
for ( const rootNode of Array.from( xml.children ) ) {
|
|
29
|
-
this.collectMissingRequiredErrors( rootNode, errors );
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
if ( errors.length ) {
|
|
33
|
-
throw new Error( `${ errors.join( '\n' ) }\n${ REQUIRED_CHILD_SCHEMA_HINT }` );
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
private collectMissingRequiredErrors( node: Element, errors: string[] ) {
|
|
38
|
-
if ( node.tagName === this.elementType ) {
|
|
39
|
-
const existingChildTags = new Set( Array.from( node.children ).map( ( child ) => child.tagName ) );
|
|
40
|
-
const missingTags = this.requiredTemplates
|
|
41
|
-
.map( resolveDefaultChildTemplateTagName )
|
|
42
|
-
.filter( ( tag ) => tag && ! existingChildTags.has( tag ) ) as string[];
|
|
43
|
-
|
|
44
|
-
if ( missingTags.length ) {
|
|
45
|
-
const configurationId = node.getAttribute( 'configuration-id' );
|
|
46
|
-
const location = configurationId
|
|
47
|
-
? `<${ node.tagName } configuration-id="${ configurationId }">`
|
|
48
|
-
: `<${ node.tagName }>`;
|
|
49
|
-
|
|
50
|
-
errors.push(
|
|
51
|
-
`${ location } Missing required direct child element tag(s): ${ missingTags.join( ', ' ) }.`
|
|
52
|
-
);
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
for ( const childNode of Array.from( node.children ) ) {
|
|
57
|
-
this.collectMissingRequiredErrors( childNode, errors );
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
}
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { type V1ElementConfig } from '@elementor/editor-elements';
|
|
2
|
-
|
|
3
|
-
export type DefaultChildTemplate = Record< string, unknown >;
|
|
4
|
-
|
|
5
|
-
export function resolveDefaultChildTemplateTagName( template: DefaultChildTemplate ): string {
|
|
6
|
-
const elementType = template.elType;
|
|
7
|
-
|
|
8
|
-
if ( elementType === 'widget' ) {
|
|
9
|
-
return typeof template.widgetType === 'string' ? template.widgetType : '';
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
return typeof elementType === 'string' ? elementType : '';
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export function getRequiredDefaultChildTemplates( elementConfig: V1ElementConfig | undefined ): DefaultChildTemplate[] {
|
|
16
|
-
const defaultChildren = elementConfig?.default_children;
|
|
17
|
-
|
|
18
|
-
if ( ! Array.isArray( defaultChildren ) ) {
|
|
19
|
-
return [];
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
return defaultChildren.filter(
|
|
23
|
-
( child ): child is DefaultChildTemplate => !! ( child as { meta?: { required?: boolean } } )?.meta?.required
|
|
24
|
-
);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export function getRequiredDefaultChildTagNames( elementConfig: V1ElementConfig | undefined ): string[] {
|
|
28
|
-
return getRequiredDefaultChildTemplates( elementConfig )
|
|
29
|
-
.map( resolveDefaultChildTemplateTagName )
|
|
30
|
-
.filter( ( tag ) => Boolean( tag ) );
|
|
31
|
-
}
|