@elementor/editor-canvas 4.3.0-984 → 4.3.0-985

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
@@ -2021,6 +2021,46 @@ function clipboardRootsAreAtomicForms(elements) {
2021
2021
  }
2022
2022
  return elements.every((el) => getClipboardElementType(el) === FORM_ELEMENT_TYPE);
2023
2023
  }
2024
+ function hasFormAncestor(node) {
2025
+ return node.closest(FORM_ELEMENT_TYPE) !== null;
2026
+ }
2027
+ function collectFormAncestorErrors(xml) {
2028
+ const errors = [];
2029
+ for (const node of xml.querySelectorAll("*")) {
2030
+ if (!FORM_FIELD_ELEMENT_TYPES.has(node.tagName.toLowerCase())) {
2031
+ continue;
2032
+ }
2033
+ if (hasFormAncestor(node)) {
2034
+ continue;
2035
+ }
2036
+ const id = node.getAttribute("configuration-id");
2037
+ errors.push(
2038
+ `<${node.tagName}${id ? ` configuration-id="${id}"` : ""}> must be nested inside <e-form> (any ancestor depth is allowed).`
2039
+ );
2040
+ }
2041
+ return errors;
2042
+ }
2043
+ function collectSubmitButtonErrors(xml) {
2044
+ const errors = [];
2045
+ for (const form of xml.querySelectorAll("e-form")) {
2046
+ const submitButtons = form.querySelectorAll("e-form-submit-button");
2047
+ if (submitButtons.length === 0) {
2048
+ errors.push(`<e-form> has no <e-form-submit-button>.`);
2049
+ } else if (submitButtons.length > 1) {
2050
+ errors.push(`<e-form> has ${submitButtons.length} submit buttons \u2014 only 1 is allowed.`);
2051
+ }
2052
+ }
2053
+ return errors;
2054
+ }
2055
+ function collectEmptyMessageErrors(xml) {
2056
+ const errors = [];
2057
+ for (const node of xml.querySelectorAll("e-form-success-message, e-form-error-message")) {
2058
+ if (node.children.length === 0) {
2059
+ errors.push(`<${node.tagName}> must have at least one child element (e.g. <e-atomic-paragraph>).`);
2060
+ }
2061
+ }
2062
+ return errors;
2063
+ }
2024
2064
 
2025
2065
  // src/form-structure/enforce-form-ancestor-commands.ts
2026
2066
  var FORM_FIELDS_OUTSIDE_ALERT = {
@@ -5195,6 +5235,11 @@ var CompositionBuilder = class _CompositionBuilder {
5195
5235
  throw new Error(`Invalid element structure:
5196
5236
  ${childTypeErrors.join("\n")}`);
5197
5237
  }
5238
+ const formErrors = [
5239
+ ...collectFormAncestorErrors(this.xml),
5240
+ ...collectSubmitButtonErrors(this.xml),
5241
+ ...collectEmptyMessageErrors(this.xml)
5242
+ ];
5198
5243
  const children = Array.from(this.xml.children);
5199
5244
  for (const childNode of children) {
5200
5245
  const modelTree = this.buildModelTree(childNode, widgetsCache);
@@ -5229,6 +5274,7 @@ ${childTypeErrors.join("\n")}`);
5229
5274
  return {
5230
5275
  configErrors,
5231
5276
  styleErrors,
5277
+ formErrors,
5232
5278
  rootContainers: [...this.rootContainers]
5233
5279
  };
5234
5280
  }
@@ -5539,7 +5585,11 @@ var initBuildCompositionsTool = (reg) => {
5539
5585
  compositionBuilder.setElementConfig(elementConfig);
5540
5586
  compositionBuilder.setStylesConfig(stylesConfig);
5541
5587
  compositionBuilder.setCustomCSS(customCSS);
5542
- const { configErrors, rootContainers: generatedRootContainers } = await compositionBuilder.build(targetContainer);
5588
+ const {
5589
+ configErrors,
5590
+ formErrors,
5591
+ rootContainers: generatedRootContainers
5592
+ } = await compositionBuilder.build(targetContainer);
5543
5593
  rootContainers.push(...generatedRootContainers);
5544
5594
  generatedXML = new XMLSerializer().serializeToString(compositionBuilder.getXML());
5545
5595
  rootContainers.forEach((container) => {
@@ -5554,6 +5604,9 @@ var initBuildCompositionsTool = (reg) => {
5554
5604
  if (configErrors.length) {
5555
5605
  errors.push(...configErrors.map((msg) => new Error(msg)));
5556
5606
  }
5607
+ if (formErrors.length) {
5608
+ errors.push(...formErrors.map((msg) => new Error(msg)));
5609
+ }
5557
5610
  } catch (error) {
5558
5611
  errors.push(error);
5559
5612
  }
package/dist/index.mjs CHANGED
@@ -1975,6 +1975,46 @@ function clipboardRootsAreAtomicForms(elements) {
1975
1975
  }
1976
1976
  return elements.every((el) => getClipboardElementType(el) === FORM_ELEMENT_TYPE);
1977
1977
  }
1978
+ function hasFormAncestor(node) {
1979
+ return node.closest(FORM_ELEMENT_TYPE) !== null;
1980
+ }
1981
+ function collectFormAncestorErrors(xml) {
1982
+ const errors = [];
1983
+ for (const node of xml.querySelectorAll("*")) {
1984
+ if (!FORM_FIELD_ELEMENT_TYPES.has(node.tagName.toLowerCase())) {
1985
+ continue;
1986
+ }
1987
+ if (hasFormAncestor(node)) {
1988
+ continue;
1989
+ }
1990
+ const id = node.getAttribute("configuration-id");
1991
+ errors.push(
1992
+ `<${node.tagName}${id ? ` configuration-id="${id}"` : ""}> must be nested inside <e-form> (any ancestor depth is allowed).`
1993
+ );
1994
+ }
1995
+ return errors;
1996
+ }
1997
+ function collectSubmitButtonErrors(xml) {
1998
+ const errors = [];
1999
+ for (const form of xml.querySelectorAll("e-form")) {
2000
+ const submitButtons = form.querySelectorAll("e-form-submit-button");
2001
+ if (submitButtons.length === 0) {
2002
+ errors.push(`<e-form> has no <e-form-submit-button>.`);
2003
+ } else if (submitButtons.length > 1) {
2004
+ errors.push(`<e-form> has ${submitButtons.length} submit buttons \u2014 only 1 is allowed.`);
2005
+ }
2006
+ }
2007
+ return errors;
2008
+ }
2009
+ function collectEmptyMessageErrors(xml) {
2010
+ const errors = [];
2011
+ for (const node of xml.querySelectorAll("e-form-success-message, e-form-error-message")) {
2012
+ if (node.children.length === 0) {
2013
+ errors.push(`<${node.tagName}> must have at least one child element (e.g. <e-atomic-paragraph>).`);
2014
+ }
2015
+ }
2016
+ return errors;
2017
+ }
1978
2018
 
1979
2019
  // src/form-structure/enforce-form-ancestor-commands.ts
1980
2020
  var FORM_FIELDS_OUTSIDE_ALERT = {
@@ -5180,6 +5220,11 @@ var CompositionBuilder = class _CompositionBuilder {
5180
5220
  throw new Error(`Invalid element structure:
5181
5221
  ${childTypeErrors.join("\n")}`);
5182
5222
  }
5223
+ const formErrors = [
5224
+ ...collectFormAncestorErrors(this.xml),
5225
+ ...collectSubmitButtonErrors(this.xml),
5226
+ ...collectEmptyMessageErrors(this.xml)
5227
+ ];
5183
5228
  const children = Array.from(this.xml.children);
5184
5229
  for (const childNode of children) {
5185
5230
  const modelTree = this.buildModelTree(childNode, widgetsCache);
@@ -5214,6 +5259,7 @@ ${childTypeErrors.join("\n")}`);
5214
5259
  return {
5215
5260
  configErrors,
5216
5261
  styleErrors,
5262
+ formErrors,
5217
5263
  rootContainers: [...this.rootContainers]
5218
5264
  };
5219
5265
  }
@@ -5524,7 +5570,11 @@ var initBuildCompositionsTool = (reg) => {
5524
5570
  compositionBuilder.setElementConfig(elementConfig);
5525
5571
  compositionBuilder.setStylesConfig(stylesConfig);
5526
5572
  compositionBuilder.setCustomCSS(customCSS);
5527
- const { configErrors, rootContainers: generatedRootContainers } = await compositionBuilder.build(targetContainer);
5573
+ const {
5574
+ configErrors,
5575
+ formErrors,
5576
+ rootContainers: generatedRootContainers
5577
+ } = await compositionBuilder.build(targetContainer);
5528
5578
  rootContainers.push(...generatedRootContainers);
5529
5579
  generatedXML = new XMLSerializer().serializeToString(compositionBuilder.getXML());
5530
5580
  rootContainers.forEach((container) => {
@@ -5539,6 +5589,9 @@ var initBuildCompositionsTool = (reg) => {
5539
5589
  if (configErrors.length) {
5540
5590
  errors.push(...configErrors.map((msg) => new Error(msg)));
5541
5591
  }
5592
+ if (formErrors.length) {
5593
+ errors.push(...formErrors.map((msg) => new Error(msg)));
5594
+ }
5542
5595
  } catch (error) {
5543
5596
  errors.push(error);
5544
5597
  }
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-984",
4
+ "version": "4.3.0-985",
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-984",
41
- "@elementor/editor-controls": "4.3.0-984",
42
- "@elementor/editor-documents": "4.3.0-984",
43
- "@elementor/editor-elements": "4.3.0-984",
44
- "@elementor/editor-interactions": "4.3.0-984",
45
- "@elementor/editor-mcp": "4.3.0-984",
46
- "@elementor/editor-notifications": "4.3.0-984",
47
- "@elementor/editor-props": "4.3.0-984",
48
- "@elementor/editor-responsive": "4.3.0-984",
49
- "@elementor/editor-styles": "4.3.0-984",
50
- "@elementor/editor-styles-repository": "4.3.0-984",
51
- "@elementor/editor-ui": "4.3.0-984",
52
- "@elementor/editor-v1-adapters": "4.3.0-984",
53
- "@elementor/events": "4.3.0-984",
54
- "@elementor/http-client": "4.3.0-984",
55
- "@elementor/schema": "4.3.0-984",
56
- "@elementor/twing": "4.3.0-984",
40
+ "@elementor/editor": "4.3.0-985",
41
+ "@elementor/editor-controls": "4.3.0-985",
42
+ "@elementor/editor-documents": "4.3.0-985",
43
+ "@elementor/editor-elements": "4.3.0-985",
44
+ "@elementor/editor-interactions": "4.3.0-985",
45
+ "@elementor/editor-mcp": "4.3.0-985",
46
+ "@elementor/editor-notifications": "4.3.0-985",
47
+ "@elementor/editor-props": "4.3.0-985",
48
+ "@elementor/editor-responsive": "4.3.0-985",
49
+ "@elementor/editor-styles": "4.3.0-985",
50
+ "@elementor/editor-styles-repository": "4.3.0-985",
51
+ "@elementor/editor-ui": "4.3.0-985",
52
+ "@elementor/editor-v1-adapters": "4.3.0-985",
53
+ "@elementor/events": "4.3.0-985",
54
+ "@elementor/http-client": "4.3.0-985",
55
+ "@elementor/schema": "4.3.0-985",
56
+ "@elementor/twing": "4.3.0-985",
57
57
  "@elementor/ui": "1.37.5",
58
- "@elementor/utils": "4.3.0-984",
59
- "@elementor/wp-media": "4.3.0-984",
58
+ "@elementor/utils": "4.3.0-985",
59
+ "@elementor/wp-media": "4.3.0-985",
60
60
  "@floating-ui/react": "^0.27.5",
61
61
  "@wordpress/i18n": "^5.13.0",
62
62
  "dompurify": "^3.2.6"
@@ -0,0 +1,2 @@
1
+ export const parseXml = ( xml: string ): Document =>
2
+ new DOMParser().parseFromString( `<root>${ xml }</root>`, 'application/xml' );
@@ -11,6 +11,11 @@ import {
11
11
  } from '@elementor/editor-elements';
12
12
  import { type z } from '@elementor/schema';
13
13
 
14
+ import {
15
+ collectEmptyMessageErrors,
16
+ collectFormAncestorErrors,
17
+ collectSubmitButtonErrors,
18
+ } from '../form-structure/utils';
14
19
  import { doUpdateElementProperty } from '../mcp/utils/do-update-element-property';
15
20
  import { mergeCustomCssText } from '../mcp/utils/merge-custom-css';
16
21
  import { RequiredChildrenEnforcer } from './utils/required-children-enforcer';
@@ -294,6 +299,12 @@ export class CompositionBuilder {
294
299
  throw new Error( `Invalid element structure:\n${ childTypeErrors.join( '\n' ) }` );
295
300
  }
296
301
 
302
+ const formErrors = [
303
+ ...collectFormAncestorErrors( this.xml ),
304
+ ...collectSubmitButtonErrors( this.xml ),
305
+ ...collectEmptyMessageErrors( this.xml ),
306
+ ];
307
+
297
308
  const children = Array.from( this.xml.children );
298
309
  for ( const childNode of children ) {
299
310
  const modelTree = this.buildModelTree( childNode, widgetsCache );
@@ -332,6 +343,7 @@ export class CompositionBuilder {
332
343
  return {
333
344
  configErrors,
334
345
  styleErrors,
346
+ formErrors,
335
347
  rootContainers: [ ...this.rootContainers ],
336
348
  };
337
349
  }
@@ -1,7 +1,11 @@
1
1
  import type { V1Element } from '@elementor/editor-elements';
2
2
 
3
+ import { parseXml } from '../../__tests__/parse-xml';
3
4
  import {
4
5
  clipboardRootsAreAtomicForms,
6
+ collectEmptyMessageErrors,
7
+ collectFormAncestorErrors,
8
+ collectSubmitButtonErrors,
5
9
  FORM_ELEMENT_TYPE,
6
10
  getClipboardElementType,
7
11
  movedContainersIncludeAtomicFormRoot,
@@ -67,4 +71,218 @@ describe( 'form-structure utils', () => {
67
71
  expect( clipboardRootsAreAtomicForms( [] ) ).toBe( false );
68
72
  } );
69
73
  } );
74
+
75
+ describe( 'collectFormAncestorErrors', () => {
76
+ it( 'returns no errors when form field is a direct child of e-form', () => {
77
+ const xml = parseXml( `
78
+ <e-form>
79
+ <e-form-input />
80
+ </e-form>
81
+ ` );
82
+ expect( collectFormAncestorErrors( xml ) ).toHaveLength( 0 );
83
+ } );
84
+ it( 'returns no errors when form field is a deep descendant of e-form (valid)', () => {
85
+ const xml = parseXml( `
86
+ <e-form>
87
+ <e-flexbox>
88
+ <e-form-input />
89
+ </e-flexbox>
90
+ </e-form>
91
+ ` );
92
+ expect( collectFormAncestorErrors( xml ) ).toHaveLength( 0 );
93
+ } );
94
+ it( 'returns no errors when e-form wraps multiple containers with fields (valid)', () => {
95
+ const xml = parseXml( `
96
+ <e-flexbox>
97
+ <e-form>
98
+ <e-form-input />
99
+ </e-form>
100
+ </e-flexbox>
101
+ ` );
102
+ expect( collectFormAncestorErrors( xml ) ).toHaveLength( 0 );
103
+ } );
104
+ it( 'returns an error when form field has no e-form ancestor (invalid)', () => {
105
+ const xml = parseXml( `
106
+ <e-flexbox>
107
+ <e-flexbox>
108
+ <e-form-input />
109
+ </e-flexbox>
110
+ </e-flexbox>
111
+ ` );
112
+ const errors = collectFormAncestorErrors( xml );
113
+ expect( errors ).toHaveLength( 1 );
114
+ expect( errors[ 0 ] ).toContain( 'e-form-input' );
115
+ expect( errors[ 0 ] ).toContain( 'e-form' );
116
+ } );
117
+ it( 'returns an error for each orphaned form field', () => {
118
+ const xml = parseXml( `
119
+ <e-flexbox>
120
+ <e-form-input />
121
+ <e-form-label />
122
+ </e-flexbox>
123
+ ` );
124
+ expect( collectFormAncestorErrors( xml ) ).toHaveLength( 2 );
125
+ } );
126
+ it( 'includes configuration-id in the error message when present', () => {
127
+ const xml = parseXml( `
128
+ <e-flexbox>
129
+ <e-form-input configuration-id="my-input" />
130
+ </e-flexbox>
131
+ ` );
132
+ const errors = collectFormAncestorErrors( xml );
133
+ expect( errors[ 0 ] ).toContain( 'configuration-id="my-input"' );
134
+ } );
135
+ it( 'returns no errors when there are no form fields at all', () => {
136
+ const xml = parseXml( `
137
+ <e-flexbox>
138
+ <e-heading />
139
+ </e-flexbox>
140
+ ` );
141
+ expect( collectFormAncestorErrors( xml ) ).toHaveLength( 0 );
142
+ } );
143
+ } );
144
+
145
+ describe( 'collectSubmitButtonErrors', () => {
146
+ it( 'returns no errors when e-form has exactly one submit button', () => {
147
+ const xml = parseXml( `
148
+ <e-form>
149
+ <e-form-input />
150
+ <e-form-submit-button />
151
+ </e-form>
152
+ ` );
153
+ expect( collectSubmitButtonErrors( xml ) ).toHaveLength( 0 );
154
+ } );
155
+
156
+ it( 'returns no errors when submit button is nested inside a container within e-form', () => {
157
+ const xml = parseXml( `
158
+ <e-form>
159
+ <e-flexbox>
160
+ <e-form-submit-button />
161
+ </e-flexbox>
162
+ </e-form>
163
+ ` );
164
+ expect( collectSubmitButtonErrors( xml ) ).toHaveLength( 0 );
165
+ } );
166
+
167
+ it( 'returns an error when e-form has no submit button', () => {
168
+ const xml = parseXml( `
169
+ <e-form>
170
+ <e-form-input />
171
+ </e-form>
172
+ ` );
173
+ const errors = collectSubmitButtonErrors( xml );
174
+ expect( errors ).toHaveLength( 1 );
175
+ expect( errors[ 0 ] ).toContain( 'e-form-submit-button' );
176
+ } );
177
+
178
+ it( 'returns an error when e-form has more than one submit button', () => {
179
+ const xml = parseXml( `
180
+ <e-form>
181
+ <e-form-submit-button />
182
+ <e-form-submit-button />
183
+ </e-form>
184
+ ` );
185
+ const errors = collectSubmitButtonErrors( xml );
186
+ expect( errors ).toHaveLength( 1 );
187
+ expect( errors[ 0 ] ).toContain( '2' );
188
+ } );
189
+
190
+ it( 'returns one error per e-form that is missing a submit button', () => {
191
+ const xml = parseXml( `
192
+ <e-form>
193
+ <e-form-input />
194
+ </e-form>
195
+ <e-form>
196
+ <e-form-input />
197
+ </e-form>
198
+ ` );
199
+ expect( collectSubmitButtonErrors( xml ) ).toHaveLength( 2 );
200
+ } );
201
+
202
+ it( 'returns no errors when there are no e-form elements', () => {
203
+ const xml = parseXml( `
204
+ <e-flexbox>
205
+ <e-heading />
206
+ </e-flexbox>
207
+ ` );
208
+ expect( collectSubmitButtonErrors( xml ) ).toHaveLength( 0 );
209
+ } );
210
+ } );
211
+
212
+ describe( 'collectEmptyMessageErrors', () => {
213
+ it( 'returns no errors when success message has children', () => {
214
+ const xml = parseXml( `
215
+ <e-form>
216
+ <e-form-success-message>
217
+ <e-atomic-paragraph />
218
+ </e-form-success-message>
219
+ </e-form>
220
+ ` );
221
+ expect( collectEmptyMessageErrors( xml ) ).toHaveLength( 0 );
222
+ } );
223
+
224
+ it( 'returns no errors when error message has children', () => {
225
+ const xml = parseXml( `
226
+ <e-form>
227
+ <e-form-error-message>
228
+ <e-atomic-paragraph />
229
+ </e-form-error-message>
230
+ </e-form>
231
+ ` );
232
+ expect( collectEmptyMessageErrors( xml ) ).toHaveLength( 0 );
233
+ } );
234
+
235
+ it( 'returns an error when success message is empty', () => {
236
+ const xml = parseXml( `
237
+ <e-form>
238
+ <e-form-success-message />
239
+ </e-form>
240
+ ` );
241
+ const errors = collectEmptyMessageErrors( xml );
242
+ expect( errors ).toHaveLength( 1 );
243
+ expect( errors[ 0 ] ).toContain( 'e-form-success-message' );
244
+ } );
245
+
246
+ it( 'returns an error when error message is empty', () => {
247
+ const xml = parseXml( `
248
+ <e-form>
249
+ <e-form-error-message />
250
+ </e-form>
251
+ ` );
252
+ const errors = collectEmptyMessageErrors( xml );
253
+ expect( errors ).toHaveLength( 1 );
254
+ expect( errors[ 0 ] ).toContain( 'e-form-error-message' );
255
+ } );
256
+
257
+ it( 'returns two errors when both success and error messages are empty', () => {
258
+ const xml = parseXml( `
259
+ <e-form>
260
+ <e-form-success-message />
261
+ <e-form-error-message />
262
+ </e-form>
263
+ ` );
264
+ expect( collectEmptyMessageErrors( xml ) ).toHaveLength( 2 );
265
+ } );
266
+
267
+ it( 'returns no errors when there are no message elements', () => {
268
+ const xml = parseXml( `
269
+ <e-form>
270
+ <e-form-input />
271
+ <e-form-submit-button />
272
+ </e-form>
273
+ ` );
274
+ expect( collectEmptyMessageErrors( xml ) ).toHaveLength( 0 );
275
+ } );
276
+
277
+ it( 'returns no errors when message element is outside e-form but has children', () => {
278
+ const xml = parseXml( `
279
+ <e-flexbox>
280
+ <e-form-success-message>
281
+ <e-atomic-paragraph />
282
+ </e-form-success-message>
283
+ </e-flexbox>
284
+ ` );
285
+ expect( collectEmptyMessageErrors( xml ) ).toHaveLength( 0 );
286
+ } );
287
+ } );
70
288
  } );
@@ -115,3 +115,49 @@ export function clipboardRootsAreAtomicForms( elements: ClipboardElement[] ): bo
115
115
 
116
116
  return elements.every( ( el ) => getClipboardElementType( el ) === FORM_ELEMENT_TYPE );
117
117
  }
118
+
119
+ export function hasFormAncestor( node: Element ): boolean {
120
+ return node.closest( FORM_ELEMENT_TYPE ) !== null;
121
+ }
122
+
123
+ export function collectFormAncestorErrors( xml: Document ): string[] {
124
+ const errors: string[] = [];
125
+ for ( const node of xml.querySelectorAll( '*' ) ) {
126
+ if ( ! FORM_FIELD_ELEMENT_TYPES.has( node.tagName.toLowerCase() ) ) {
127
+ continue;
128
+ }
129
+ if ( hasFormAncestor( node ) ) {
130
+ continue;
131
+ }
132
+ const id = node.getAttribute( 'configuration-id' );
133
+ errors.push(
134
+ `<${ node.tagName }${
135
+ id ? ` configuration-id="${ id }"` : ''
136
+ }> must be nested inside <e-form> (any ancestor depth is allowed).`
137
+ );
138
+ }
139
+ return errors;
140
+ }
141
+
142
+ export function collectSubmitButtonErrors( xml: Document ): string[] {
143
+ const errors: string[] = [];
144
+ for ( const form of xml.querySelectorAll( 'e-form' ) ) {
145
+ const submitButtons = form.querySelectorAll( 'e-form-submit-button' );
146
+ if ( submitButtons.length === 0 ) {
147
+ errors.push( `<e-form> has no <e-form-submit-button>.` );
148
+ } else if ( submitButtons.length > 1 ) {
149
+ errors.push( `<e-form> has ${ submitButtons.length } submit buttons — only 1 is allowed.` );
150
+ }
151
+ }
152
+ return errors;
153
+ }
154
+
155
+ export function collectEmptyMessageErrors( xml: Document ): string[] {
156
+ const errors: string[] = [];
157
+ for ( const node of xml.querySelectorAll( 'e-form-success-message, e-form-error-message' ) ) {
158
+ if ( node.children.length === 0 ) {
159
+ errors.push( `<${ node.tagName }> must have at least one child element (e.g. <e-atomic-paragraph>).` );
160
+ }
161
+ }
162
+ return errors;
163
+ }
@@ -84,8 +84,11 @@ export const initBuildCompositionsTool = ( reg: MCPRegistryEntry ) => {
84
84
  compositionBuilder.setStylesConfig( stylesConfig );
85
85
  compositionBuilder.setCustomCSS( customCSS );
86
86
 
87
- const { configErrors, rootContainers: generatedRootContainers } =
88
- await compositionBuilder.build( targetContainer );
87
+ const {
88
+ configErrors,
89
+ formErrors,
90
+ rootContainers: generatedRootContainers,
91
+ } = await compositionBuilder.build( targetContainer );
89
92
 
90
93
  rootContainers.push( ...generatedRootContainers );
91
94
  generatedXML = new XMLSerializer().serializeToString( compositionBuilder.getXML() );
@@ -105,6 +108,10 @@ export const initBuildCompositionsTool = ( reg: MCPRegistryEntry ) => {
105
108
  if ( configErrors.length ) {
106
109
  errors.push( ...configErrors.map( ( msg ) => new Error( msg ) ) );
107
110
  }
111
+
112
+ if ( formErrors.length ) {
113
+ errors.push( ...formErrors.map( ( msg ) => new Error( msg ) ) );
114
+ }
108
115
  } catch ( error ) {
109
116
  errors.push( error as Error );
110
117
  }