@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 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
- customCss = {
4339
- raw: btoa(customCssValue)
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
- if (styleConfig.$intention) {
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 customCSS = this.elementCustomCSS[configId];
4727
- if (customCSS) {
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: customCSS },
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, invalidStyles };
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, invalidStyles } = await this.applyProperties();
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
- customCss = {
4325
- raw: btoa(customCssValue)
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
- if (styleConfig.$intention) {
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 customCSS = this.elementCustomCSS[configId];
4713
- if (customCSS) {
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: customCSS },
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, invalidStyles };
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, invalidStyles } = await this.applyProperties();
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-919",
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-919",
40
+ "@elementor/editor": "4.2.0-921",
41
41
  "dompurify": "^3.2.6",
42
- "@elementor/editor-controls": "4.2.0-919",
43
- "@elementor/editor-documents": "4.2.0-919",
44
- "@elementor/editor-elements": "4.2.0-919",
45
- "@elementor/editor-interactions": "4.2.0-919",
46
- "@elementor/editor-mcp": "4.2.0-919",
47
- "@elementor/editor-notifications": "4.2.0-919",
48
- "@elementor/editor-props": "4.2.0-919",
49
- "@elementor/editor-responsive": "4.2.0-919",
50
- "@elementor/editor-styles": "4.2.0-919",
51
- "@elementor/editor-styles-repository": "4.2.0-919",
52
- "@elementor/editor-ui": "4.2.0-919",
53
- "@elementor/editor-v1-adapters": "4.2.0-919",
54
- "@elementor/schema": "4.2.0-919",
55
- "@elementor/twing": "4.2.0-919",
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-919",
58
- "@elementor/wp-media": "4.2.0-919",
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
- if ( styleConfig.$intention ) {
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 customCSS = this.elementCustomCSS[ configId ];
257
- if ( customCSS ) {
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: customCSS },
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, invalidStyles };
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, invalidStyles } = await this.applyProperties();
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
- invalidStyles,
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 { getWidgetsCache, updateElementSettings } from '@elementor/editor-elements';
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
- customCss = {
67
- raw: btoa( customCssValue as string ),
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
+ };