@elementor/editor-canvas 4.0.0-683 → 4.0.0

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.d.mts CHANGED
@@ -164,6 +164,7 @@ type BackboneCollection<Model extends object> = {
164
164
  };
165
165
  type ElementModel = {
166
166
  id: string;
167
+ originId?: string;
167
168
  elType: string;
168
169
  settings: BackboneModel<Props>;
169
170
  editor_settings: Record<string, unknown>;
package/dist/index.d.ts CHANGED
@@ -164,6 +164,7 @@ type BackboneCollection<Model extends object> = {
164
164
  };
165
165
  type ElementModel = {
166
166
  id: string;
167
+ originId?: string;
167
168
  elType: string;
168
169
  settings: BackboneModel<Props>;
169
170
  editor_settings: Record<string, unknown>;
package/dist/index.js CHANGED
@@ -59,7 +59,7 @@ module.exports = __toCommonJS(index_exports);
59
59
  var import_editor_v1_adapters = require("@elementor/editor-v1-adapters");
60
60
  var BREAKPOINTS_SCHEMA_URI = "elementor://breakpoints/list";
61
61
  var initBreakpointsResource = (reg) => {
62
- const { mcpServer, sendResourceUpdated } = reg;
62
+ const { resource, sendResourceUpdated } = reg;
63
63
  const getBreakpointsList = () => {
64
64
  const { breakpoints } = window.elementor?.config?.responsive || {};
65
65
  if (!breakpoints) {
@@ -83,9 +83,16 @@ var initBreakpointsResource = (reg) => {
83
83
  }
84
84
  ]
85
85
  });
86
- mcpServer.resource("breakpoints ", BREAKPOINTS_SCHEMA_URI, () => {
87
- return buildResourceResponse();
88
- });
86
+ resource(
87
+ "breakpoints ",
88
+ BREAKPOINTS_SCHEMA_URI,
89
+ {
90
+ description: "Breakpoints list."
91
+ },
92
+ () => {
93
+ return buildResourceResponse();
94
+ }
95
+ );
89
96
  window.addEventListener((0, import_editor_v1_adapters.v1ReadyEvent)().name, () => {
90
97
  sendResourceUpdated({
91
98
  uri: BREAKPOINTS_SCHEMA_URI,
@@ -833,14 +840,22 @@ var UnknownStyleStateError = (0, import_utils2.createError)({
833
840
  var SELECTORS_MAP = {
834
841
  class: "."
835
842
  };
843
+ var DEFAULT_BREAKPOINT = "desktop";
844
+ var DEFAULT_STATE = "normal";
845
+ function getStyleUniqueKey(style) {
846
+ const breakpoint = style.variants[0]?.meta?.breakpoint ?? DEFAULT_BREAKPOINT;
847
+ const state = style.variants[0]?.meta?.state ?? DEFAULT_STATE;
848
+ return `${style.id}-${breakpoint}-${state}`;
849
+ }
836
850
  function createStylesRenderer({ resolve, breakpoints, selectorPrefix = "" }) {
837
851
  return async ({ styles, signal }) => {
838
- const seenIds = /* @__PURE__ */ new Set();
852
+ const seenKeys = /* @__PURE__ */ new Set();
839
853
  const uniqueStyles = styles.filter((style) => {
840
- if (seenIds.has(style.id)) {
854
+ const key = getStyleUniqueKey(style);
855
+ if (seenKeys.has(key)) {
841
856
  return false;
842
857
  }
843
- seenIds.add(style.id);
858
+ seenKeys.add(key);
844
859
  return true;
845
860
  });
846
861
  const stylesCssPromises = uniqueStyles.map(async (style) => {
@@ -1025,7 +1040,7 @@ function createProviderSubscriber2({ provider, renderStyles, setStyleItems, cach
1025
1040
  });
1026
1041
  return renderStyles({ styles: breakToBreakpoints(styles), signal }).then((rendered) => {
1027
1042
  rebuildCache(cache, allStyles, rendered);
1028
- return rendered;
1043
+ return getOrderedItems(cache);
1029
1044
  });
1030
1045
  }
1031
1046
  function breakToBreakpoints(styles) {
@@ -1625,6 +1640,9 @@ var shadowTransformer = createTransformer((value) => {
1625
1640
 
1626
1641
  // src/transformers/styles/size-transformer.ts
1627
1642
  var sizeTransformer = createTransformer((value) => {
1643
+ if (value.unit === "auto") {
1644
+ return "auto";
1645
+ }
1628
1646
  return value.unit === "custom" ? value.size : `${value.size}${value.unit}`;
1629
1647
  });
1630
1648
 
@@ -2008,6 +2026,7 @@ function createTemplatedElementView({
2008
2026
  this._lastResolvedSettingsHash = settingsHash;
2009
2027
  const context = {
2010
2028
  id: this.model.get("id"),
2029
+ interaction_id: this.getInteractionId(),
2011
2030
  type,
2012
2031
  settings,
2013
2032
  base_styles: baseStylesDictionary
@@ -2042,6 +2061,11 @@ function createTemplatedElementView({
2042
2061
  _openEditingPanel(options) {
2043
2062
  this._doAfterRender(() => super._openEditingPanel(options));
2044
2063
  }
2064
+ getInteractionId() {
2065
+ const originId = this.model.get("originId");
2066
+ const id = this.model.get("id");
2067
+ return originId ?? id;
2068
+ }
2045
2069
  };
2046
2070
  }
2047
2071
 
@@ -2074,10 +2098,11 @@ function createNestedTemplatedElementType({
2074
2098
  }
2075
2099
  function buildEditorAttributes(model) {
2076
2100
  const id = model.get("id");
2101
+ const originId = model.get("originId");
2077
2102
  const cid = model.cid ?? "";
2078
2103
  const attrs = {
2079
2104
  "data-model-cid": cid,
2080
- "data-interaction-id": id,
2105
+ "data-interaction-id": originId ?? id,
2081
2106
  "x-ignore": "true"
2082
2107
  };
2083
2108
  return Object.entries(attrs).map(([key, value]) => `${key}="${value}"`).join(" ");
@@ -2111,6 +2136,9 @@ function createNestedTemplatedElementView({
2111
2136
  invalidateRenderCache() {
2112
2137
  this._lastResolvedSettingsHash = null;
2113
2138
  },
2139
+ renderOnChange() {
2140
+ this.render();
2141
+ },
2114
2142
  render() {
2115
2143
  this._abortController?.abort();
2116
2144
  this._abortController = new AbortController();
@@ -2154,6 +2182,7 @@ function createNestedTemplatedElementView({
2154
2182
  this._lastResolvedSettingsHash = settingsHash;
2155
2183
  const context = {
2156
2184
  id: model.get("id"),
2185
+ interaction_id: this.getInteractionId(),
2157
2186
  type,
2158
2187
  settings,
2159
2188
  base_styles: baseStylesDictionary,
@@ -2283,6 +2312,11 @@ function createNestedTemplatedElementView({
2283
2312
  },
2284
2313
  _openEditingPanel(options) {
2285
2314
  this._doAfterRender(() => parentOpenEditingPanel.call(this, options));
2315
+ },
2316
+ getInteractionId() {
2317
+ const originId = this.model.get("originId");
2318
+ const id = this.model.get("id");
2319
+ return originId ?? id;
2286
2320
  }
2287
2321
  });
2288
2322
  }
@@ -2937,7 +2971,7 @@ function initTabsModelExtensions() {
2937
2971
  var import_editor_v1_adapters13 = require("@elementor/editor-v1-adapters");
2938
2972
  var DOCUMENT_STRUCTURE_URI = "elementor://document/structure";
2939
2973
  var initDocumentStructureResource = (reg) => {
2940
- const { mcpServer, sendResourceUpdated } = reg;
2974
+ const { resource, sendResourceUpdated } = reg;
2941
2975
  let currentDocumentStructure = null;
2942
2976
  const updateDocumentStructure = () => {
2943
2977
  const structure = getDocumentStructure();
@@ -2959,17 +2993,23 @@ var initDocumentStructureResource = (reg) => {
2959
2993
  updateDocumentStructure
2960
2994
  );
2961
2995
  updateDocumentStructure();
2962
- mcpServer.resource("document-structure", DOCUMENT_STRUCTURE_URI, async () => {
2963
- const structure = getDocumentStructure();
2964
- return {
2965
- contents: [
2966
- {
2967
- uri: DOCUMENT_STRUCTURE_URI,
2968
- text: JSON.stringify(structure, null, 2)
2969
- }
2970
- ]
2971
- };
2972
- });
2996
+ resource(
2997
+ "document-structure",
2998
+ DOCUMENT_STRUCTURE_URI,
2999
+ {
3000
+ description: "Document structure."
3001
+ },
3002
+ async () => {
3003
+ return {
3004
+ contents: [
3005
+ {
3006
+ uri: DOCUMENT_STRUCTURE_URI,
3007
+ text: JSON.stringify(getDocumentStructure(), null, 2)
3008
+ }
3009
+ ]
3010
+ };
3011
+ }
3012
+ );
2973
3013
  };
2974
3014
  function getDocumentStructure() {
2975
3015
  const extendedWindow = window;
@@ -3009,6 +3049,7 @@ function extractElementData(element) {
3009
3049
  }
3010
3050
 
3011
3051
  // src/mcp/tools/build-composition/tool.ts
3052
+ var import_editor_documents3 = require("@elementor/editor-documents");
3012
3053
  var import_editor_elements10 = require("@elementor/editor-elements");
3013
3054
 
3014
3055
  // src/composition-builder/composition-builder.ts
@@ -3423,6 +3464,16 @@ var CompositionBuilder = class _CompositionBuilder {
3423
3464
  }
3424
3465
  };
3425
3466
 
3467
+ // src/mcp/utils/get-composition-target-container.ts
3468
+ var import_editor_documents2 = require("@elementor/editor-documents");
3469
+ function getCompositionTargetContainer(documentContainer, documentType) {
3470
+ const firstChild = documentContainer.children?.[0];
3471
+ if (documentType === import_editor_documents2.COMPONENT_DOCUMENT_TYPE && firstChild) {
3472
+ return firstChild;
3473
+ }
3474
+ return documentContainer;
3475
+ }
3476
+
3426
3477
  // src/mcp/tools/build-composition/prompt.ts
3427
3478
  var import_editor_mcp2 = require("@elementor/editor-mcp");
3428
3479
  var generatePrompt = () => {
@@ -3618,6 +3669,8 @@ var initBuildCompositionsTool = (reg) => {
3618
3669
  const errors = [];
3619
3670
  const rootContainers = [];
3620
3671
  const documentContainer = (0, import_editor_elements10.getContainer)("document");
3672
+ const currentDocument = (0, import_editor_documents3.getCurrentDocument)();
3673
+ const targetContainer = getCompositionTargetContainer(documentContainer, currentDocument?.type.value);
3621
3674
  try {
3622
3675
  const compositionBuilder = CompositionBuilder.fromXMLString(xmlStructure, {
3623
3676
  createElement: import_editor_elements10.createElement,
@@ -3630,7 +3683,7 @@ var initBuildCompositionsTool = (reg) => {
3630
3683
  configErrors,
3631
3684
  invalidStyles,
3632
3685
  rootContainers: generatedRootContainers
3633
- } = compositionBuilder.build(documentContainer);
3686
+ } = compositionBuilder.build(targetContainer);
3634
3687
  rootContainers.push(...generatedRootContainers);
3635
3688
  generatedXML = new XMLSerializer().serializeToString(compositionBuilder.getXML());
3636
3689
  if (configErrors.length) {
@@ -4014,14 +4067,12 @@ var initGetElementConfigTool = (reg) => {
4014
4067
  var initCanvasMcp = (reg) => {
4015
4068
  const { setMCPDescription } = reg;
4016
4069
  setMCPDescription(
4017
- `Everything related to creative design, layout, styling and building the pages, specifically element of type "widget".
4070
+ `Everything related to V4 ( Atomic ) canvas.
4018
4071
  # Canvas workflow for new compositions
4019
- - Check existing global variables
4020
- - Check existing global classes
4021
- - Create missing global variables
4022
- - Create reusable global classes
4023
- - Build valid XML with minimal inline styles (layout/positioning only)
4024
- - Apply global classes to elements`
4072
+ - Configure elements settings and styles
4073
+ - Build compositions/sections out of V4 atomic elements using context aware designs using the website resources
4074
+ - Get and retrieve element configuration values
4075
+ `
4025
4076
  );
4026
4077
  initWidgetsSchemaResource(reg);
4027
4078
  initDocumentStructureResource(reg);
package/dist/index.mjs CHANGED
@@ -2,7 +2,7 @@
2
2
  import { v1ReadyEvent } from "@elementor/editor-v1-adapters";
3
3
  var BREAKPOINTS_SCHEMA_URI = "elementor://breakpoints/list";
4
4
  var initBreakpointsResource = (reg) => {
5
- const { mcpServer, sendResourceUpdated } = reg;
5
+ const { resource, sendResourceUpdated } = reg;
6
6
  const getBreakpointsList = () => {
7
7
  const { breakpoints } = window.elementor?.config?.responsive || {};
8
8
  if (!breakpoints) {
@@ -26,9 +26,16 @@ var initBreakpointsResource = (reg) => {
26
26
  }
27
27
  ]
28
28
  });
29
- mcpServer.resource("breakpoints ", BREAKPOINTS_SCHEMA_URI, () => {
30
- return buildResourceResponse();
31
- });
29
+ resource(
30
+ "breakpoints ",
31
+ BREAKPOINTS_SCHEMA_URI,
32
+ {
33
+ description: "Breakpoints list."
34
+ },
35
+ () => {
36
+ return buildResourceResponse();
37
+ }
38
+ );
32
39
  window.addEventListener(v1ReadyEvent().name, () => {
33
40
  sendResourceUpdated({
34
41
  uri: BREAKPOINTS_SCHEMA_URI,
@@ -799,14 +806,22 @@ var UnknownStyleStateError = createError({
799
806
  var SELECTORS_MAP = {
800
807
  class: "."
801
808
  };
809
+ var DEFAULT_BREAKPOINT = "desktop";
810
+ var DEFAULT_STATE = "normal";
811
+ function getStyleUniqueKey(style) {
812
+ const breakpoint = style.variants[0]?.meta?.breakpoint ?? DEFAULT_BREAKPOINT;
813
+ const state = style.variants[0]?.meta?.state ?? DEFAULT_STATE;
814
+ return `${style.id}-${breakpoint}-${state}`;
815
+ }
802
816
  function createStylesRenderer({ resolve, breakpoints, selectorPrefix = "" }) {
803
817
  return async ({ styles, signal }) => {
804
- const seenIds = /* @__PURE__ */ new Set();
818
+ const seenKeys = /* @__PURE__ */ new Set();
805
819
  const uniqueStyles = styles.filter((style) => {
806
- if (seenIds.has(style.id)) {
820
+ const key = getStyleUniqueKey(style);
821
+ if (seenKeys.has(key)) {
807
822
  return false;
808
823
  }
809
- seenIds.add(style.id);
824
+ seenKeys.add(key);
810
825
  return true;
811
826
  });
812
827
  const stylesCssPromises = uniqueStyles.map(async (style) => {
@@ -991,7 +1006,7 @@ function createProviderSubscriber2({ provider, renderStyles, setStyleItems, cach
991
1006
  });
992
1007
  return renderStyles({ styles: breakToBreakpoints(styles), signal }).then((rendered) => {
993
1008
  rebuildCache(cache, allStyles, rendered);
994
- return rendered;
1009
+ return getOrderedItems(cache);
995
1010
  });
996
1011
  }
997
1012
  function breakToBreakpoints(styles) {
@@ -1591,6 +1606,9 @@ var shadowTransformer = createTransformer((value) => {
1591
1606
 
1592
1607
  // src/transformers/styles/size-transformer.ts
1593
1608
  var sizeTransformer = createTransformer((value) => {
1609
+ if (value.unit === "auto") {
1610
+ return "auto";
1611
+ }
1594
1612
  return value.unit === "custom" ? value.size : `${value.size}${value.unit}`;
1595
1613
  });
1596
1614
 
@@ -1974,6 +1992,7 @@ function createTemplatedElementView({
1974
1992
  this._lastResolvedSettingsHash = settingsHash;
1975
1993
  const context = {
1976
1994
  id: this.model.get("id"),
1995
+ interaction_id: this.getInteractionId(),
1977
1996
  type,
1978
1997
  settings,
1979
1998
  base_styles: baseStylesDictionary
@@ -2008,6 +2027,11 @@ function createTemplatedElementView({
2008
2027
  _openEditingPanel(options) {
2009
2028
  this._doAfterRender(() => super._openEditingPanel(options));
2010
2029
  }
2030
+ getInteractionId() {
2031
+ const originId = this.model.get("originId");
2032
+ const id = this.model.get("id");
2033
+ return originId ?? id;
2034
+ }
2011
2035
  };
2012
2036
  }
2013
2037
 
@@ -2040,10 +2064,11 @@ function createNestedTemplatedElementType({
2040
2064
  }
2041
2065
  function buildEditorAttributes(model) {
2042
2066
  const id = model.get("id");
2067
+ const originId = model.get("originId");
2043
2068
  const cid = model.cid ?? "";
2044
2069
  const attrs = {
2045
2070
  "data-model-cid": cid,
2046
- "data-interaction-id": id,
2071
+ "data-interaction-id": originId ?? id,
2047
2072
  "x-ignore": "true"
2048
2073
  };
2049
2074
  return Object.entries(attrs).map(([key, value]) => `${key}="${value}"`).join(" ");
@@ -2077,6 +2102,9 @@ function createNestedTemplatedElementView({
2077
2102
  invalidateRenderCache() {
2078
2103
  this._lastResolvedSettingsHash = null;
2079
2104
  },
2105
+ renderOnChange() {
2106
+ this.render();
2107
+ },
2080
2108
  render() {
2081
2109
  this._abortController?.abort();
2082
2110
  this._abortController = new AbortController();
@@ -2120,6 +2148,7 @@ function createNestedTemplatedElementView({
2120
2148
  this._lastResolvedSettingsHash = settingsHash;
2121
2149
  const context = {
2122
2150
  id: model.get("id"),
2151
+ interaction_id: this.getInteractionId(),
2123
2152
  type,
2124
2153
  settings,
2125
2154
  base_styles: baseStylesDictionary,
@@ -2249,6 +2278,11 @@ function createNestedTemplatedElementView({
2249
2278
  },
2250
2279
  _openEditingPanel(options) {
2251
2280
  this._doAfterRender(() => parentOpenEditingPanel.call(this, options));
2281
+ },
2282
+ getInteractionId() {
2283
+ const originId = this.model.get("originId");
2284
+ const id = this.model.get("id");
2285
+ return originId ?? id;
2252
2286
  }
2253
2287
  });
2254
2288
  }
@@ -2907,7 +2941,7 @@ function initTabsModelExtensions() {
2907
2941
  import { __privateListenTo as listenTo, commandEndEvent as commandEndEvent4 } from "@elementor/editor-v1-adapters";
2908
2942
  var DOCUMENT_STRUCTURE_URI = "elementor://document/structure";
2909
2943
  var initDocumentStructureResource = (reg) => {
2910
- const { mcpServer, sendResourceUpdated } = reg;
2944
+ const { resource, sendResourceUpdated } = reg;
2911
2945
  let currentDocumentStructure = null;
2912
2946
  const updateDocumentStructure = () => {
2913
2947
  const structure = getDocumentStructure();
@@ -2929,17 +2963,23 @@ var initDocumentStructureResource = (reg) => {
2929
2963
  updateDocumentStructure
2930
2964
  );
2931
2965
  updateDocumentStructure();
2932
- mcpServer.resource("document-structure", DOCUMENT_STRUCTURE_URI, async () => {
2933
- const structure = getDocumentStructure();
2934
- return {
2935
- contents: [
2936
- {
2937
- uri: DOCUMENT_STRUCTURE_URI,
2938
- text: JSON.stringify(structure, null, 2)
2939
- }
2940
- ]
2941
- };
2942
- });
2966
+ resource(
2967
+ "document-structure",
2968
+ DOCUMENT_STRUCTURE_URI,
2969
+ {
2970
+ description: "Document structure."
2971
+ },
2972
+ async () => {
2973
+ return {
2974
+ contents: [
2975
+ {
2976
+ uri: DOCUMENT_STRUCTURE_URI,
2977
+ text: JSON.stringify(getDocumentStructure(), null, 2)
2978
+ }
2979
+ ]
2980
+ };
2981
+ }
2982
+ );
2943
2983
  };
2944
2984
  function getDocumentStructure() {
2945
2985
  const extendedWindow = window;
@@ -2979,6 +3019,7 @@ function extractElementData(element) {
2979
3019
  }
2980
3020
 
2981
3021
  // src/mcp/tools/build-composition/tool.ts
3022
+ import { getCurrentDocument } from "@elementor/editor-documents";
2982
3023
  import {
2983
3024
  createElement as createElement8,
2984
3025
  deleteElement,
@@ -3409,6 +3450,16 @@ var CompositionBuilder = class _CompositionBuilder {
3409
3450
  }
3410
3451
  };
3411
3452
 
3453
+ // src/mcp/utils/get-composition-target-container.ts
3454
+ import { COMPONENT_DOCUMENT_TYPE } from "@elementor/editor-documents";
3455
+ function getCompositionTargetContainer(documentContainer, documentType) {
3456
+ const firstChild = documentContainer.children?.[0];
3457
+ if (documentType === COMPONENT_DOCUMENT_TYPE && firstChild) {
3458
+ return firstChild;
3459
+ }
3460
+ return documentContainer;
3461
+ }
3462
+
3412
3463
  // src/mcp/tools/build-composition/prompt.ts
3413
3464
  import { toolPrompts } from "@elementor/editor-mcp";
3414
3465
  var generatePrompt = () => {
@@ -3604,6 +3655,8 @@ var initBuildCompositionsTool = (reg) => {
3604
3655
  const errors = [];
3605
3656
  const rootContainers = [];
3606
3657
  const documentContainer = getContainer3("document");
3658
+ const currentDocument = getCurrentDocument();
3659
+ const targetContainer = getCompositionTargetContainer(documentContainer, currentDocument?.type.value);
3607
3660
  try {
3608
3661
  const compositionBuilder = CompositionBuilder.fromXMLString(xmlStructure, {
3609
3662
  createElement: createElement8,
@@ -3616,7 +3669,7 @@ var initBuildCompositionsTool = (reg) => {
3616
3669
  configErrors,
3617
3670
  invalidStyles,
3618
3671
  rootContainers: generatedRootContainers
3619
- } = compositionBuilder.build(documentContainer);
3672
+ } = compositionBuilder.build(targetContainer);
3620
3673
  rootContainers.push(...generatedRootContainers);
3621
3674
  generatedXML = new XMLSerializer().serializeToString(compositionBuilder.getXML());
3622
3675
  if (configErrors.length) {
@@ -4000,14 +4053,12 @@ var initGetElementConfigTool = (reg) => {
4000
4053
  var initCanvasMcp = (reg) => {
4001
4054
  const { setMCPDescription } = reg;
4002
4055
  setMCPDescription(
4003
- `Everything related to creative design, layout, styling and building the pages, specifically element of type "widget".
4056
+ `Everything related to V4 ( Atomic ) canvas.
4004
4057
  # Canvas workflow for new compositions
4005
- - Check existing global variables
4006
- - Check existing global classes
4007
- - Create missing global variables
4008
- - Create reusable global classes
4009
- - Build valid XML with minimal inline styles (layout/positioning only)
4010
- - Apply global classes to elements`
4058
+ - Configure elements settings and styles
4059
+ - Build compositions/sections out of V4 atomic elements using context aware designs using the website resources
4060
+ - Get and retrieve element configuration values
4061
+ `
4011
4062
  );
4012
4063
  initWidgetsSchemaResource(reg);
4013
4064
  initDocumentStructureResource(reg);
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.0.0-683",
4
+ "version": "4.0.0",
5
5
  "private": false,
6
6
  "author": "Elementor Team",
7
7
  "homepage": "https://elementor.com/",
@@ -37,24 +37,24 @@
37
37
  "react-dom": "^18.3.1"
38
38
  },
39
39
  "dependencies": {
40
- "@elementor/editor": "4.0.0-683",
41
- "@elementor/editor-controls": "4.0.0-683",
42
- "@elementor/editor-documents": "4.0.0-683",
43
- "@elementor/editor-elements": "4.0.0-683",
44
- "@elementor/editor-interactions": "4.0.0-683",
45
- "@elementor/editor-mcp": "4.0.0-683",
46
- "@elementor/editor-notifications": "4.0.0-683",
47
- "@elementor/editor-props": "4.0.0-683",
48
- "@elementor/editor-responsive": "4.0.0-683",
49
- "@elementor/editor-styles": "4.0.0-683",
50
- "@elementor/editor-styles-repository": "4.0.0-683",
51
- "@elementor/editor-ui": "4.0.0-683",
52
- "@elementor/editor-v1-adapters": "4.0.0-683",
53
- "@elementor/schema": "4.0.0-683",
54
- "@elementor/twing": "4.0.0-683",
40
+ "@elementor/editor": "4.0.0",
41
+ "@elementor/editor-controls": "4.0.0",
42
+ "@elementor/editor-documents": "4.0.0",
43
+ "@elementor/editor-elements": "4.0.0",
44
+ "@elementor/editor-interactions": "4.0.0",
45
+ "@elementor/editor-mcp": "4.0.0",
46
+ "@elementor/editor-notifications": "4.0.0",
47
+ "@elementor/editor-props": "4.0.0",
48
+ "@elementor/editor-responsive": "4.0.0",
49
+ "@elementor/editor-styles": "4.0.0",
50
+ "@elementor/editor-styles-repository": "4.0.0",
51
+ "@elementor/editor-ui": "4.0.0",
52
+ "@elementor/editor-v1-adapters": "4.0.0",
53
+ "@elementor/schema": "4.0.0",
54
+ "@elementor/twing": "4.0.0",
55
55
  "@elementor/ui": "1.36.17",
56
- "@elementor/utils": "4.0.0-683",
57
- "@elementor/wp-media": "4.0.0-683",
56
+ "@elementor/utils": "4.0.0",
57
+ "@elementor/wp-media": "4.0.0",
58
58
  "@floating-ui/react": "^0.27.5",
59
59
  "@wordpress/i18n": "^5.13.0"
60
60
  },
@@ -289,6 +289,57 @@ describe( 'useStyleItems', () => {
289
289
  expect( result.current ).toHaveLength( 2 );
290
290
  } );
291
291
 
292
+ it( 'should maintain breakpoint order after style update', async () => {
293
+ // Arrange.
294
+ const renderStylesMock = jest.fn().mockImplementation( ( { styles } ) =>
295
+ Promise.resolve(
296
+ styles.map( ( style: StyleDefinition ) => ( {
297
+ id: style.id,
298
+ breakpoint: style?.variants[ 0 ]?.meta.breakpoint || 'desktop',
299
+ } ) )
300
+ )
301
+ );
302
+
303
+ jest.mocked( useStyleRenderer ).mockReturnValue( renderStylesMock );
304
+
305
+ const mockProvider = createMockStylesProvider( { key: 'provider1', priority: 1 }, [
306
+ createMockStyleDefinitionWithVariants( {
307
+ id: 'style1',
308
+ variants: [
309
+ { meta: { breakpoint: null, state: null }, props: { padding: '10px' }, custom_css: null },
310
+ { meta: { breakpoint: 'tablet', state: null }, props: { padding: '8px' }, custom_css: null },
311
+ { meta: { breakpoint: 'mobile', state: null }, props: { padding: '5px' }, custom_css: null },
312
+ ],
313
+ } ),
314
+ ] );
315
+
316
+ jest.mocked( stylesRepository ).getProviders.mockReturnValue( [ mockProvider ] );
317
+
318
+ // Act - initial render.
319
+ const { result } = renderHook( () => useStyleItems() );
320
+
321
+ await act( async () => {
322
+ mockProvider.actions.updateProps?.( {
323
+ id: 'style1',
324
+ meta: { breakpoint: 'tablet', state: null },
325
+ props: { padding: '12px' },
326
+ } );
327
+ } );
328
+
329
+ // Assert - items should be ordered by breakpoint (desktop, tablet, mobile).
330
+ const breakpointOrder = result.current.map( ( item ) => item.breakpoint );
331
+ expect( breakpointOrder ).toEqual( [ 'desktop', 'tablet', 'mobile' ] );
332
+
333
+ // Act - update again (should maintain order).
334
+ await act( async () => {
335
+ mockProvider.actions.update?.( { id: 'style1', label: 'Updated' } );
336
+ } );
337
+
338
+ // Assert - order should still be maintained.
339
+ const breakpointOrderAfterUpdate = result.current.map( ( item ) => item.breakpoint );
340
+ expect( breakpointOrderAfterUpdate ).toEqual( [ 'desktop', 'tablet', 'mobile' ] );
341
+ } );
342
+
292
343
  it( 'should only re-render changed styles on differential update', async () => {
293
344
  // Arrange.
294
345
  const renderStylesMock = jest.fn().mockImplementation( ( { styles } ) =>
@@ -191,7 +191,7 @@ function createProviderSubscriber( { provider, renderStyles, setStyleItems, cach
191
191
  return renderStyles( { styles: breakToBreakpoints( styles ), signal } ).then( ( rendered ) => {
192
192
  rebuildCache( cache, allStyles, rendered );
193
193
 
194
- return rendered;
194
+ return getOrderedItems( cache );
195
195
  } );
196
196
  }
197
197
 
@@ -61,13 +61,14 @@ export function createNestedTemplatedElementType( {
61
61
  };
62
62
  }
63
63
 
64
- function buildEditorAttributes( model: { get: ( key: 'id' ) => string; cid?: string } ): string {
64
+ function buildEditorAttributes( model: ElementView[ 'model' ] ): string {
65
65
  const id = model.get( 'id' );
66
+ const originId = model.get( 'originId' );
66
67
  const cid = model.cid ?? '';
67
68
 
68
69
  const attrs: Record< string, string > = {
69
70
  'data-model-cid': cid,
70
- 'data-interaction-id': id,
71
+ 'data-interaction-id': originId ?? id,
71
72
  'x-ignore': 'true',
72
73
  };
73
74
 
@@ -116,6 +117,10 @@ export function createNestedTemplatedElementView( {
116
117
  this._lastResolvedSettingsHash = null;
117
118
  },
118
119
 
120
+ renderOnChange() {
121
+ this.render();
122
+ },
123
+
119
124
  render() {
120
125
  this._abortController?.abort();
121
126
  this._abortController = new AbortController();
@@ -181,6 +186,7 @@ export function createNestedTemplatedElementView( {
181
186
 
182
187
  const context = {
183
188
  id: model.get( 'id' ),
189
+ interaction_id: this.getInteractionId(),
184
190
  type,
185
191
  settings,
186
192
  base_styles: baseStylesDictionary,
@@ -361,5 +367,12 @@ export function createNestedTemplatedElementView( {
361
367
  _openEditingPanel( options?: { scrollIntoView: boolean } ) {
362
368
  this._doAfterRender( () => parentOpenEditingPanel.call( this, options ) );
363
369
  },
370
+
371
+ getInteractionId() {
372
+ const originId = this.model.get( 'originId' );
373
+ const id = this.model.get( 'id' );
374
+
375
+ return originId ?? id;
376
+ },
364
377
  } ) as unknown as typeof ElementView;
365
378
  }
@@ -162,6 +162,7 @@ export function createTemplatedElementView( {
162
162
 
163
163
  const context = {
164
164
  id: this.model.get( 'id' ),
165
+ interaction_id: this.getInteractionId(),
165
166
  type,
166
167
  settings,
167
168
  base_styles: baseStylesDictionary,
@@ -206,5 +207,12 @@ export function createTemplatedElementView( {
206
207
  _openEditingPanel( options?: { scrollIntoView: boolean } ) {
207
208
  this._doAfterRender( () => super._openEditingPanel( options ) );
208
209
  }
210
+
211
+ getInteractionId() {
212
+ const originId = this.model.get( 'originId' );
213
+ const id = this.model.get( 'id' );
214
+
215
+ return originId ?? id;
216
+ }
209
217
  };
210
218
  }
@@ -203,6 +203,7 @@ type BackboneCollection< Model extends object > = {
203
203
 
204
204
  export type ElementModel = {
205
205
  id: string;
206
+ originId?: string;
206
207
  elType: string;
207
208
  settings: BackboneModel< Props >;
208
209
  editor_settings: Record< string, unknown >;
@@ -10,14 +10,12 @@ import { initGetElementConfigTool } from './tools/get-element-config/tool';
10
10
  export const initCanvasMcp = ( reg: MCPRegistryEntry ) => {
11
11
  const { setMCPDescription } = reg;
12
12
  setMCPDescription(
13
- `Everything related to creative design, layout, styling and building the pages, specifically element of type "widget".
13
+ `Everything related to V4 ( Atomic ) canvas.
14
14
  # Canvas workflow for new compositions
15
- - Check existing global variables
16
- - Check existing global classes
17
- - Create missing global variables
18
- - Create reusable global classes
19
- - Build valid XML with minimal inline styles (layout/positioning only)
20
- - Apply global classes to elements`
15
+ - Configure elements settings and styles
16
+ - Build compositions/sections out of V4 atomic elements using context aware designs using the website resources
17
+ - Get and retrieve element configuration values
18
+ `
21
19
  );
22
20
  initWidgetsSchemaResource( reg );
23
21
  initDocumentStructureResource( reg );
@@ -5,7 +5,7 @@ import { v1ReadyEvent } from '@elementor/editor-v1-adapters';
5
5
  export const BREAKPOINTS_SCHEMA_URI = 'elementor://breakpoints/list';
6
6
 
7
7
  export const initBreakpointsResource = ( reg: MCPRegistryEntry ) => {
8
- const { mcpServer, sendResourceUpdated } = reg;
8
+ const { resource, sendResourceUpdated } = reg;
9
9
 
10
10
  const getBreakpointsList = () => {
11
11
  const { breakpoints } = ( window as unknown as ExtendedWindow ).elementor?.config?.responsive || {};
@@ -34,9 +34,16 @@ export const initBreakpointsResource = ( reg: MCPRegistryEntry ) => {
34
34
  ],
35
35
  } );
36
36
 
37
- mcpServer.resource( 'breakpoints ', BREAKPOINTS_SCHEMA_URI, () => {
38
- return buildResourceResponse();
39
- } );
37
+ resource(
38
+ 'breakpoints ',
39
+ BREAKPOINTS_SCHEMA_URI,
40
+ {
41
+ description: 'Breakpoints list.',
42
+ },
43
+ () => {
44
+ return buildResourceResponse();
45
+ }
46
+ );
40
47
 
41
48
  window.addEventListener( v1ReadyEvent().name, () => {
42
49
  sendResourceUpdated( {
@@ -39,7 +39,7 @@ type ElementorContainer = {
39
39
  export const DOCUMENT_STRUCTURE_URI = 'elementor://document/structure';
40
40
 
41
41
  export const initDocumentStructureResource = ( reg: MCPRegistryEntry ) => {
42
- const { mcpServer, sendResourceUpdated } = reg;
42
+ const { resource, sendResourceUpdated } = reg;
43
43
 
44
44
  let currentDocumentStructure: string | null = null;
45
45
 
@@ -69,18 +69,23 @@ export const initDocumentStructureResource = ( reg: MCPRegistryEntry ) => {
69
69
  // Initialize on load
70
70
  updateDocumentStructure();
71
71
 
72
- mcpServer.resource( 'document-structure', DOCUMENT_STRUCTURE_URI, async () => {
73
- const structure = getDocumentStructure();
74
-
75
- return {
76
- contents: [
77
- {
78
- uri: DOCUMENT_STRUCTURE_URI,
79
- text: JSON.stringify( structure, null, 2 ),
80
- },
81
- ],
82
- };
83
- } );
72
+ resource(
73
+ 'document-structure',
74
+ DOCUMENT_STRUCTURE_URI,
75
+ {
76
+ description: 'Document structure.',
77
+ },
78
+ async () => {
79
+ return {
80
+ contents: [
81
+ {
82
+ uri: DOCUMENT_STRUCTURE_URI,
83
+ text: JSON.stringify( getDocumentStructure(), null, 2 ),
84
+ },
85
+ ],
86
+ };
87
+ }
88
+ );
84
89
  };
85
90
 
86
91
  function getDocumentStructure() {
@@ -1,3 +1,4 @@
1
+ import { getCurrentDocument } from '@elementor/editor-documents';
1
2
  import {
2
3
  createElement,
3
4
  deleteElement,
@@ -10,6 +11,7 @@ import { type MCPRegistryEntry } from '@elementor/editor-mcp';
10
11
  import { CompositionBuilder } from '../../../composition-builder/composition-builder';
11
12
  import { BEST_PRACTICES_URI, STYLE_SCHEMA_URI, WIDGET_SCHEMA_URI } from '../../resources/widgets-schema-resource';
12
13
  import { doUpdateElementProperty } from '../../utils/do-update-element-property';
14
+ import { getCompositionTargetContainer } from '../../utils/get-composition-target-container';
13
15
  import { generatePrompt } from './prompt';
14
16
  import { inputSchema as schema, outputSchema } from './schema';
15
17
 
@@ -37,6 +39,8 @@ export const initBuildCompositionsTool = ( reg: MCPRegistryEntry ) => {
37
39
  const errors: Error[] = [];
38
40
  const rootContainers: V1Element[] = [];
39
41
  const documentContainer = getContainer( 'document' ) as unknown as V1Element;
42
+ const currentDocument = getCurrentDocument();
43
+ const targetContainer = getCompositionTargetContainer( documentContainer, currentDocument?.type.value );
40
44
  try {
41
45
  const compositionBuilder = CompositionBuilder.fromXMLString( xmlStructure, {
42
46
  createElement,
@@ -50,7 +54,7 @@ export const initBuildCompositionsTool = ( reg: MCPRegistryEntry ) => {
50
54
  configErrors,
51
55
  invalidStyles,
52
56
  rootContainers: generatedRootContainers,
53
- } = compositionBuilder.build( documentContainer );
57
+ } = compositionBuilder.build( targetContainer );
54
58
 
55
59
  rootContainers.push( ...generatedRootContainers );
56
60
  generatedXML = new XMLSerializer().serializeToString( compositionBuilder.getXML() );
@@ -0,0 +1,59 @@
1
+ import { type V1Element } from '@elementor/editor-elements';
2
+
3
+ import { getCompositionTargetContainer } from '../get-composition-target-container';
4
+
5
+ const createMockContainer = ( id: string, children?: V1Element[] ): V1Element =>
6
+ ( {
7
+ id,
8
+ model: { get: jest.fn(), set: jest.fn(), toJSON: jest.fn() },
9
+ settings: { get: jest.fn(), set: jest.fn(), toJSON: jest.fn() },
10
+ children,
11
+ } ) as unknown as V1Element;
12
+
13
+ describe( 'getCompositionTargetContainer', () => {
14
+ it( 'should return first child when document type is elementor_component', () => {
15
+ // Arrange
16
+ const firstChild = createMockContainer( 'child-1' );
17
+ const documentContainer = createMockContainer( 'document', [ firstChild ] );
18
+
19
+ // Act
20
+ const result = getCompositionTargetContainer( documentContainer, 'elementor_component' );
21
+
22
+ // Assert
23
+ expect( result ).toBe( firstChild );
24
+ } );
25
+
26
+ it( 'should return document container when document type is not a component', () => {
27
+ // Arrange
28
+ const firstChild = createMockContainer( 'child-1' );
29
+ const documentContainer = createMockContainer( 'document', [ firstChild ] );
30
+
31
+ // Act
32
+ const result = getCompositionTargetContainer( documentContainer, 'page' );
33
+
34
+ // Assert
35
+ expect( result ).toBe( documentContainer );
36
+ } );
37
+
38
+ it( 'should return document container when document type is undefined', () => {
39
+ // Arrange
40
+ const documentContainer = createMockContainer( 'document' );
41
+
42
+ // Act
43
+ const result = getCompositionTargetContainer( documentContainer, undefined );
44
+
45
+ // Assert
46
+ expect( result ).toBe( documentContainer );
47
+ } );
48
+
49
+ it( 'should return document container when component has no children', () => {
50
+ // Arrange
51
+ const documentContainer = createMockContainer( 'document' );
52
+
53
+ // Act
54
+ const result = getCompositionTargetContainer( documentContainer, 'elementor_component' );
55
+
56
+ // Assert
57
+ expect( result ).toBe( documentContainer );
58
+ } );
59
+ } );
@@ -0,0 +1,15 @@
1
+ import { COMPONENT_DOCUMENT_TYPE } from '@elementor/editor-documents';
2
+ import { type V1Element } from '@elementor/editor-elements';
3
+
4
+ export function getCompositionTargetContainer(
5
+ documentContainer: V1Element,
6
+ documentType: string | undefined
7
+ ): V1Element {
8
+ const firstChild = documentContainer.children?.[ 0 ];
9
+
10
+ if ( documentType === COMPONENT_DOCUMENT_TYPE && firstChild ) {
11
+ return firstChild;
12
+ }
13
+
14
+ return documentContainer;
15
+ }
@@ -118,6 +118,123 @@ describe( 'renderStyles', () => {
118
118
  } );
119
119
  } );
120
120
 
121
+ describe( 'breakpoint deduplication', () => {
122
+ it( 'should render all breakpoints when same id has multiple breakpoint variants', async () => {
123
+ // Arrange - simulates output from breakToBreakpoints in use-style-items.
124
+ const desktopStyle: RendererStyleDefinition = {
125
+ id: 'button-style',
126
+ type: 'class',
127
+ cssName: 'e-button',
128
+ label: 'Button',
129
+ variants: [ { meta: { breakpoint: null, state: null }, props: { 'font-size': '16px' }, custom_css: null } ],
130
+ };
131
+ const tabletStyle: RendererStyleDefinition = {
132
+ ...desktopStyle,
133
+ variants: [
134
+ { meta: { breakpoint: 'tablet', state: null }, props: { 'font-size': '14px' }, custom_css: null },
135
+ ],
136
+ };
137
+ const mobileStyle: RendererStyleDefinition = {
138
+ ...desktopStyle,
139
+ variants: [
140
+ { meta: { breakpoint: 'mobile', state: null }, props: { 'font-size': '12px' }, custom_css: null },
141
+ ],
142
+ };
143
+
144
+ const resolve = jest.fn( ( { props } ) => props );
145
+ const renderStyles = createStylesRenderer( {
146
+ breakpoints: {
147
+ tablet: { width: 992, type: 'max-width' },
148
+ mobile: { width: 768, type: 'max-width' },
149
+ } as BreakpointsMap,
150
+ resolve,
151
+ } );
152
+
153
+ // Act.
154
+ const result = await renderStyles( { styles: [ desktopStyle, tabletStyle, mobileStyle ] } );
155
+
156
+ // Assert - all three breakpoints must be rendered (previously tablet/mobile were dropped).
157
+ expect( result ).toHaveLength( 3 );
158
+ expect( result.map( ( r ) => r.breakpoint ) ).toEqual( [ 'desktop', 'tablet', 'mobile' ] );
159
+ expect( result[ 0 ].value ).toContain( 'font-size:16px' );
160
+ expect( result[ 1 ].value ).toContain( '@media(max-width:992px)' );
161
+ expect( result[ 1 ].value ).toContain( 'font-size:14px' );
162
+ expect( result[ 2 ].value ).toContain( '@media(max-width:768px)' );
163
+ expect( result[ 2 ].value ).toContain( 'font-size:12px' );
164
+ } );
165
+
166
+ it( 'should deduplicate same id + breakpoint + state combinations', async () => {
167
+ // Arrange - two styles with same id, breakpoint, and state should dedupe.
168
+ const style1: RendererStyleDefinition = {
169
+ id: 'button-style',
170
+ type: 'class',
171
+ cssName: 'e-button',
172
+ label: 'Button',
173
+ variants: [
174
+ { meta: { breakpoint: 'tablet', state: null }, props: { 'font-size': '14px' }, custom_css: null },
175
+ ],
176
+ };
177
+ const style2: RendererStyleDefinition = {
178
+ ...style1,
179
+ variants: [
180
+ { meta: { breakpoint: 'tablet', state: null }, props: { 'font-size': '16px' }, custom_css: null },
181
+ ],
182
+ };
183
+
184
+ const resolve = jest.fn( ( { props } ) => props );
185
+ const renderStyles = createStylesRenderer( {
186
+ breakpoints: {
187
+ tablet: { width: 992, type: 'max-width' },
188
+ } as BreakpointsMap,
189
+ resolve,
190
+ } );
191
+
192
+ // Act.
193
+ const result = await renderStyles( { styles: [ style1, style2 ] } );
194
+
195
+ // Assert - should only render first occurrence.
196
+ expect( result ).toHaveLength( 1 );
197
+ expect( result[ 0 ].value ).toContain( 'font-size:14px' );
198
+ } );
199
+
200
+ it( 'should render separately when same id + breakpoint have different states', async () => {
201
+ // Arrange - same id and breakpoint but different states should NOT dedupe.
202
+ const normalStyle: RendererStyleDefinition = {
203
+ id: 'button-style',
204
+ type: 'class',
205
+ cssName: 'e-button',
206
+ label: 'Button',
207
+ variants: [
208
+ { meta: { breakpoint: 'tablet', state: null }, props: { 'font-size': '14px' }, custom_css: null },
209
+ ],
210
+ };
211
+ const hoverStyle: RendererStyleDefinition = {
212
+ ...normalStyle,
213
+ variants: [
214
+ { meta: { breakpoint: 'tablet', state: 'hover' }, props: { 'font-size': '16px' }, custom_css: null },
215
+ ],
216
+ };
217
+
218
+ const resolve = jest.fn( ( { props } ) => props );
219
+ const renderStyles = createStylesRenderer( {
220
+ breakpoints: {
221
+ tablet: { width: 992, type: 'max-width' },
222
+ } as BreakpointsMap,
223
+ resolve,
224
+ } );
225
+
226
+ // Act.
227
+ const result = await renderStyles( { styles: [ normalStyle, hoverStyle ] } );
228
+
229
+ // Assert - both should be rendered since states differ.
230
+ expect( result ).toHaveLength( 2 );
231
+ expect( result[ 0 ].state ).toBeNull();
232
+ expect( result[ 0 ].value ).toContain( 'font-size:14px' );
233
+ expect( result[ 1 ].state ).toBe( 'hover' );
234
+ expect( result[ 1 ].value ).toContain( 'font-size:16px' );
235
+ } );
236
+ } );
237
+
121
238
  describe( 'custom_css rendering', () => {
122
239
  it( 'should not render custom_css if raw is empty', async () => {
123
240
  // Arrange.
@@ -46,14 +46,24 @@ const SELECTORS_MAP: Record< StyleDefinitionType, string > = {
46
46
  class: '.',
47
47
  };
48
48
 
49
+ const DEFAULT_BREAKPOINT = 'desktop';
50
+ const DEFAULT_STATE = 'normal';
51
+
52
+ function getStyleUniqueKey( style: RendererStyleDefinition ): string {
53
+ const breakpoint = style.variants[ 0 ]?.meta?.breakpoint ?? DEFAULT_BREAKPOINT;
54
+ const state = style.variants[ 0 ]?.meta?.state ?? DEFAULT_STATE;
55
+ return `${ style.id }-${ breakpoint }-${ state }`;
56
+ }
57
+
49
58
  export function createStylesRenderer( { resolve, breakpoints, selectorPrefix = '' }: CreateStyleRendererArgs ) {
50
59
  return async ( { styles, signal }: StyleRendererArgs ): Promise< StyleItem[] > => {
51
- const seenIds = new Set< string >();
60
+ const seenKeys = new Set< string >();
52
61
  const uniqueStyles = styles.filter( ( style ) => {
53
- if ( seenIds.has( style.id ) ) {
62
+ const key = getStyleUniqueKey( style );
63
+ if ( seenKeys.has( key ) ) {
54
64
  return false;
55
65
  }
56
- seenIds.add( style.id );
66
+ seenKeys.add( key );
57
67
  return true;
58
68
  } );
59
69
 
@@ -0,0 +1,24 @@
1
+ import { sizeTransformer } from '../size-transformer';
2
+
3
+ function run( val: { size?: number; unit?: string } ) {
4
+ return sizeTransformer( val, { key: 'width', signal: undefined } );
5
+ }
6
+
7
+ describe( 'sizeTransformer', () => {
8
+ it( 'returns "auto" when unit is auto and size is null', () => {
9
+ expect( run( { size: undefined, unit: 'auto' } ) ).toBe( 'auto' );
10
+ } );
11
+
12
+ it( 'returns "auto" when unit is auto and size is undefined', () => {
13
+ expect( run( { unit: 'auto' } ) ).toBe( 'auto' );
14
+ } );
15
+
16
+ it( 'concatenates size and unit for normal units', () => {
17
+ expect( run( { size: 100, unit: 'px' } ) ).toBe( '100px' );
18
+ expect( run( { size: 50, unit: '%' } ) ).toBe( '50%' );
19
+ } );
20
+
21
+ it( 'returns only size for custom unit', () => {
22
+ expect( run( { size: 100, unit: 'custom' } ) ).toBe( 100 );
23
+ } );
24
+ } );
@@ -6,5 +6,8 @@ type Size = {
6
6
  };
7
7
 
8
8
  export const sizeTransformer = createTransformer( ( value: Size ) => {
9
+ if ( value.unit === 'auto' ) {
10
+ return 'auto';
11
+ }
9
12
  return value.unit === 'custom' ? value.size : `${ value.size }${ value.unit }`;
10
13
  } );