@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 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-877",
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-877",
40
+ "@elementor/editor": "4.2.0-878",
41
41
  "dompurify": "^3.2.6",
42
- "@elementor/editor-controls": "4.2.0-877",
43
- "@elementor/editor-documents": "4.2.0-877",
44
- "@elementor/editor-elements": "4.2.0-877",
45
- "@elementor/editor-interactions": "4.2.0-877",
46
- "@elementor/editor-mcp": "4.2.0-877",
47
- "@elementor/editor-notifications": "4.2.0-877",
48
- "@elementor/editor-props": "4.2.0-877",
49
- "@elementor/editor-responsive": "4.2.0-877",
50
- "@elementor/editor-styles": "4.2.0-877",
51
- "@elementor/editor-styles-repository": "4.2.0-877",
52
- "@elementor/editor-ui": "4.2.0-877",
53
- "@elementor/editor-v1-adapters": "4.2.0-877",
54
- "@elementor/schema": "4.2.0-877",
55
- "@elementor/twing": "4.2.0-877",
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-877",
58
- "@elementor/wp-media": "4.2.0-877",
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 CreateElementParams, type V1Element, type V1ElementConfig } from '@elementor/editor-elements';
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
- }