@elementor/editor-canvas 4.2.0-919 → 4.2.0-921
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 +44 -37
- package/dist/index.mjs +45 -38
- package/package.json +18 -18
- package/src/composition-builder/composition-builder.ts +13 -11
- package/src/mcp/tools/build-composition/tool.ts +2 -22
- package/src/mcp/utils/__tests__/do-update-element-property.test.ts +84 -1
- package/src/mcp/utils/__tests__/merge-custom-css.test.ts +36 -0
- package/src/mcp/utils/do-update-element-property.ts +25 -6
- package/src/mcp/utils/merge-custom-css.ts +19 -0
package/dist/index.js
CHANGED
|
@@ -4299,6 +4299,26 @@ var import_editor_elements13 = require("@elementor/editor-elements");
|
|
|
4299
4299
|
var import_editor_props7 = require("@elementor/editor-props");
|
|
4300
4300
|
var import_editor_styles5 = require("@elementor/editor-styles");
|
|
4301
4301
|
var import_editor_v1_adapters20 = require("@elementor/editor-v1-adapters");
|
|
4302
|
+
|
|
4303
|
+
// src/mcp/utils/merge-custom-css.ts
|
|
4304
|
+
var CUSTOM_CSS_SEPARATOR = "\n";
|
|
4305
|
+
var mergeCustomCssText = (...cssParts) => cssParts.map((cssPart) => cssPart?.trim()).filter((cssPart) => !!cssPart).join(CUSTOM_CSS_SEPARATOR);
|
|
4306
|
+
var readStoredCustomCssText = (raw) => {
|
|
4307
|
+
if (!raw) {
|
|
4308
|
+
return "";
|
|
4309
|
+
}
|
|
4310
|
+
try {
|
|
4311
|
+
return atob(raw);
|
|
4312
|
+
} catch {
|
|
4313
|
+
return "";
|
|
4314
|
+
}
|
|
4315
|
+
};
|
|
4316
|
+
|
|
4317
|
+
// src/mcp/utils/do-update-element-property.ts
|
|
4318
|
+
var LOCAL_STYLE_META = {
|
|
4319
|
+
breakpoint: "desktop",
|
|
4320
|
+
state: null
|
|
4321
|
+
};
|
|
4302
4322
|
function resolvePropValue(value, forceKey) {
|
|
4303
4323
|
const Utils = window.elementorV2.editorVariables.Utils;
|
|
4304
4324
|
return import_editor_props7.Schema.adjustLlmPropValueSchema(value, {
|
|
@@ -4307,7 +4327,7 @@ function resolvePropValue(value, forceKey) {
|
|
|
4307
4327
|
});
|
|
4308
4328
|
}
|
|
4309
4329
|
var doUpdateElementProperty = (params) => {
|
|
4310
|
-
const { elementId, propertyName, propertyValue, elementType } = params;
|
|
4330
|
+
const { elementId, propertyName, propertyValue, elementType, customCssWriteMode = "replace" } = params;
|
|
4311
4331
|
if (propertyName === "_styles") {
|
|
4312
4332
|
const elementStyles = (0, import_editor_elements13.getElementStyles)(elementId) || {};
|
|
4313
4333
|
const propertyMapValue = propertyValue;
|
|
@@ -4324,6 +4344,8 @@ var doUpdateElementProperty = (params) => {
|
|
|
4324
4344
|
return [key, resolvePropValue(val, propKey2)];
|
|
4325
4345
|
})
|
|
4326
4346
|
);
|
|
4347
|
+
const localStyle = Object.values(elementStyles).find((style) => style.label === "local");
|
|
4348
|
+
const existingCustomCssText = localStyle ? readStoredCustomCssText((0, import_editor_styles5.getVariantByMeta)(localStyle, LOCAL_STYLE_META)?.custom_css?.raw) : "";
|
|
4327
4349
|
let customCss;
|
|
4328
4350
|
Object.keys(propertyMapValue).forEach((stylePropName) => {
|
|
4329
4351
|
const propertyRawSchema = styleSchema[stylePropName];
|
|
@@ -4335,9 +4357,14 @@ var doUpdateElementProperty = (params) => {
|
|
|
4335
4357
|
if (!customCssValue) {
|
|
4336
4358
|
customCssValue = "";
|
|
4337
4359
|
}
|
|
4338
|
-
|
|
4339
|
-
|
|
4340
|
-
|
|
4360
|
+
const customCssText = customCssWriteMode === "merge-with-stored" ? mergeCustomCssText(existingCustomCssText, customCssValue) : String(customCssValue);
|
|
4361
|
+
if (customCssText) {
|
|
4362
|
+
customCss = {
|
|
4363
|
+
raw: btoa(customCssText)
|
|
4364
|
+
};
|
|
4365
|
+
} else {
|
|
4366
|
+
customCss = { raw: btoa("") };
|
|
4367
|
+
}
|
|
4341
4368
|
return;
|
|
4342
4369
|
}
|
|
4343
4370
|
const isSupported = !!propertyRawSchema;
|
|
@@ -4355,7 +4382,6 @@ var doUpdateElementProperty = (params) => {
|
|
|
4355
4382
|
}
|
|
4356
4383
|
});
|
|
4357
4384
|
delete transformedStyleValues.custom_css;
|
|
4358
|
-
const localStyle = Object.values(elementStyles).find((style) => style.label === "local");
|
|
4359
4385
|
if (!localStyle) {
|
|
4360
4386
|
(0, import_editor_elements13.createElementStyle)({
|
|
4361
4387
|
elementId,
|
|
@@ -4658,7 +4684,6 @@ var CompositionBuilder = class _CompositionBuilder {
|
|
|
4658
4684
|
async applyProperties() {
|
|
4659
4685
|
const configErrors = [];
|
|
4660
4686
|
const styleErrors = [];
|
|
4661
|
-
const invalidStyles = {};
|
|
4662
4687
|
const allConfigIds = /* @__PURE__ */ new Set([
|
|
4663
4688
|
...Object.keys(this.elementConfig),
|
|
4664
4689
|
...Object.keys(this.elementStylesConfig),
|
|
@@ -4694,17 +4719,18 @@ var CompositionBuilder = class _CompositionBuilder {
|
|
|
4694
4719
|
}
|
|
4695
4720
|
}
|
|
4696
4721
|
const styleConfig = this.elementStylesConfig[configId];
|
|
4722
|
+
let hasInvalidStyles = false;
|
|
4697
4723
|
if (styleConfig) {
|
|
4698
4724
|
const validStylesPropValues = {};
|
|
4699
4725
|
for (const [styleName, stylePropValue] of Object.entries(styleConfig)) {
|
|
4726
|
+
if (styleName === "$intention") {
|
|
4727
|
+
continue;
|
|
4728
|
+
}
|
|
4700
4729
|
const { valid, errors: validationErrors } = validateInput.validateStyles({
|
|
4701
4730
|
[styleName]: stylePropValue
|
|
4702
4731
|
});
|
|
4703
4732
|
if (!valid) {
|
|
4704
|
-
|
|
4705
|
-
invalidStyles[element.id] = invalidStyles[element.id] || [];
|
|
4706
|
-
invalidStyles[element.id].push(styleName);
|
|
4707
|
-
}
|
|
4733
|
+
hasInvalidStyles = true;
|
|
4708
4734
|
styleErrors.push(...validationErrors || []);
|
|
4709
4735
|
} else {
|
|
4710
4736
|
validStylesPropValues[styleName] = stylePropValue;
|
|
@@ -4723,13 +4749,15 @@ var CompositionBuilder = class _CompositionBuilder {
|
|
|
4723
4749
|
}
|
|
4724
4750
|
}
|
|
4725
4751
|
}
|
|
4726
|
-
const
|
|
4727
|
-
|
|
4752
|
+
const intentionCss = typeof styleConfig?.$intention === "string" ? styleConfig.$intention.trim() : "";
|
|
4753
|
+
const fallbackCss = hasInvalidStyles && intentionCss ? intentionCss : "";
|
|
4754
|
+
const mergedCustomCss = mergeCustomCssText(this.elementCustomCSS[configId], fallbackCss);
|
|
4755
|
+
if (mergedCustomCss) {
|
|
4728
4756
|
try {
|
|
4729
4757
|
this.api.doUpdateElementProperty({
|
|
4730
4758
|
elementId: element.id,
|
|
4731
4759
|
propertyName: "_styles",
|
|
4732
|
-
propertyValue: { custom_css:
|
|
4760
|
+
propertyValue: { custom_css: mergedCustomCss },
|
|
4733
4761
|
elementType: node.tagName
|
|
4734
4762
|
});
|
|
4735
4763
|
} catch (cssErr) {
|
|
@@ -4738,7 +4766,7 @@ var CompositionBuilder = class _CompositionBuilder {
|
|
|
4738
4766
|
}
|
|
4739
4767
|
await this.awaitViewRender(element);
|
|
4740
4768
|
}
|
|
4741
|
-
return { configErrors, styleErrors
|
|
4769
|
+
return { configErrors, styleErrors };
|
|
4742
4770
|
}
|
|
4743
4771
|
async build(rootContainer) {
|
|
4744
4772
|
const widgetsCache = this.api.getWidgetsCache() || {};
|
|
@@ -4783,11 +4811,10 @@ ${childTypeErrors.join("\n")}`);
|
|
|
4783
4811
|
throw e;
|
|
4784
4812
|
}
|
|
4785
4813
|
}
|
|
4786
|
-
const { configErrors, styleErrors
|
|
4814
|
+
const { configErrors, styleErrors } = await this.applyProperties();
|
|
4787
4815
|
return {
|
|
4788
4816
|
configErrors,
|
|
4789
4817
|
styleErrors,
|
|
4790
|
-
invalidStyles,
|
|
4791
4818
|
rootContainers: [...this.rootContainers]
|
|
4792
4819
|
};
|
|
4793
4820
|
}
|
|
@@ -5086,31 +5113,11 @@ var initBuildCompositionsTool = (reg) => {
|
|
|
5086
5113
|
compositionBuilder.setElementConfig(elementConfig);
|
|
5087
5114
|
compositionBuilder.setStylesConfig(stylesConfig);
|
|
5088
5115
|
compositionBuilder.setCustomCSS(customCSS);
|
|
5089
|
-
const {
|
|
5090
|
-
invalidStyles,
|
|
5091
|
-
configErrors,
|
|
5092
|
-
rootContainers: generatedRootContainers
|
|
5093
|
-
} = await compositionBuilder.build(targetContainer);
|
|
5116
|
+
const { configErrors, rootContainers: generatedRootContainers } = await compositionBuilder.build(targetContainer);
|
|
5094
5117
|
rootContainers.push(...generatedRootContainers);
|
|
5095
5118
|
generatedXML = new XMLSerializer().serializeToString(compositionBuilder.getXML());
|
|
5096
5119
|
if (configErrors.length) {
|
|
5097
5120
|
errors.push(...configErrors.map((msg) => new Error(msg)));
|
|
5098
|
-
} else {
|
|
5099
|
-
Object.entries(invalidStyles).forEach(([elementId, rawCssRules]) => {
|
|
5100
|
-
const customCss = {
|
|
5101
|
-
value: rawCssRules.join(";\n")
|
|
5102
|
-
};
|
|
5103
|
-
doUpdateElementProperty({
|
|
5104
|
-
elementId,
|
|
5105
|
-
propertyName: "_styles",
|
|
5106
|
-
propertyValue: {
|
|
5107
|
-
_styles: {
|
|
5108
|
-
custom_css: customCss
|
|
5109
|
-
}
|
|
5110
|
-
},
|
|
5111
|
-
elementType: "widget"
|
|
5112
|
-
});
|
|
5113
|
-
});
|
|
5114
5121
|
}
|
|
5115
5122
|
} catch (error) {
|
|
5116
5123
|
errors.push(error);
|
package/dist/index.mjs
CHANGED
|
@@ -4283,8 +4283,28 @@ import {
|
|
|
4283
4283
|
updateElementStyle
|
|
4284
4284
|
} from "@elementor/editor-elements";
|
|
4285
4285
|
import { getPropSchemaFromCache, Schema as Schema2 } from "@elementor/editor-props";
|
|
4286
|
-
import { getStylesSchema as getStylesSchema3 } from "@elementor/editor-styles";
|
|
4286
|
+
import { getStylesSchema as getStylesSchema3, getVariantByMeta } from "@elementor/editor-styles";
|
|
4287
4287
|
import { __privateRunCommandSync as runCommandSync2 } from "@elementor/editor-v1-adapters";
|
|
4288
|
+
|
|
4289
|
+
// src/mcp/utils/merge-custom-css.ts
|
|
4290
|
+
var CUSTOM_CSS_SEPARATOR = "\n";
|
|
4291
|
+
var mergeCustomCssText = (...cssParts) => cssParts.map((cssPart) => cssPart?.trim()).filter((cssPart) => !!cssPart).join(CUSTOM_CSS_SEPARATOR);
|
|
4292
|
+
var readStoredCustomCssText = (raw) => {
|
|
4293
|
+
if (!raw) {
|
|
4294
|
+
return "";
|
|
4295
|
+
}
|
|
4296
|
+
try {
|
|
4297
|
+
return atob(raw);
|
|
4298
|
+
} catch {
|
|
4299
|
+
return "";
|
|
4300
|
+
}
|
|
4301
|
+
};
|
|
4302
|
+
|
|
4303
|
+
// src/mcp/utils/do-update-element-property.ts
|
|
4304
|
+
var LOCAL_STYLE_META = {
|
|
4305
|
+
breakpoint: "desktop",
|
|
4306
|
+
state: null
|
|
4307
|
+
};
|
|
4288
4308
|
function resolvePropValue(value, forceKey) {
|
|
4289
4309
|
const Utils = window.elementorV2.editorVariables.Utils;
|
|
4290
4310
|
return Schema2.adjustLlmPropValueSchema(value, {
|
|
@@ -4293,7 +4313,7 @@ function resolvePropValue(value, forceKey) {
|
|
|
4293
4313
|
});
|
|
4294
4314
|
}
|
|
4295
4315
|
var doUpdateElementProperty = (params) => {
|
|
4296
|
-
const { elementId, propertyName, propertyValue, elementType } = params;
|
|
4316
|
+
const { elementId, propertyName, propertyValue, elementType, customCssWriteMode = "replace" } = params;
|
|
4297
4317
|
if (propertyName === "_styles") {
|
|
4298
4318
|
const elementStyles = getElementStyles(elementId) || {};
|
|
4299
4319
|
const propertyMapValue = propertyValue;
|
|
@@ -4310,6 +4330,8 @@ var doUpdateElementProperty = (params) => {
|
|
|
4310
4330
|
return [key, resolvePropValue(val, propKey2)];
|
|
4311
4331
|
})
|
|
4312
4332
|
);
|
|
4333
|
+
const localStyle = Object.values(elementStyles).find((style) => style.label === "local");
|
|
4334
|
+
const existingCustomCssText = localStyle ? readStoredCustomCssText(getVariantByMeta(localStyle, LOCAL_STYLE_META)?.custom_css?.raw) : "";
|
|
4313
4335
|
let customCss;
|
|
4314
4336
|
Object.keys(propertyMapValue).forEach((stylePropName) => {
|
|
4315
4337
|
const propertyRawSchema = styleSchema[stylePropName];
|
|
@@ -4321,9 +4343,14 @@ var doUpdateElementProperty = (params) => {
|
|
|
4321
4343
|
if (!customCssValue) {
|
|
4322
4344
|
customCssValue = "";
|
|
4323
4345
|
}
|
|
4324
|
-
|
|
4325
|
-
|
|
4326
|
-
|
|
4346
|
+
const customCssText = customCssWriteMode === "merge-with-stored" ? mergeCustomCssText(existingCustomCssText, customCssValue) : String(customCssValue);
|
|
4347
|
+
if (customCssText) {
|
|
4348
|
+
customCss = {
|
|
4349
|
+
raw: btoa(customCssText)
|
|
4350
|
+
};
|
|
4351
|
+
} else {
|
|
4352
|
+
customCss = { raw: btoa("") };
|
|
4353
|
+
}
|
|
4327
4354
|
return;
|
|
4328
4355
|
}
|
|
4329
4356
|
const isSupported = !!propertyRawSchema;
|
|
@@ -4341,7 +4368,6 @@ var doUpdateElementProperty = (params) => {
|
|
|
4341
4368
|
}
|
|
4342
4369
|
});
|
|
4343
4370
|
delete transformedStyleValues.custom_css;
|
|
4344
|
-
const localStyle = Object.values(elementStyles).find((style) => style.label === "local");
|
|
4345
4371
|
if (!localStyle) {
|
|
4346
4372
|
createElementStyle({
|
|
4347
4373
|
elementId,
|
|
@@ -4644,7 +4670,6 @@ var CompositionBuilder = class _CompositionBuilder {
|
|
|
4644
4670
|
async applyProperties() {
|
|
4645
4671
|
const configErrors = [];
|
|
4646
4672
|
const styleErrors = [];
|
|
4647
|
-
const invalidStyles = {};
|
|
4648
4673
|
const allConfigIds = /* @__PURE__ */ new Set([
|
|
4649
4674
|
...Object.keys(this.elementConfig),
|
|
4650
4675
|
...Object.keys(this.elementStylesConfig),
|
|
@@ -4680,17 +4705,18 @@ var CompositionBuilder = class _CompositionBuilder {
|
|
|
4680
4705
|
}
|
|
4681
4706
|
}
|
|
4682
4707
|
const styleConfig = this.elementStylesConfig[configId];
|
|
4708
|
+
let hasInvalidStyles = false;
|
|
4683
4709
|
if (styleConfig) {
|
|
4684
4710
|
const validStylesPropValues = {};
|
|
4685
4711
|
for (const [styleName, stylePropValue] of Object.entries(styleConfig)) {
|
|
4712
|
+
if (styleName === "$intention") {
|
|
4713
|
+
continue;
|
|
4714
|
+
}
|
|
4686
4715
|
const { valid, errors: validationErrors } = validateInput.validateStyles({
|
|
4687
4716
|
[styleName]: stylePropValue
|
|
4688
4717
|
});
|
|
4689
4718
|
if (!valid) {
|
|
4690
|
-
|
|
4691
|
-
invalidStyles[element.id] = invalidStyles[element.id] || [];
|
|
4692
|
-
invalidStyles[element.id].push(styleName);
|
|
4693
|
-
}
|
|
4719
|
+
hasInvalidStyles = true;
|
|
4694
4720
|
styleErrors.push(...validationErrors || []);
|
|
4695
4721
|
} else {
|
|
4696
4722
|
validStylesPropValues[styleName] = stylePropValue;
|
|
@@ -4709,13 +4735,15 @@ var CompositionBuilder = class _CompositionBuilder {
|
|
|
4709
4735
|
}
|
|
4710
4736
|
}
|
|
4711
4737
|
}
|
|
4712
|
-
const
|
|
4713
|
-
|
|
4738
|
+
const intentionCss = typeof styleConfig?.$intention === "string" ? styleConfig.$intention.trim() : "";
|
|
4739
|
+
const fallbackCss = hasInvalidStyles && intentionCss ? intentionCss : "";
|
|
4740
|
+
const mergedCustomCss = mergeCustomCssText(this.elementCustomCSS[configId], fallbackCss);
|
|
4741
|
+
if (mergedCustomCss) {
|
|
4714
4742
|
try {
|
|
4715
4743
|
this.api.doUpdateElementProperty({
|
|
4716
4744
|
elementId: element.id,
|
|
4717
4745
|
propertyName: "_styles",
|
|
4718
|
-
propertyValue: { custom_css:
|
|
4746
|
+
propertyValue: { custom_css: mergedCustomCss },
|
|
4719
4747
|
elementType: node.tagName
|
|
4720
4748
|
});
|
|
4721
4749
|
} catch (cssErr) {
|
|
@@ -4724,7 +4752,7 @@ var CompositionBuilder = class _CompositionBuilder {
|
|
|
4724
4752
|
}
|
|
4725
4753
|
await this.awaitViewRender(element);
|
|
4726
4754
|
}
|
|
4727
|
-
return { configErrors, styleErrors
|
|
4755
|
+
return { configErrors, styleErrors };
|
|
4728
4756
|
}
|
|
4729
4757
|
async build(rootContainer) {
|
|
4730
4758
|
const widgetsCache = this.api.getWidgetsCache() || {};
|
|
@@ -4769,11 +4797,10 @@ ${childTypeErrors.join("\n")}`);
|
|
|
4769
4797
|
throw e;
|
|
4770
4798
|
}
|
|
4771
4799
|
}
|
|
4772
|
-
const { configErrors, styleErrors
|
|
4800
|
+
const { configErrors, styleErrors } = await this.applyProperties();
|
|
4773
4801
|
return {
|
|
4774
4802
|
configErrors,
|
|
4775
4803
|
styleErrors,
|
|
4776
|
-
invalidStyles,
|
|
4777
4804
|
rootContainers: [...this.rootContainers]
|
|
4778
4805
|
};
|
|
4779
4806
|
}
|
|
@@ -5072,31 +5099,11 @@ var initBuildCompositionsTool = (reg) => {
|
|
|
5072
5099
|
compositionBuilder.setElementConfig(elementConfig);
|
|
5073
5100
|
compositionBuilder.setStylesConfig(stylesConfig);
|
|
5074
5101
|
compositionBuilder.setCustomCSS(customCSS);
|
|
5075
|
-
const {
|
|
5076
|
-
invalidStyles,
|
|
5077
|
-
configErrors,
|
|
5078
|
-
rootContainers: generatedRootContainers
|
|
5079
|
-
} = await compositionBuilder.build(targetContainer);
|
|
5102
|
+
const { configErrors, rootContainers: generatedRootContainers } = await compositionBuilder.build(targetContainer);
|
|
5080
5103
|
rootContainers.push(...generatedRootContainers);
|
|
5081
5104
|
generatedXML = new XMLSerializer().serializeToString(compositionBuilder.getXML());
|
|
5082
5105
|
if (configErrors.length) {
|
|
5083
5106
|
errors.push(...configErrors.map((msg) => new Error(msg)));
|
|
5084
|
-
} else {
|
|
5085
|
-
Object.entries(invalidStyles).forEach(([elementId, rawCssRules]) => {
|
|
5086
|
-
const customCss = {
|
|
5087
|
-
value: rawCssRules.join(";\n")
|
|
5088
|
-
};
|
|
5089
|
-
doUpdateElementProperty({
|
|
5090
|
-
elementId,
|
|
5091
|
-
propertyName: "_styles",
|
|
5092
|
-
propertyValue: {
|
|
5093
|
-
_styles: {
|
|
5094
|
-
custom_css: customCss
|
|
5095
|
-
}
|
|
5096
|
-
},
|
|
5097
|
-
elementType: "widget"
|
|
5098
|
-
});
|
|
5099
|
-
});
|
|
5100
5107
|
}
|
|
5101
5108
|
} catch (error) {
|
|
5102
5109
|
errors.push(error);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elementor/editor-canvas",
|
|
3
3
|
"description": "Elementor Editor Canvas",
|
|
4
|
-
"version": "4.2.0-
|
|
4
|
+
"version": "4.2.0-921",
|
|
5
5
|
"private": false,
|
|
6
6
|
"author": "Elementor Team",
|
|
7
7
|
"homepage": "https://elementor.com/",
|
|
@@ -37,25 +37,25 @@
|
|
|
37
37
|
"react-dom": "^18.3.1"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@elementor/editor": "4.2.0-
|
|
40
|
+
"@elementor/editor": "4.2.0-921",
|
|
41
41
|
"dompurify": "^3.2.6",
|
|
42
|
-
"@elementor/editor-controls": "4.2.0-
|
|
43
|
-
"@elementor/editor-documents": "4.2.0-
|
|
44
|
-
"@elementor/editor-elements": "4.2.0-
|
|
45
|
-
"@elementor/editor-interactions": "4.2.0-
|
|
46
|
-
"@elementor/editor-mcp": "4.2.0-
|
|
47
|
-
"@elementor/editor-notifications": "4.2.0-
|
|
48
|
-
"@elementor/editor-props": "4.2.0-
|
|
49
|
-
"@elementor/editor-responsive": "4.2.0-
|
|
50
|
-
"@elementor/editor-styles": "4.2.0-
|
|
51
|
-
"@elementor/editor-styles-repository": "4.2.0-
|
|
52
|
-
"@elementor/editor-ui": "4.2.0-
|
|
53
|
-
"@elementor/editor-v1-adapters": "4.2.0-
|
|
54
|
-
"@elementor/schema": "4.2.0-
|
|
55
|
-
"@elementor/twing": "4.2.0-
|
|
42
|
+
"@elementor/editor-controls": "4.2.0-921",
|
|
43
|
+
"@elementor/editor-documents": "4.2.0-921",
|
|
44
|
+
"@elementor/editor-elements": "4.2.0-921",
|
|
45
|
+
"@elementor/editor-interactions": "4.2.0-921",
|
|
46
|
+
"@elementor/editor-mcp": "4.2.0-921",
|
|
47
|
+
"@elementor/editor-notifications": "4.2.0-921",
|
|
48
|
+
"@elementor/editor-props": "4.2.0-921",
|
|
49
|
+
"@elementor/editor-responsive": "4.2.0-921",
|
|
50
|
+
"@elementor/editor-styles": "4.2.0-921",
|
|
51
|
+
"@elementor/editor-styles-repository": "4.2.0-921",
|
|
52
|
+
"@elementor/editor-ui": "4.2.0-921",
|
|
53
|
+
"@elementor/editor-v1-adapters": "4.2.0-921",
|
|
54
|
+
"@elementor/schema": "4.2.0-921",
|
|
55
|
+
"@elementor/twing": "4.2.0-921",
|
|
56
56
|
"@elementor/ui": "1.37.5",
|
|
57
|
-
"@elementor/utils": "4.2.0-
|
|
58
|
-
"@elementor/wp-media": "4.2.0-
|
|
57
|
+
"@elementor/utils": "4.2.0-921",
|
|
58
|
+
"@elementor/wp-media": "4.2.0-921",
|
|
59
59
|
"@floating-ui/react": "^0.27.5",
|
|
60
60
|
"@wordpress/i18n": "^5.13.0"
|
|
61
61
|
},
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
import { type z } from '@elementor/schema';
|
|
13
13
|
|
|
14
14
|
import { doUpdateElementProperty } from '../mcp/utils/do-update-element-property';
|
|
15
|
+
import { mergeCustomCssText } from '../mcp/utils/merge-custom-css';
|
|
15
16
|
import { validateInput } from '../mcp/utils/validate-input';
|
|
16
17
|
import { RequiredChildrenEnforcer } from './utils/required-children-enforcer';
|
|
17
18
|
import { getRequiredDefaultChildTemplates } from './utils/required-default-child-tags';
|
|
@@ -183,7 +184,6 @@ export class CompositionBuilder {
|
|
|
183
184
|
private async applyProperties() {
|
|
184
185
|
const configErrors: string[] = [];
|
|
185
186
|
const styleErrors: string[] = [];
|
|
186
|
-
const invalidStyles: Record< string, string[] > = {};
|
|
187
187
|
|
|
188
188
|
const allConfigIds = new Set( [
|
|
189
189
|
...Object.keys( this.elementConfig ),
|
|
@@ -223,17 +223,18 @@ export class CompositionBuilder {
|
|
|
223
223
|
}
|
|
224
224
|
|
|
225
225
|
const styleConfig = this.elementStylesConfig[ configId ];
|
|
226
|
+
let hasInvalidStyles = false;
|
|
226
227
|
if ( styleConfig ) {
|
|
227
228
|
const validStylesPropValues: Record< string, AnyValue > = {};
|
|
228
229
|
for ( const [ styleName, stylePropValue ] of Object.entries( styleConfig ) ) {
|
|
230
|
+
if ( styleName === '$intention' ) {
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
229
233
|
const { valid, errors: validationErrors } = validateInput.validateStyles( {
|
|
230
234
|
[ styleName ]: stylePropValue,
|
|
231
235
|
} );
|
|
232
236
|
if ( ! valid ) {
|
|
233
|
-
|
|
234
|
-
invalidStyles[ element.id ] = invalidStyles[ element.id ] || [];
|
|
235
|
-
invalidStyles[ element.id ].push( styleName );
|
|
236
|
-
}
|
|
237
|
+
hasInvalidStyles = true;
|
|
237
238
|
styleErrors.push( ...( validationErrors || [] ) );
|
|
238
239
|
} else {
|
|
239
240
|
validStylesPropValues[ styleName ] = stylePropValue;
|
|
@@ -253,13 +254,15 @@ export class CompositionBuilder {
|
|
|
253
254
|
}
|
|
254
255
|
}
|
|
255
256
|
|
|
256
|
-
const
|
|
257
|
-
|
|
257
|
+
const intentionCss = typeof styleConfig?.$intention === 'string' ? styleConfig.$intention.trim() : '';
|
|
258
|
+
const fallbackCss = hasInvalidStyles && intentionCss ? intentionCss : '';
|
|
259
|
+
const mergedCustomCss = mergeCustomCssText( this.elementCustomCSS[ configId ], fallbackCss );
|
|
260
|
+
if ( mergedCustomCss ) {
|
|
258
261
|
try {
|
|
259
262
|
this.api.doUpdateElementProperty( {
|
|
260
263
|
elementId: element.id,
|
|
261
264
|
propertyName: '_styles',
|
|
262
|
-
propertyValue: { custom_css:
|
|
265
|
+
propertyValue: { custom_css: mergedCustomCss },
|
|
263
266
|
elementType: node.tagName,
|
|
264
267
|
} );
|
|
265
268
|
} catch ( cssErr ) {
|
|
@@ -270,7 +273,7 @@ export class CompositionBuilder {
|
|
|
270
273
|
await this.awaitViewRender( element );
|
|
271
274
|
}
|
|
272
275
|
|
|
273
|
-
return { configErrors, styleErrors
|
|
276
|
+
return { configErrors, styleErrors };
|
|
274
277
|
}
|
|
275
278
|
|
|
276
279
|
async build( rootContainer: V1Element ) {
|
|
@@ -322,12 +325,11 @@ export class CompositionBuilder {
|
|
|
322
325
|
}
|
|
323
326
|
}
|
|
324
327
|
|
|
325
|
-
const { configErrors, styleErrors
|
|
328
|
+
const { configErrors, styleErrors } = await this.applyProperties();
|
|
326
329
|
|
|
327
330
|
return {
|
|
328
331
|
configErrors,
|
|
329
332
|
styleErrors,
|
|
330
|
-
invalidStyles,
|
|
331
333
|
rootContainers: [ ...this.rootContainers ],
|
|
332
334
|
};
|
|
333
335
|
}
|
|
@@ -11,7 +11,6 @@ import { type MCPRegistryEntry } from '@elementor/editor-mcp';
|
|
|
11
11
|
import { CompositionBuilder } from '../../../composition-builder/composition-builder';
|
|
12
12
|
import { AVAILABLE_WIDGETS_URI_V4 } from '../../resources/available-widgets-resource';
|
|
13
13
|
import { BEST_PRACTICES_URI, STYLE_SCHEMA_URI, WIDGET_SCHEMA_URI } from '../../resources/widgets-schema-resource';
|
|
14
|
-
import { doUpdateElementProperty } from '../../utils/do-update-element-property';
|
|
15
14
|
import { isWidgetAvailableForLLM } from '../../utils/element-data-util';
|
|
16
15
|
import { getCompositionTargetContainer } from '../../utils/get-composition-target-container';
|
|
17
16
|
import { BUILD_COMPOSITIONS_GUIDE_URI, generatePrompt } from './prompt';
|
|
@@ -71,33 +70,14 @@ export const initBuildCompositionsTool = ( reg: MCPRegistryEntry ) => {
|
|
|
71
70
|
compositionBuilder.setStylesConfig( stylesConfig );
|
|
72
71
|
compositionBuilder.setCustomCSS( customCSS );
|
|
73
72
|
|
|
74
|
-
const {
|
|
75
|
-
|
|
76
|
-
configErrors,
|
|
77
|
-
rootContainers: generatedRootContainers,
|
|
78
|
-
} = await compositionBuilder.build( targetContainer );
|
|
73
|
+
const { configErrors, rootContainers: generatedRootContainers } =
|
|
74
|
+
await compositionBuilder.build( targetContainer );
|
|
79
75
|
|
|
80
76
|
rootContainers.push( ...generatedRootContainers );
|
|
81
77
|
generatedXML = new XMLSerializer().serializeToString( compositionBuilder.getXML() );
|
|
82
78
|
|
|
83
79
|
if ( configErrors.length ) {
|
|
84
80
|
errors.push( ...configErrors.map( ( msg ) => new Error( msg ) ) );
|
|
85
|
-
} else {
|
|
86
|
-
Object.entries( invalidStyles ).forEach( ( [ elementId, rawCssRules ] ) => {
|
|
87
|
-
const customCss = {
|
|
88
|
-
value: rawCssRules.join( ';\n' ),
|
|
89
|
-
};
|
|
90
|
-
doUpdateElementProperty( {
|
|
91
|
-
elementId,
|
|
92
|
-
propertyName: '_styles',
|
|
93
|
-
propertyValue: {
|
|
94
|
-
_styles: {
|
|
95
|
-
custom_css: customCss,
|
|
96
|
-
},
|
|
97
|
-
},
|
|
98
|
-
elementType: 'widget',
|
|
99
|
-
} );
|
|
100
|
-
} );
|
|
101
81
|
}
|
|
102
82
|
} catch ( error ) {
|
|
103
83
|
errors.push( error as Error );
|
|
@@ -1,5 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
getElementStyles,
|
|
3
|
+
getWidgetsCache,
|
|
4
|
+
updateElementSettings,
|
|
5
|
+
updateElementStyle,
|
|
6
|
+
} from '@elementor/editor-elements';
|
|
2
7
|
import { Schema } from '@elementor/editor-props';
|
|
8
|
+
import { getVariantByMeta } from '@elementor/editor-styles';
|
|
3
9
|
import { __privateRunCommandSync } from '@elementor/editor-v1-adapters';
|
|
4
10
|
|
|
5
11
|
import { doUpdateElementProperty } from '../do-update-element-property';
|
|
@@ -22,6 +28,7 @@ jest.mock( '@elementor/editor-props', () => ( {
|
|
|
22
28
|
|
|
23
29
|
jest.mock( '@elementor/editor-styles', () => ( {
|
|
24
30
|
getStylesSchema: jest.fn( () => ( {} ) ),
|
|
31
|
+
getVariantByMeta: jest.fn(),
|
|
25
32
|
} ) );
|
|
26
33
|
|
|
27
34
|
jest.mock( '@elementor/editor-v1-adapters', () => ( {
|
|
@@ -33,6 +40,9 @@ const ELEMENT_TYPE = 'atomic-heading';
|
|
|
33
40
|
const EXPECTED_JSON_SCHEMA_SNIPPET = '{"type":"object"}';
|
|
34
41
|
const PROPERTY_NAME = 'title';
|
|
35
42
|
const PROP_SCHEMA_ENTRY = { key: 'titlePropKey' };
|
|
43
|
+
const LOCAL_STYLE_ID = 'local-style-id';
|
|
44
|
+
const EXISTING_CUSTOM_CSS = 'padding: 2rem;';
|
|
45
|
+
const ADDITIONAL_CUSTOM_CSS = 'font-size: 1.5rem;';
|
|
36
46
|
|
|
37
47
|
const widgetsCacheFixture = {
|
|
38
48
|
[ ELEMENT_TYPE ]: {
|
|
@@ -132,4 +142,77 @@ describe( 'doUpdateElementProperty', () => {
|
|
|
132
142
|
{ internal: true }
|
|
133
143
|
);
|
|
134
144
|
} );
|
|
145
|
+
|
|
146
|
+
it( 'replaces existing local style custom_css by default', () => {
|
|
147
|
+
// Arrange
|
|
148
|
+
jest.mocked( getElementStyles ).mockReturnValue( {
|
|
149
|
+
[ LOCAL_STYLE_ID ]: {
|
|
150
|
+
id: LOCAL_STYLE_ID,
|
|
151
|
+
label: 'local',
|
|
152
|
+
type: 'class',
|
|
153
|
+
variants: [],
|
|
154
|
+
},
|
|
155
|
+
} );
|
|
156
|
+
jest.mocked( getVariantByMeta ).mockReturnValue( {
|
|
157
|
+
meta: { breakpoint: 'desktop', state: null },
|
|
158
|
+
props: {},
|
|
159
|
+
custom_css: { raw: btoa( EXISTING_CUSTOM_CSS ) },
|
|
160
|
+
} );
|
|
161
|
+
|
|
162
|
+
// Act
|
|
163
|
+
doUpdateElementProperty( {
|
|
164
|
+
elementId: ELEMENT_ID,
|
|
165
|
+
elementType: ELEMENT_TYPE,
|
|
166
|
+
propertyName: '_styles',
|
|
167
|
+
propertyValue: {
|
|
168
|
+
custom_css: ADDITIONAL_CUSTOM_CSS,
|
|
169
|
+
},
|
|
170
|
+
} );
|
|
171
|
+
|
|
172
|
+
// Assert
|
|
173
|
+
expect( updateElementStyle ).toHaveBeenCalledWith(
|
|
174
|
+
expect.objectContaining( {
|
|
175
|
+
custom_css: {
|
|
176
|
+
raw: btoa( ADDITIONAL_CUSTOM_CSS ),
|
|
177
|
+
},
|
|
178
|
+
} )
|
|
179
|
+
);
|
|
180
|
+
} );
|
|
181
|
+
|
|
182
|
+
it( 'merges incoming custom_css with stored css when customCssWriteMode is merge-with-stored', () => {
|
|
183
|
+
// Arrange
|
|
184
|
+
jest.mocked( getElementStyles ).mockReturnValue( {
|
|
185
|
+
[ LOCAL_STYLE_ID ]: {
|
|
186
|
+
id: LOCAL_STYLE_ID,
|
|
187
|
+
label: 'local',
|
|
188
|
+
type: 'class',
|
|
189
|
+
variants: [],
|
|
190
|
+
},
|
|
191
|
+
} );
|
|
192
|
+
jest.mocked( getVariantByMeta ).mockReturnValue( {
|
|
193
|
+
meta: { breakpoint: 'desktop', state: null },
|
|
194
|
+
props: {},
|
|
195
|
+
custom_css: { raw: btoa( EXISTING_CUSTOM_CSS ) },
|
|
196
|
+
} );
|
|
197
|
+
|
|
198
|
+
// Act
|
|
199
|
+
doUpdateElementProperty( {
|
|
200
|
+
elementId: ELEMENT_ID,
|
|
201
|
+
elementType: ELEMENT_TYPE,
|
|
202
|
+
propertyName: '_styles',
|
|
203
|
+
propertyValue: {
|
|
204
|
+
custom_css: ADDITIONAL_CUSTOM_CSS,
|
|
205
|
+
},
|
|
206
|
+
customCssWriteMode: 'merge-with-stored',
|
|
207
|
+
} );
|
|
208
|
+
|
|
209
|
+
// Assert
|
|
210
|
+
expect( updateElementStyle ).toHaveBeenCalledWith(
|
|
211
|
+
expect.objectContaining( {
|
|
212
|
+
custom_css: {
|
|
213
|
+
raw: btoa( `${ EXISTING_CUSTOM_CSS }\n${ ADDITIONAL_CUSTOM_CSS }` ),
|
|
214
|
+
},
|
|
215
|
+
} )
|
|
216
|
+
);
|
|
217
|
+
} );
|
|
135
218
|
} );
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { mergeCustomCssText, readStoredCustomCssText } from '../merge-custom-css';
|
|
2
|
+
|
|
3
|
+
describe( 'mergeCustomCssText', () => {
|
|
4
|
+
it( 'merges explicit custom CSS with intention fallback CSS', () => {
|
|
5
|
+
// Arrange
|
|
6
|
+
const explicitCss = 'padding: 2rem;';
|
|
7
|
+
const intentionCss = 'font-size: 1.5rem;';
|
|
8
|
+
|
|
9
|
+
// Act
|
|
10
|
+
const merged = mergeCustomCssText( explicitCss, intentionCss );
|
|
11
|
+
|
|
12
|
+
// Assert
|
|
13
|
+
expect( merged ).toBe( 'padding: 2rem;\nfont-size: 1.5rem;' );
|
|
14
|
+
} );
|
|
15
|
+
|
|
16
|
+
it( 'returns a single CSS block when only one part is provided', () => {
|
|
17
|
+
// Act
|
|
18
|
+
const merged = mergeCustomCssText( undefined, 'color: red;' );
|
|
19
|
+
|
|
20
|
+
// Assert
|
|
21
|
+
expect( merged ).toBe( 'color: red;' );
|
|
22
|
+
} );
|
|
23
|
+
} );
|
|
24
|
+
|
|
25
|
+
describe( 'readStoredCustomCssText', () => {
|
|
26
|
+
it( 'decodes stored custom CSS text', () => {
|
|
27
|
+
// Arrange
|
|
28
|
+
const cssText = 'display: flex;';
|
|
29
|
+
|
|
30
|
+
// Act
|
|
31
|
+
const decoded = readStoredCustomCssText( btoa( cssText ) );
|
|
32
|
+
|
|
33
|
+
// Assert
|
|
34
|
+
expect( decoded ).toBe( cssText );
|
|
35
|
+
} );
|
|
36
|
+
} );
|
|
@@ -6,18 +6,26 @@ import {
|
|
|
6
6
|
updateElementStyle,
|
|
7
7
|
} from '@elementor/editor-elements';
|
|
8
8
|
import { getPropSchemaFromCache, type PropValue, Schema, type TransformablePropValue } from '@elementor/editor-props';
|
|
9
|
-
import { type CustomCss, getStylesSchema } from '@elementor/editor-styles';
|
|
9
|
+
import { type CustomCss, getStylesSchema, getVariantByMeta } from '@elementor/editor-styles';
|
|
10
10
|
import { __privateRunCommandSync as runCommandSync } from '@elementor/editor-v1-adapters';
|
|
11
11
|
import { type Utils as IUtils } from '@elementor/editor-variables';
|
|
12
12
|
import { type z } from '@elementor/schema';
|
|
13
13
|
|
|
14
|
+
import { mergeCustomCssText, readStoredCustomCssText } from './merge-custom-css';
|
|
15
|
+
|
|
14
16
|
// TODO: see https://elementor.atlassian.net/browse/ED-22513 for better cross-module access
|
|
15
17
|
type XElementor = z.infer< z.ZodAny >;
|
|
18
|
+
const LOCAL_STYLE_META = {
|
|
19
|
+
breakpoint: 'desktop',
|
|
20
|
+
state: null,
|
|
21
|
+
} as const;
|
|
22
|
+
type CustomCssWriteMode = 'replace' | 'merge-with-stored';
|
|
16
23
|
type OwnParams = {
|
|
17
24
|
elementId: string;
|
|
18
25
|
elementType: string;
|
|
19
26
|
propertyName: string;
|
|
20
27
|
propertyValue: string | PropValue | TransformablePropValue< string, unknown >;
|
|
28
|
+
customCssWriteMode?: CustomCssWriteMode;
|
|
21
29
|
};
|
|
22
30
|
|
|
23
31
|
export function resolvePropValue( value: unknown, forceKey?: string ): PropValue {
|
|
@@ -35,7 +43,7 @@ export function resolvePropValue( value: unknown, forceKey?: string ): PropValue
|
|
|
35
43
|
* Also, it supports updating styles "on-the-way" by checking for "_styles" property with PropValue bag that fits the common style schema.
|
|
36
44
|
*/
|
|
37
45
|
export const doUpdateElementProperty = ( params: OwnParams ) => {
|
|
38
|
-
const { elementId, propertyName, propertyValue, elementType } = params;
|
|
46
|
+
const { elementId, propertyName, propertyValue, elementType, customCssWriteMode = 'replace' } = params;
|
|
39
47
|
if ( propertyName === '_styles' ) {
|
|
40
48
|
const elementStyles = getElementStyles( elementId ) || {};
|
|
41
49
|
const propertyMapValue = propertyValue as Record< string, PropValue >;
|
|
@@ -52,6 +60,10 @@ export const doUpdateElementProperty = ( params: OwnParams ) => {
|
|
|
52
60
|
return [ key, resolvePropValue( val, propKey ) ];
|
|
53
61
|
} )
|
|
54
62
|
);
|
|
63
|
+
const localStyle = Object.values( elementStyles ).find( ( style ) => style.label === 'local' );
|
|
64
|
+
const existingCustomCssText = localStyle
|
|
65
|
+
? readStoredCustomCssText( getVariantByMeta( localStyle, LOCAL_STYLE_META )?.custom_css?.raw )
|
|
66
|
+
: '';
|
|
55
67
|
let customCss: CustomCss | undefined;
|
|
56
68
|
Object.keys( propertyMapValue as Record< string, unknown > ).forEach( ( stylePropName ) => {
|
|
57
69
|
const propertyRawSchema = styleSchema[ stylePropName ];
|
|
@@ -63,9 +75,17 @@ export const doUpdateElementProperty = ( params: OwnParams ) => {
|
|
|
63
75
|
if ( ! customCssValue ) {
|
|
64
76
|
customCssValue = '';
|
|
65
77
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
78
|
+
const customCssText =
|
|
79
|
+
customCssWriteMode === 'merge-with-stored'
|
|
80
|
+
? mergeCustomCssText( existingCustomCssText, customCssValue as string )
|
|
81
|
+
: String( customCssValue );
|
|
82
|
+
if ( customCssText ) {
|
|
83
|
+
customCss = {
|
|
84
|
+
raw: btoa( customCssText ),
|
|
85
|
+
};
|
|
86
|
+
} else {
|
|
87
|
+
customCss = { raw: btoa( '' ) };
|
|
88
|
+
}
|
|
69
89
|
return;
|
|
70
90
|
}
|
|
71
91
|
const isSupported = !! propertyRawSchema;
|
|
@@ -83,7 +103,6 @@ export const doUpdateElementProperty = ( params: OwnParams ) => {
|
|
|
83
103
|
}
|
|
84
104
|
} );
|
|
85
105
|
delete transformedStyleValues.custom_css;
|
|
86
|
-
const localStyle = Object.values( elementStyles ).find( ( style ) => style.label === 'local' );
|
|
87
106
|
if ( ! localStyle ) {
|
|
88
107
|
createElementStyle( {
|
|
89
108
|
elementId,
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
const CUSTOM_CSS_SEPARATOR = '\n';
|
|
2
|
+
|
|
3
|
+
export const mergeCustomCssText = ( ...cssParts: Array< string | undefined > ): string =>
|
|
4
|
+
cssParts
|
|
5
|
+
.map( ( cssPart ) => cssPart?.trim() )
|
|
6
|
+
.filter( ( cssPart ): cssPart is string => !! cssPart )
|
|
7
|
+
.join( CUSTOM_CSS_SEPARATOR );
|
|
8
|
+
|
|
9
|
+
export const readStoredCustomCssText = ( raw: string | undefined ): string => {
|
|
10
|
+
if ( ! raw ) {
|
|
11
|
+
return '';
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
return atob( raw );
|
|
16
|
+
} catch {
|
|
17
|
+
return '';
|
|
18
|
+
}
|
|
19
|
+
};
|