@elementor/editor-canvas 4.1.0-762 → 4.1.0-764

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
@@ -221,6 +221,15 @@ Variables from the user context ARE NOT SUPPORTED AND WILL RESOLVE IN ERROR.
221
221
  llmGuidance.instructions = "These are the default styles applied to the widget. Override only when necessary.";
222
222
  llmGuidance.default_styles = defaultStyles;
223
223
  }
224
+ const allowedChildTypes = widgetData.allowed_child_types;
225
+ const allWidgets = (0, import_editor_elements.getWidgetsCache)() || {};
226
+ const allowedParents = Object.entries(allWidgets).filter(([, parentConfig]) => parentConfig.allowed_child_types?.includes(widgetType)).map(([parentType]) => parentType);
227
+ if (allowedChildTypes?.length || allowedParents.length) {
228
+ llmGuidance.nesting = {
229
+ ...allowedChildTypes?.length ? { allowed_child_types: allowedChildTypes } : {},
230
+ ...allowedParents.length ? { allowed_parents: allowedParents } : {}
231
+ };
232
+ }
224
233
  return {
225
234
  contents: [
226
235
  {
@@ -3502,7 +3511,6 @@ var CompositionBuilder = class _CompositionBuilder {
3502
3511
  elementStylesConfig = {};
3503
3512
  elementCusomCSS = {};
3504
3513
  rootContainers = [];
3505
- containerElements = [];
3506
3514
  api = {
3507
3515
  createElement: import_editor_elements9.createElement,
3508
3516
  getWidgetsCache: import_editor_elements9.getWidgetsCache,
@@ -3543,45 +3551,49 @@ var CompositionBuilder = class _CompositionBuilder {
3543
3551
  getXML() {
3544
3552
  return this.xml;
3545
3553
  }
3546
- iterateBuild(node, containerElement, childIndex) {
3554
+ buildModelTree(node, widgetsCache) {
3547
3555
  const elementTag = node.tagName;
3548
- const isContainer = this.containerElements.includes(elementTag);
3549
- const parentElType = containerElement.model.get("elType");
3550
- let targetContainer = parentElType === "e-tabs" ? containerElement.children?.[1].children?.[childIndex] || containerElement.children?.[1] : containerElement;
3551
- if (!targetContainer) {
3552
- targetContainer = containerElement;
3553
- }
3554
- const newElement = isContainer ? this.api.createElement({
3555
- container: targetContainer,
3556
- model: {
3557
- elType: elementTag,
3558
- id: (0, import_editor_elements9.generateElementId)(),
3559
- editor_settings: {
3560
- title: node.getAttribute("configuration-id") ?? void 0
3561
- }
3562
- },
3563
- options: { useHistory: false }
3564
- }) : this.api.createElement({
3565
- container: targetContainer,
3566
- model: {
3567
- elType: "widget",
3568
- widgetType: elementTag,
3569
- id: (0, import_editor_elements9.generateElementId)(),
3570
- editor_settings: {
3571
- title: node.getAttribute("configuration-id") ?? void 0
3556
+ const isWidget = widgetsCache[elementTag]?.elType === "widget";
3557
+ const id = this.api.generateElementId();
3558
+ const children = Array.from(node.children).map((child) => this.buildModelTree(child, widgetsCache));
3559
+ node.setAttribute("id", id);
3560
+ const base = {
3561
+ id,
3562
+ skipDefaultChildren: true,
3563
+ elements: children,
3564
+ editor_settings: {
3565
+ title: node.getAttribute("configuration-id") ?? void 0
3566
+ }
3567
+ };
3568
+ if (isWidget) {
3569
+ return { ...base, elType: "widget", widgetType: elementTag };
3570
+ }
3571
+ return { ...base, elType: elementTag };
3572
+ }
3573
+ async awaitViewRender(element) {
3574
+ const view = element.view;
3575
+ if (view?._currentRenderPromise instanceof Promise) {
3576
+ await view._currentRenderPromise;
3577
+ } else {
3578
+ await Promise.resolve();
3579
+ }
3580
+ }
3581
+ validateChildTypes(node, widgetsCache) {
3582
+ const errors = [];
3583
+ const allowedChildTypes = widgetsCache[node.tagName]?.allowed_child_types;
3584
+ if (allowedChildTypes?.length) {
3585
+ for (const child of Array.from(node.children)) {
3586
+ if (!allowedChildTypes.includes(child.tagName)) {
3587
+ errors.push(
3588
+ `"${child.tagName}" is not allowed as a child of "${node.tagName}". Allowed: ${allowedChildTypes.join(", ")}`
3589
+ );
3572
3590
  }
3573
- },
3574
- options: { useHistory: false }
3575
- });
3576
- if (containerElement.id === "document") {
3577
- this.rootContainers.push(newElement);
3591
+ }
3578
3592
  }
3579
- node.setAttribute("id", newElement.id);
3580
- let currentChild = 0;
3581
- for (const childNode of Array.from(node.children)) {
3582
- this.iterateBuild(childNode, newElement, currentChild);
3583
- currentChild++;
3593
+ for (const child of Array.from(node.children)) {
3594
+ errors.push(...this.validateChildTypes(child, widgetsCache));
3584
3595
  }
3596
+ return errors;
3585
3597
  }
3586
3598
  findSchemaForNode(node) {
3587
3599
  const widgetsCache = this.api.getWidgetsCache() || {};
@@ -3607,63 +3619,31 @@ var CompositionBuilder = class _CompositionBuilder {
3607
3619
  node
3608
3620
  };
3609
3621
  }
3610
- applyStyles() {
3611
- const errors = [];
3622
+ async applyProperties() {
3623
+ const configErrors = [];
3624
+ const styleErrors = [];
3612
3625
  const invalidStyles = {};
3613
- for (const [styleId, styleConfig] of Object.entries(this.elementStylesConfig)) {
3614
- const { element, node } = this.matchNodeByConfigId(styleId);
3615
- const validStylesPropValues = {};
3616
- for (const [styleName, stylePropValue] of Object.entries(styleConfig)) {
3617
- const { valid, errors: validationErrors } = validateInput.validateStyles({
3618
- [styleName]: stylePropValue
3619
- });
3620
- if (!valid) {
3621
- if (styleConfig.$intention) {
3622
- invalidStyles[element.id] = invalidStyles[element.id] || [];
3623
- invalidStyles[element.id].push(styleName);
3624
- }
3625
- errors.push(...validationErrors || []);
3626
- } else {
3627
- validStylesPropValues[styleName] = stylePropValue;
3626
+ const allConfigIds = /* @__PURE__ */ new Set([
3627
+ ...Object.keys(this.elementConfig),
3628
+ ...Object.keys(this.elementStylesConfig),
3629
+ ...Object.keys(this.elementCusomCSS)
3630
+ ]);
3631
+ for (const configId of allConfigIds) {
3632
+ let element, node;
3633
+ try {
3634
+ ({ element, node } = this.matchNodeByConfigId(configId));
3635
+ } catch (matchErr) {
3636
+ const msg = matchErr.message;
3637
+ if (this.elementConfig[configId]) {
3638
+ configErrors.push(msg);
3639
+ }
3640
+ if (this.elementStylesConfig[configId] || this.elementCusomCSS[configId]) {
3641
+ styleErrors.push(msg);
3628
3642
  }
3629
- }
3630
- if (Object.keys(validStylesPropValues).length === 0) {
3631
3643
  continue;
3632
3644
  }
3633
- try {
3634
- this.api.doUpdateElementProperty({
3635
- elementId: element.id,
3636
- propertyName: "_styles",
3637
- propertyValue: validStylesPropValues,
3638
- elementType: node.tagName
3639
- });
3640
- } catch (error) {
3641
- errors.push(String(error));
3642
- }
3643
- }
3644
- for (const [customCSSId, customCSS] of Object.entries(this.elementCusomCSS)) {
3645
- const { element, node } = this.matchNodeByConfigId(customCSSId);
3646
- this.api.doUpdateElementProperty({
3647
- elementId: element.id,
3648
- propertyName: "_styles",
3649
- propertyValue: { custom_css: customCSS },
3650
- elementType: node.tagName
3651
- });
3652
- }
3653
- return {
3654
- errors,
3655
- invalidStyles
3656
- };
3657
- }
3658
- applyConfigs() {
3659
- const errors = [];
3660
- for (const [configId, config] of Object.entries(this.elementConfig)) {
3661
- const { element, node } = this.matchNodeByConfigId(configId);
3662
- const propSchema = this.findSchemaForNode(node);
3663
- const result = validateInput.validateProps(propSchema, config);
3664
- if (!result.valid && result.errors?.length) {
3665
- errors.push(...result.errors);
3666
- } else {
3645
+ const config = this.elementConfig[configId];
3646
+ if (config) {
3667
3647
  for (const [propertyName, propertyValue] of Object.entries(config)) {
3668
3648
  try {
3669
3649
  this.api.doUpdateElementProperty({
@@ -3673,30 +3653,84 @@ var CompositionBuilder = class _CompositionBuilder {
3673
3653
  elementType: node.tagName
3674
3654
  });
3675
3655
  } catch (error) {
3676
- errors.push(error.message);
3656
+ configErrors.push(error.message);
3657
+ }
3658
+ }
3659
+ }
3660
+ const styleConfig = this.elementStylesConfig[configId];
3661
+ if (styleConfig) {
3662
+ const validStylesPropValues = {};
3663
+ for (const [styleName, stylePropValue] of Object.entries(styleConfig)) {
3664
+ const { valid, errors: validationErrors } = validateInput.validateStyles({
3665
+ [styleName]: stylePropValue
3666
+ });
3667
+ if (!valid) {
3668
+ if (styleConfig.$intention) {
3669
+ invalidStyles[element.id] = invalidStyles[element.id] || [];
3670
+ invalidStyles[element.id].push(styleName);
3671
+ }
3672
+ styleErrors.push(...validationErrors || []);
3673
+ } else {
3674
+ validStylesPropValues[styleName] = stylePropValue;
3675
+ }
3676
+ }
3677
+ if (Object.keys(validStylesPropValues).length > 0) {
3678
+ try {
3679
+ this.api.doUpdateElementProperty({
3680
+ elementId: element.id,
3681
+ propertyName: "_styles",
3682
+ propertyValue: validStylesPropValues,
3683
+ elementType: node.tagName
3684
+ });
3685
+ } catch (error) {
3686
+ styleErrors.push(String(error));
3677
3687
  }
3678
3688
  }
3679
3689
  }
3690
+ const customCSS = this.elementCusomCSS[configId];
3691
+ if (customCSS) {
3692
+ try {
3693
+ this.api.doUpdateElementProperty({
3694
+ elementId: element.id,
3695
+ propertyName: "_styles",
3696
+ propertyValue: { custom_css: customCSS },
3697
+ elementType: node.tagName
3698
+ });
3699
+ } catch (cssErr) {
3700
+ styleErrors.push(String(cssErr));
3701
+ }
3702
+ }
3703
+ await this.awaitViewRender(element);
3680
3704
  }
3681
- return errors;
3705
+ return { configErrors, styleErrors, invalidStyles };
3682
3706
  }
3683
- build(rootContainer) {
3707
+ async build(rootContainer) {
3684
3708
  const widgetsCache = this.api.getWidgetsCache() || {};
3685
- const CONTAINER_ELEMENTS = Object.values(widgetsCache).filter((widget) => widget.meta?.is_container).map((widget) => widget.elType).filter((x) => typeof x === "string");
3686
- this.containerElements = CONTAINER_ELEMENTS;
3687
3709
  new Set(this.xml.querySelectorAll("*")).forEach((node) => {
3688
3710
  if (!widgetsCache[node.tagName]) {
3689
3711
  throw new Error(`Unknown widget type: ${node.tagName}`);
3690
3712
  }
3691
3713
  });
3714
+ const childTypeErrors = [];
3715
+ for (const rootChild of Array.from(this.xml.children)) {
3716
+ childTypeErrors.push(...this.validateChildTypes(rootChild, widgetsCache));
3717
+ }
3718
+ if (childTypeErrors.length) {
3719
+ throw new Error(`Invalid element structure:
3720
+ ${childTypeErrors.join("\n")}`);
3721
+ }
3692
3722
  const children = Array.from(this.xml.children);
3693
- let currentChild = 0;
3694
3723
  for (const childNode of children) {
3695
- this.iterateBuild(childNode, rootContainer, currentChild);
3696
- currentChild++;
3724
+ const modelTree = this.buildModelTree(childNode, widgetsCache);
3725
+ const newElement = this.api.createElement({
3726
+ container: rootContainer,
3727
+ model: modelTree,
3728
+ options: { useHistory: false }
3729
+ });
3730
+ this.rootContainers.push(newElement);
3731
+ await this.awaitViewRender(newElement);
3697
3732
  }
3698
- const { errors: styleErrors, invalidStyles } = this.applyStyles();
3699
- const configErrors = this.applyConfigs();
3733
+ const { configErrors, styleErrors, invalidStyles } = await this.applyProperties();
3700
3734
  return {
3701
3735
  configErrors,
3702
3736
  styleErrors,
@@ -3736,6 +3770,12 @@ var generatePrompt = () => {
3736
3770
  - Every element needs unique "configuration-id"
3737
3771
  - No attributes, classes, IDs, or text nodes in XML
3738
3772
 
3773
+ ## NESTED ELEMENTS
3774
+ Some elements have internal tree structures (nesting). When using these elements, you MUST build the FULL tree in XML.
3775
+ - Check \`llm_guidance.nesting\` in widget schemas for structure requirements
3776
+ - \`allowed_child_types\` lists which element types can be nested inside
3777
+ - \`allowed_parents\` lists which element types this element can be placed inside
3778
+
3739
3779
  # CONFIGURATION
3740
3780
  - Map configuration-id \u2192 elementConfig (props) + stylesConfig (layout only)
3741
3781
  - All PropValues require \`$$type\` matching schema
@@ -3921,17 +3961,9 @@ var initBuildCompositionsTool = (reg) => {
3921
3961
  compositionBuilder.setElementConfig(elementConfig);
3922
3962
  compositionBuilder.setStylesConfig(stylesConfig);
3923
3963
  compositionBuilder.setCustomCSS(customCSS);
3924
- const {
3925
- configErrors,
3926
- invalidStyles,
3927
- rootContainers: generatedRootContainers
3928
- } = compositionBuilder.build(targetContainer);
3964
+ const { invalidStyles, rootContainers: generatedRootContainers } = await compositionBuilder.build(targetContainer);
3929
3965
  rootContainers.push(...generatedRootContainers);
3930
3966
  generatedXML = new XMLSerializer().serializeToString(compositionBuilder.getXML());
3931
- if (configErrors.length) {
3932
- errors.push(...configErrors.map((e) => new Error(e)));
3933
- throw new Error("Configuration errors occurred during composition building.");
3934
- }
3935
3967
  Object.entries(invalidStyles).forEach(([elementId, rawCssRules]) => {
3936
3968
  const customCss = {
3937
3969
  value: rawCssRules.join(";\n")
package/dist/index.mjs CHANGED
@@ -166,6 +166,15 @@ Variables from the user context ARE NOT SUPPORTED AND WILL RESOLVE IN ERROR.
166
166
  llmGuidance.instructions = "These are the default styles applied to the widget. Override only when necessary.";
167
167
  llmGuidance.default_styles = defaultStyles;
168
168
  }
169
+ const allowedChildTypes = widgetData.allowed_child_types;
170
+ const allWidgets = getWidgetsCache() || {};
171
+ const allowedParents = Object.entries(allWidgets).filter(([, parentConfig]) => parentConfig.allowed_child_types?.includes(widgetType)).map(([parentType]) => parentType);
172
+ if (allowedChildTypes?.length || allowedParents.length) {
173
+ llmGuidance.nesting = {
174
+ ...allowedChildTypes?.length ? { allowed_child_types: allowedChildTypes } : {},
175
+ ...allowedParents.length ? { allowed_parents: allowedParents } : {}
176
+ };
177
+ }
169
178
  return {
170
179
  contents: [
171
180
  {
@@ -3488,7 +3497,6 @@ var CompositionBuilder = class _CompositionBuilder {
3488
3497
  elementStylesConfig = {};
3489
3498
  elementCusomCSS = {};
3490
3499
  rootContainers = [];
3491
- containerElements = [];
3492
3500
  api = {
3493
3501
  createElement: createElement7,
3494
3502
  getWidgetsCache: getWidgetsCache5,
@@ -3529,45 +3537,49 @@ var CompositionBuilder = class _CompositionBuilder {
3529
3537
  getXML() {
3530
3538
  return this.xml;
3531
3539
  }
3532
- iterateBuild(node, containerElement, childIndex) {
3540
+ buildModelTree(node, widgetsCache) {
3533
3541
  const elementTag = node.tagName;
3534
- const isContainer = this.containerElements.includes(elementTag);
3535
- const parentElType = containerElement.model.get("elType");
3536
- let targetContainer = parentElType === "e-tabs" ? containerElement.children?.[1].children?.[childIndex] || containerElement.children?.[1] : containerElement;
3537
- if (!targetContainer) {
3538
- targetContainer = containerElement;
3539
- }
3540
- const newElement = isContainer ? this.api.createElement({
3541
- container: targetContainer,
3542
- model: {
3543
- elType: elementTag,
3544
- id: generateElementId(),
3545
- editor_settings: {
3546
- title: node.getAttribute("configuration-id") ?? void 0
3547
- }
3548
- },
3549
- options: { useHistory: false }
3550
- }) : this.api.createElement({
3551
- container: targetContainer,
3552
- model: {
3553
- elType: "widget",
3554
- widgetType: elementTag,
3555
- id: generateElementId(),
3556
- editor_settings: {
3557
- title: node.getAttribute("configuration-id") ?? void 0
3542
+ const isWidget = widgetsCache[elementTag]?.elType === "widget";
3543
+ const id = this.api.generateElementId();
3544
+ const children = Array.from(node.children).map((child) => this.buildModelTree(child, widgetsCache));
3545
+ node.setAttribute("id", id);
3546
+ const base = {
3547
+ id,
3548
+ skipDefaultChildren: true,
3549
+ elements: children,
3550
+ editor_settings: {
3551
+ title: node.getAttribute("configuration-id") ?? void 0
3552
+ }
3553
+ };
3554
+ if (isWidget) {
3555
+ return { ...base, elType: "widget", widgetType: elementTag };
3556
+ }
3557
+ return { ...base, elType: elementTag };
3558
+ }
3559
+ async awaitViewRender(element) {
3560
+ const view = element.view;
3561
+ if (view?._currentRenderPromise instanceof Promise) {
3562
+ await view._currentRenderPromise;
3563
+ } else {
3564
+ await Promise.resolve();
3565
+ }
3566
+ }
3567
+ validateChildTypes(node, widgetsCache) {
3568
+ const errors = [];
3569
+ const allowedChildTypes = widgetsCache[node.tagName]?.allowed_child_types;
3570
+ if (allowedChildTypes?.length) {
3571
+ for (const child of Array.from(node.children)) {
3572
+ if (!allowedChildTypes.includes(child.tagName)) {
3573
+ errors.push(
3574
+ `"${child.tagName}" is not allowed as a child of "${node.tagName}". Allowed: ${allowedChildTypes.join(", ")}`
3575
+ );
3558
3576
  }
3559
- },
3560
- options: { useHistory: false }
3561
- });
3562
- if (containerElement.id === "document") {
3563
- this.rootContainers.push(newElement);
3577
+ }
3564
3578
  }
3565
- node.setAttribute("id", newElement.id);
3566
- let currentChild = 0;
3567
- for (const childNode of Array.from(node.children)) {
3568
- this.iterateBuild(childNode, newElement, currentChild);
3569
- currentChild++;
3579
+ for (const child of Array.from(node.children)) {
3580
+ errors.push(...this.validateChildTypes(child, widgetsCache));
3570
3581
  }
3582
+ return errors;
3571
3583
  }
3572
3584
  findSchemaForNode(node) {
3573
3585
  const widgetsCache = this.api.getWidgetsCache() || {};
@@ -3593,63 +3605,31 @@ var CompositionBuilder = class _CompositionBuilder {
3593
3605
  node
3594
3606
  };
3595
3607
  }
3596
- applyStyles() {
3597
- const errors = [];
3608
+ async applyProperties() {
3609
+ const configErrors = [];
3610
+ const styleErrors = [];
3598
3611
  const invalidStyles = {};
3599
- for (const [styleId, styleConfig] of Object.entries(this.elementStylesConfig)) {
3600
- const { element, node } = this.matchNodeByConfigId(styleId);
3601
- const validStylesPropValues = {};
3602
- for (const [styleName, stylePropValue] of Object.entries(styleConfig)) {
3603
- const { valid, errors: validationErrors } = validateInput.validateStyles({
3604
- [styleName]: stylePropValue
3605
- });
3606
- if (!valid) {
3607
- if (styleConfig.$intention) {
3608
- invalidStyles[element.id] = invalidStyles[element.id] || [];
3609
- invalidStyles[element.id].push(styleName);
3610
- }
3611
- errors.push(...validationErrors || []);
3612
- } else {
3613
- validStylesPropValues[styleName] = stylePropValue;
3612
+ const allConfigIds = /* @__PURE__ */ new Set([
3613
+ ...Object.keys(this.elementConfig),
3614
+ ...Object.keys(this.elementStylesConfig),
3615
+ ...Object.keys(this.elementCusomCSS)
3616
+ ]);
3617
+ for (const configId of allConfigIds) {
3618
+ let element, node;
3619
+ try {
3620
+ ({ element, node } = this.matchNodeByConfigId(configId));
3621
+ } catch (matchErr) {
3622
+ const msg = matchErr.message;
3623
+ if (this.elementConfig[configId]) {
3624
+ configErrors.push(msg);
3625
+ }
3626
+ if (this.elementStylesConfig[configId] || this.elementCusomCSS[configId]) {
3627
+ styleErrors.push(msg);
3614
3628
  }
3615
- }
3616
- if (Object.keys(validStylesPropValues).length === 0) {
3617
3629
  continue;
3618
3630
  }
3619
- try {
3620
- this.api.doUpdateElementProperty({
3621
- elementId: element.id,
3622
- propertyName: "_styles",
3623
- propertyValue: validStylesPropValues,
3624
- elementType: node.tagName
3625
- });
3626
- } catch (error) {
3627
- errors.push(String(error));
3628
- }
3629
- }
3630
- for (const [customCSSId, customCSS] of Object.entries(this.elementCusomCSS)) {
3631
- const { element, node } = this.matchNodeByConfigId(customCSSId);
3632
- this.api.doUpdateElementProperty({
3633
- elementId: element.id,
3634
- propertyName: "_styles",
3635
- propertyValue: { custom_css: customCSS },
3636
- elementType: node.tagName
3637
- });
3638
- }
3639
- return {
3640
- errors,
3641
- invalidStyles
3642
- };
3643
- }
3644
- applyConfigs() {
3645
- const errors = [];
3646
- for (const [configId, config] of Object.entries(this.elementConfig)) {
3647
- const { element, node } = this.matchNodeByConfigId(configId);
3648
- const propSchema = this.findSchemaForNode(node);
3649
- const result = validateInput.validateProps(propSchema, config);
3650
- if (!result.valid && result.errors?.length) {
3651
- errors.push(...result.errors);
3652
- } else {
3631
+ const config = this.elementConfig[configId];
3632
+ if (config) {
3653
3633
  for (const [propertyName, propertyValue] of Object.entries(config)) {
3654
3634
  try {
3655
3635
  this.api.doUpdateElementProperty({
@@ -3659,30 +3639,84 @@ var CompositionBuilder = class _CompositionBuilder {
3659
3639
  elementType: node.tagName
3660
3640
  });
3661
3641
  } catch (error) {
3662
- errors.push(error.message);
3642
+ configErrors.push(error.message);
3643
+ }
3644
+ }
3645
+ }
3646
+ const styleConfig = this.elementStylesConfig[configId];
3647
+ if (styleConfig) {
3648
+ const validStylesPropValues = {};
3649
+ for (const [styleName, stylePropValue] of Object.entries(styleConfig)) {
3650
+ const { valid, errors: validationErrors } = validateInput.validateStyles({
3651
+ [styleName]: stylePropValue
3652
+ });
3653
+ if (!valid) {
3654
+ if (styleConfig.$intention) {
3655
+ invalidStyles[element.id] = invalidStyles[element.id] || [];
3656
+ invalidStyles[element.id].push(styleName);
3657
+ }
3658
+ styleErrors.push(...validationErrors || []);
3659
+ } else {
3660
+ validStylesPropValues[styleName] = stylePropValue;
3661
+ }
3662
+ }
3663
+ if (Object.keys(validStylesPropValues).length > 0) {
3664
+ try {
3665
+ this.api.doUpdateElementProperty({
3666
+ elementId: element.id,
3667
+ propertyName: "_styles",
3668
+ propertyValue: validStylesPropValues,
3669
+ elementType: node.tagName
3670
+ });
3671
+ } catch (error) {
3672
+ styleErrors.push(String(error));
3663
3673
  }
3664
3674
  }
3665
3675
  }
3676
+ const customCSS = this.elementCusomCSS[configId];
3677
+ if (customCSS) {
3678
+ try {
3679
+ this.api.doUpdateElementProperty({
3680
+ elementId: element.id,
3681
+ propertyName: "_styles",
3682
+ propertyValue: { custom_css: customCSS },
3683
+ elementType: node.tagName
3684
+ });
3685
+ } catch (cssErr) {
3686
+ styleErrors.push(String(cssErr));
3687
+ }
3688
+ }
3689
+ await this.awaitViewRender(element);
3666
3690
  }
3667
- return errors;
3691
+ return { configErrors, styleErrors, invalidStyles };
3668
3692
  }
3669
- build(rootContainer) {
3693
+ async build(rootContainer) {
3670
3694
  const widgetsCache = this.api.getWidgetsCache() || {};
3671
- const CONTAINER_ELEMENTS = Object.values(widgetsCache).filter((widget) => widget.meta?.is_container).map((widget) => widget.elType).filter((x) => typeof x === "string");
3672
- this.containerElements = CONTAINER_ELEMENTS;
3673
3695
  new Set(this.xml.querySelectorAll("*")).forEach((node) => {
3674
3696
  if (!widgetsCache[node.tagName]) {
3675
3697
  throw new Error(`Unknown widget type: ${node.tagName}`);
3676
3698
  }
3677
3699
  });
3700
+ const childTypeErrors = [];
3701
+ for (const rootChild of Array.from(this.xml.children)) {
3702
+ childTypeErrors.push(...this.validateChildTypes(rootChild, widgetsCache));
3703
+ }
3704
+ if (childTypeErrors.length) {
3705
+ throw new Error(`Invalid element structure:
3706
+ ${childTypeErrors.join("\n")}`);
3707
+ }
3678
3708
  const children = Array.from(this.xml.children);
3679
- let currentChild = 0;
3680
3709
  for (const childNode of children) {
3681
- this.iterateBuild(childNode, rootContainer, currentChild);
3682
- currentChild++;
3710
+ const modelTree = this.buildModelTree(childNode, widgetsCache);
3711
+ const newElement = this.api.createElement({
3712
+ container: rootContainer,
3713
+ model: modelTree,
3714
+ options: { useHistory: false }
3715
+ });
3716
+ this.rootContainers.push(newElement);
3717
+ await this.awaitViewRender(newElement);
3683
3718
  }
3684
- const { errors: styleErrors, invalidStyles } = this.applyStyles();
3685
- const configErrors = this.applyConfigs();
3719
+ const { configErrors, styleErrors, invalidStyles } = await this.applyProperties();
3686
3720
  return {
3687
3721
  configErrors,
3688
3722
  styleErrors,
@@ -3722,6 +3756,12 @@ var generatePrompt = () => {
3722
3756
  - Every element needs unique "configuration-id"
3723
3757
  - No attributes, classes, IDs, or text nodes in XML
3724
3758
 
3759
+ ## NESTED ELEMENTS
3760
+ Some elements have internal tree structures (nesting). When using these elements, you MUST build the FULL tree in XML.
3761
+ - Check \`llm_guidance.nesting\` in widget schemas for structure requirements
3762
+ - \`allowed_child_types\` lists which element types can be nested inside
3763
+ - \`allowed_parents\` lists which element types this element can be placed inside
3764
+
3725
3765
  # CONFIGURATION
3726
3766
  - Map configuration-id \u2192 elementConfig (props) + stylesConfig (layout only)
3727
3767
  - All PropValues require \`$$type\` matching schema
@@ -3907,17 +3947,9 @@ var initBuildCompositionsTool = (reg) => {
3907
3947
  compositionBuilder.setElementConfig(elementConfig);
3908
3948
  compositionBuilder.setStylesConfig(stylesConfig);
3909
3949
  compositionBuilder.setCustomCSS(customCSS);
3910
- const {
3911
- configErrors,
3912
- invalidStyles,
3913
- rootContainers: generatedRootContainers
3914
- } = compositionBuilder.build(targetContainer);
3950
+ const { invalidStyles, rootContainers: generatedRootContainers } = await compositionBuilder.build(targetContainer);
3915
3951
  rootContainers.push(...generatedRootContainers);
3916
3952
  generatedXML = new XMLSerializer().serializeToString(compositionBuilder.getXML());
3917
- if (configErrors.length) {
3918
- errors.push(...configErrors.map((e) => new Error(e)));
3919
- throw new Error("Configuration errors occurred during composition building.");
3920
- }
3921
3953
  Object.entries(invalidStyles).forEach(([elementId, rawCssRules]) => {
3922
3954
  const customCss = {
3923
3955
  value: rawCssRules.join(";\n")
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.1.0-762",
4
+ "version": "4.1.0-764",
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.1.0-762",
40
+ "@elementor/editor": "4.1.0-764",
41
41
  "dompurify": "^3.2.6",
42
- "@elementor/editor-controls": "4.1.0-762",
43
- "@elementor/editor-documents": "4.1.0-762",
44
- "@elementor/editor-elements": "4.1.0-762",
45
- "@elementor/editor-interactions": "4.1.0-762",
46
- "@elementor/editor-mcp": "4.1.0-762",
47
- "@elementor/editor-notifications": "4.1.0-762",
48
- "@elementor/editor-props": "4.1.0-762",
49
- "@elementor/editor-responsive": "4.1.0-762",
50
- "@elementor/editor-styles": "4.1.0-762",
51
- "@elementor/editor-styles-repository": "4.1.0-762",
52
- "@elementor/editor-ui": "4.1.0-762",
53
- "@elementor/editor-v1-adapters": "4.1.0-762",
54
- "@elementor/schema": "4.1.0-762",
55
- "@elementor/twing": "4.1.0-762",
42
+ "@elementor/editor-controls": "4.1.0-764",
43
+ "@elementor/editor-documents": "4.1.0-764",
44
+ "@elementor/editor-elements": "4.1.0-764",
45
+ "@elementor/editor-interactions": "4.1.0-764",
46
+ "@elementor/editor-mcp": "4.1.0-764",
47
+ "@elementor/editor-notifications": "4.1.0-764",
48
+ "@elementor/editor-props": "4.1.0-764",
49
+ "@elementor/editor-responsive": "4.1.0-764",
50
+ "@elementor/editor-styles": "4.1.0-764",
51
+ "@elementor/editor-styles-repository": "4.1.0-764",
52
+ "@elementor/editor-ui": "4.1.0-764",
53
+ "@elementor/editor-v1-adapters": "4.1.0-764",
54
+ "@elementor/schema": "4.1.0-764",
55
+ "@elementor/twing": "4.1.0-764",
56
56
  "@elementor/ui": "1.36.17",
57
- "@elementor/utils": "4.1.0-762",
58
- "@elementor/wp-media": "4.1.0-762",
57
+ "@elementor/utils": "4.1.0-764",
58
+ "@elementor/wp-media": "4.1.0-764",
59
59
  "@floating-ui/react": "^0.27.5",
60
60
  "@wordpress/i18n": "^5.13.0"
61
61
  },
@@ -1,9 +1,11 @@
1
1
  import {
2
2
  createElement,
3
+ type CreateElementParams,
3
4
  generateElementId,
4
5
  getContainer,
5
6
  getWidgetsCache,
6
7
  type V1Element,
8
+ type V1ElementConfig,
7
9
  } from '@elementor/editor-elements';
8
10
  import { type z } from '@elementor/schema';
9
11
 
@@ -34,7 +36,6 @@ export class CompositionBuilder {
34
36
  private elementStylesConfig: Record< string, Record< string, AnyValue > > = {};
35
37
  private elementCusomCSS: Record< string, string > = {};
36
38
  private rootContainers: V1Element[] = [];
37
- private containerElements: string[] = [];
38
39
  private api: API = {
39
40
  createElement,
40
41
  getWidgetsCache,
@@ -82,50 +83,63 @@ export class CompositionBuilder {
82
83
  return this.xml;
83
84
  }
84
85
 
85
- private iterateBuild( node: Element, containerElement: V1Element, childIndex: number ) {
86
+ private buildModelTree(
87
+ node: Element,
88
+ widgetsCache: Record< string, V1ElementConfig >
89
+ ): Record< string, unknown > {
86
90
  const elementTag = node.tagName;
87
- const isContainer = this.containerElements.includes( elementTag );
88
- const parentElType = containerElement.model.get( 'elType' );
89
- let targetContainer =
90
- parentElType === 'e-tabs'
91
- ? containerElement.children?.[ 1 ].children?.[ childIndex ] || containerElement.children?.[ 1 ]
92
- : containerElement;
93
- if ( ! targetContainer ) {
94
- targetContainer = containerElement;
91
+ const isWidget = widgetsCache[ elementTag ]?.elType === 'widget';
92
+ const id = this.api.generateElementId();
93
+ const children = Array.from( node.children ).map( ( child ) => this.buildModelTree( child, widgetsCache ) );
94
+
95
+ node.setAttribute( 'id', id );
96
+
97
+ const base = {
98
+ id,
99
+ skipDefaultChildren: true,
100
+ elements: children,
101
+ editor_settings: {
102
+ title: node.getAttribute( 'configuration-id' ) ?? undefined,
103
+ },
104
+ };
105
+
106
+ if ( isWidget ) {
107
+ return { ...base, elType: 'widget' as const, widgetType: elementTag };
95
108
  }
96
- const newElement = isContainer
97
- ? this.api.createElement( {
98
- container: targetContainer,
99
- model: {
100
- elType: elementTag,
101
- id: generateElementId(),
102
- editor_settings: {
103
- title: node.getAttribute( 'configuration-id' ) ?? undefined,
104
- },
105
- },
106
- options: { useHistory: false },
107
- } )
108
- : this.api.createElement( {
109
- container: targetContainer,
110
- model: {
111
- elType: 'widget',
112
- widgetType: elementTag,
113
- id: generateElementId(),
114
- editor_settings: {
115
- title: node.getAttribute( 'configuration-id' ) ?? undefined,
116
- },
117
- },
118
- options: { useHistory: false },
119
- } );
120
- if ( containerElement.id === 'document' ) {
121
- this.rootContainers.push( newElement );
109
+
110
+ return { ...base, elType: elementTag };
111
+ }
112
+
113
+ private async awaitViewRender( element: V1Element ) {
114
+ const view = element.view as Record< string, unknown > | undefined;
115
+ if ( view?._currentRenderPromise instanceof Promise ) {
116
+ await view._currentRenderPromise;
117
+ } else {
118
+ await Promise.resolve();
122
119
  }
123
- node.setAttribute( 'id', newElement.id );
124
- let currentChild = 0;
125
- for ( const childNode of Array.from( node.children ) ) {
126
- this.iterateBuild( childNode, newElement, currentChild );
127
- currentChild++;
120
+ }
121
+
122
+ private validateChildTypes( node: Element, widgetsCache: Record< string, V1ElementConfig > ): string[] {
123
+ const errors: string[] = [];
124
+ const allowedChildTypes = widgetsCache[ node.tagName ]?.allowed_child_types;
125
+
126
+ if ( allowedChildTypes?.length ) {
127
+ for ( const child of Array.from( node.children ) ) {
128
+ if ( ! allowedChildTypes.includes( child.tagName ) ) {
129
+ errors.push(
130
+ `"${ child.tagName }" is not allowed as a child of "${
131
+ node.tagName
132
+ }". Allowed: ${ allowedChildTypes.join( ', ' ) }`
133
+ );
134
+ }
135
+ }
136
+ }
137
+
138
+ for ( const child of Array.from( node.children ) ) {
139
+ errors.push( ...this.validateChildTypes( child, widgetsCache ) );
128
140
  }
141
+
142
+ return errors;
129
143
  }
130
144
 
131
145
  private findSchemaForNode( node: Element ) {
@@ -154,64 +168,34 @@ export class CompositionBuilder {
154
168
  };
155
169
  }
156
170
 
157
- applyStyles() {
158
- const errors: string[] = [];
171
+ private async applyProperties() {
172
+ const configErrors: string[] = [];
173
+ const styleErrors: string[] = [];
159
174
  const invalidStyles: Record< string, string[] > = {};
160
- for ( const [ styleId, styleConfig ] of Object.entries( this.elementStylesConfig ) ) {
161
- const { element, node } = this.matchNodeByConfigId( styleId );
162
- const validStylesPropValues: Record< string, AnyValue > = {};
163
- for ( const [ styleName, stylePropValue ] of Object.entries( styleConfig ) ) {
164
- const { valid, errors: validationErrors } = validateInput.validateStyles( {
165
- [ styleName ]: stylePropValue,
166
- } );
167
- if ( ! valid ) {
168
- if ( styleConfig.$intention ) {
169
- invalidStyles[ element.id ] = invalidStyles[ element.id ] || [];
170
- invalidStyles[ element.id ].push( styleName );
171
- }
172
- errors.push( ...( validationErrors || [] ) );
173
- } else {
174
- validStylesPropValues[ styleName ] = stylePropValue;
175
+
176
+ const allConfigIds = new Set( [
177
+ ...Object.keys( this.elementConfig ),
178
+ ...Object.keys( this.elementStylesConfig ),
179
+ ...Object.keys( this.elementCusomCSS ),
180
+ ] );
181
+
182
+ for ( const configId of allConfigIds ) {
183
+ let element, node;
184
+ try {
185
+ ( { element, node } = this.matchNodeByConfigId( configId ) );
186
+ } catch ( matchErr ) {
187
+ const msg = ( matchErr as Error ).message;
188
+ if ( this.elementConfig[ configId ] ) {
189
+ configErrors.push( msg );
190
+ }
191
+ if ( this.elementStylesConfig[ configId ] || this.elementCusomCSS[ configId ] ) {
192
+ styleErrors.push( msg );
175
193
  }
176
- }
177
- if ( Object.keys( validStylesPropValues ).length === 0 ) {
178
194
  continue;
179
195
  }
180
- try {
181
- this.api.doUpdateElementProperty( {
182
- elementId: element.id,
183
- propertyName: '_styles',
184
- propertyValue: validStylesPropValues,
185
- elementType: node.tagName,
186
- } );
187
- } catch ( error ) {
188
- errors.push( String( error ) );
189
- }
190
- }
191
- for ( const [ customCSSId, customCSS ] of Object.entries( this.elementCusomCSS ) ) {
192
- const { element, node } = this.matchNodeByConfigId( customCSSId );
193
- this.api.doUpdateElementProperty( {
194
- elementId: element.id,
195
- propertyName: '_styles',
196
- propertyValue: { custom_css: customCSS },
197
- elementType: node.tagName,
198
- } );
199
- }
200
- return {
201
- errors,
202
- invalidStyles,
203
- };
204
- }
205
196
 
206
- applyConfigs() {
207
- const errors: string[] = [];
208
- for ( const [ configId, config ] of Object.entries( this.elementConfig ) ) {
209
- const { element, node } = this.matchNodeByConfigId( configId );
210
- const propSchema = this.findSchemaForNode( node );
211
- const result = validateInput.validateProps( propSchema, config );
212
- if ( ! result.valid && result.errors?.length ) {
213
- errors.push( ...result.errors );
214
- } else {
197
+ const config = this.elementConfig[ configId ];
198
+ if ( config ) {
215
199
  for ( const [ propertyName, propertyValue ] of Object.entries( config ) ) {
216
200
  try {
217
201
  this.api.doUpdateElementProperty( {
@@ -221,36 +205,95 @@ export class CompositionBuilder {
221
205
  elementType: node.tagName,
222
206
  } );
223
207
  } catch ( error ) {
224
- errors.push( ( error as Error ).message );
208
+ configErrors.push( ( error as Error ).message );
209
+ }
210
+ }
211
+ }
212
+
213
+ const styleConfig = this.elementStylesConfig[ configId ];
214
+ if ( styleConfig ) {
215
+ const validStylesPropValues: Record< string, AnyValue > = {};
216
+ for ( const [ styleName, stylePropValue ] of Object.entries( styleConfig ) ) {
217
+ const { valid, errors: validationErrors } = validateInput.validateStyles( {
218
+ [ styleName ]: stylePropValue,
219
+ } );
220
+ if ( ! valid ) {
221
+ if ( styleConfig.$intention ) {
222
+ invalidStyles[ element.id ] = invalidStyles[ element.id ] || [];
223
+ invalidStyles[ element.id ].push( styleName );
224
+ }
225
+ styleErrors.push( ...( validationErrors || [] ) );
226
+ } else {
227
+ validStylesPropValues[ styleName ] = stylePropValue;
228
+ }
229
+ }
230
+ if ( Object.keys( validStylesPropValues ).length > 0 ) {
231
+ try {
232
+ this.api.doUpdateElementProperty( {
233
+ elementId: element.id,
234
+ propertyName: '_styles',
235
+ propertyValue: validStylesPropValues,
236
+ elementType: node.tagName,
237
+ } );
238
+ } catch ( error ) {
239
+ styleErrors.push( String( error ) );
225
240
  }
226
241
  }
227
242
  }
243
+
244
+ const customCSS = this.elementCusomCSS[ configId ];
245
+ if ( customCSS ) {
246
+ try {
247
+ this.api.doUpdateElementProperty( {
248
+ elementId: element.id,
249
+ propertyName: '_styles',
250
+ propertyValue: { custom_css: customCSS },
251
+ elementType: node.tagName,
252
+ } );
253
+ } catch ( cssErr ) {
254
+ styleErrors.push( String( cssErr ) );
255
+ }
256
+ }
257
+
258
+ await this.awaitViewRender( element );
228
259
  }
229
- return errors;
260
+
261
+ return { configErrors, styleErrors, invalidStyles };
230
262
  }
231
263
 
232
- build( rootContainer: V1Element ) {
264
+ async build( rootContainer: V1Element ) {
233
265
  const widgetsCache = this.api.getWidgetsCache() || {};
234
- const CONTAINER_ELEMENTS = Object.values( widgetsCache )
235
- .filter( ( widget ) => widget.meta?.is_container )
236
- .map( ( widget ) => widget.elType )
237
- .filter( ( x ) => typeof x === 'string' );
238
- this.containerElements = CONTAINER_ELEMENTS;
266
+
239
267
  new Set( this.xml.querySelectorAll( '*' ) ).forEach( ( node ) => {
240
268
  if ( ! widgetsCache[ node.tagName ] ) {
241
269
  throw new Error( `Unknown widget type: ${ node.tagName }` );
242
270
  }
243
271
  } );
244
272
 
273
+ const childTypeErrors: string[] = [];
274
+ for ( const rootChild of Array.from( this.xml.children ) ) {
275
+ childTypeErrors.push( ...this.validateChildTypes( rootChild, widgetsCache ) );
276
+ }
277
+ if ( childTypeErrors.length ) {
278
+ throw new Error( `Invalid element structure:\n${ childTypeErrors.join( '\n' ) }` );
279
+ }
280
+
245
281
  const children = Array.from( this.xml.children );
246
- let currentChild = 0;
247
282
  for ( const childNode of children ) {
248
- this.iterateBuild( childNode, rootContainer, currentChild );
249
- currentChild++;
283
+ const modelTree = this.buildModelTree( childNode, widgetsCache );
284
+
285
+ const newElement = this.api.createElement( {
286
+ container: rootContainer,
287
+ model: modelTree as CreateElementParams[ 'model' ],
288
+ options: { useHistory: false },
289
+ } );
290
+
291
+ this.rootContainers.push( newElement );
292
+
293
+ await this.awaitViewRender( newElement );
250
294
  }
251
295
 
252
- const { errors: styleErrors, invalidStyles } = this.applyStyles();
253
- const configErrors = this.applyConfigs();
296
+ const { configErrors, styleErrors, invalidStyles } = await this.applyProperties();
254
297
 
255
298
  return {
256
299
  configErrors,
@@ -141,6 +141,20 @@ Variables from the user context ARE NOT SUPPORTED AND WILL RESOLVE IN ERROR.
141
141
  llmGuidance.default_styles = defaultStyles;
142
142
  }
143
143
 
144
+ const allowedChildTypes = widgetData.allowed_child_types;
145
+
146
+ const allWidgets = getWidgetsCache() || {};
147
+ const allowedParents = Object.entries( allWidgets )
148
+ .filter( ( [ , parentConfig ] ) => parentConfig.allowed_child_types?.includes( widgetType ) )
149
+ .map( ( [ parentType ] ) => parentType );
150
+
151
+ if ( allowedChildTypes?.length || allowedParents.length ) {
152
+ llmGuidance.nesting = {
153
+ ...( allowedChildTypes?.length ? { allowed_child_types: allowedChildTypes } : {} ),
154
+ ...( allowedParents.length ? { allowed_parents: allowedParents } : {} ),
155
+ };
156
+ }
157
+
144
158
  return {
145
159
  contents: [
146
160
  {
@@ -19,6 +19,12 @@ export const generatePrompt = () => {
19
19
  - Every element needs unique "configuration-id"
20
20
  - No attributes, classes, IDs, or text nodes in XML
21
21
 
22
+ ## NESTED ELEMENTS
23
+ Some elements have internal tree structures (nesting). When using these elements, you MUST build the FULL tree in XML.
24
+ - Check \`llm_guidance.nesting\` in widget schemas for structure requirements
25
+ - \`allowed_child_types\` lists which element types can be nested inside
26
+ - \`allowed_parents\` lists which element types this element can be placed inside
27
+
22
28
  # CONFIGURATION
23
29
  - Map configuration-id → elementConfig (props) + stylesConfig (layout only)
24
30
  - All PropValues require \`$$type\` matching schema
@@ -50,20 +50,12 @@ export const initBuildCompositionsTool = ( reg: MCPRegistryEntry ) => {
50
50
  compositionBuilder.setStylesConfig( stylesConfig );
51
51
  compositionBuilder.setCustomCSS( customCSS );
52
52
 
53
- const {
54
- configErrors,
55
- invalidStyles,
56
- rootContainers: generatedRootContainers,
57
- } = compositionBuilder.build( targetContainer );
53
+ const { invalidStyles, rootContainers: generatedRootContainers } =
54
+ await compositionBuilder.build( targetContainer );
58
55
 
59
56
  rootContainers.push( ...generatedRootContainers );
60
57
  generatedXML = new XMLSerializer().serializeToString( compositionBuilder.getXML() );
61
58
 
62
- if ( configErrors.length ) {
63
- errors.push( ...configErrors.map( ( e ) => new Error( e ) ) );
64
- throw new Error( 'Configuration errors occurred during composition building.' );
65
- }
66
-
67
59
  Object.entries( invalidStyles ).forEach( ( [ elementId, rawCssRules ] ) => {
68
60
  const customCss = {
69
61
  value: rawCssRules.join( ';\n' ),