@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 +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +80 -29
- package/dist/index.mjs +80 -29
- package/package.json +18 -18
- package/src/hooks/__tests__/use-style-items.test.ts +51 -0
- package/src/hooks/use-style-items.ts +1 -1
- package/src/legacy/create-nested-templated-element-type.ts +15 -2
- package/src/legacy/create-templated-element-type.ts +8 -0
- package/src/legacy/types.ts +1 -0
- package/src/mcp/canvas-mcp.ts +5 -7
- package/src/mcp/resources/breakpoints-resource.ts +11 -4
- package/src/mcp/resources/document-structure-resource.ts +18 -13
- package/src/mcp/tools/build-composition/tool.ts +5 -1
- package/src/mcp/utils/__tests__/get-composition-target-container.test.ts +59 -0
- package/src/mcp/utils/get-composition-target-container.ts +15 -0
- package/src/renderers/__tests__/create-styles-renderer.test.ts +117 -0
- package/src/renderers/create-styles-renderer.ts +13 -3
- package/src/transformers/styles/__tests__/size-transformer.test.ts +24 -0
- package/src/transformers/styles/size-transformer.ts +3 -0
package/dist/index.d.mts
CHANGED
package/dist/index.d.ts
CHANGED
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 {
|
|
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
|
-
|
|
87
|
-
|
|
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
|
|
852
|
+
const seenKeys = /* @__PURE__ */ new Set();
|
|
839
853
|
const uniqueStyles = styles.filter((style) => {
|
|
840
|
-
|
|
854
|
+
const key = getStyleUniqueKey(style);
|
|
855
|
+
if (seenKeys.has(key)) {
|
|
841
856
|
return false;
|
|
842
857
|
}
|
|
843
|
-
|
|
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
|
|
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 {
|
|
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
|
-
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
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(
|
|
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
|
|
4070
|
+
`Everything related to V4 ( Atomic ) canvas.
|
|
4018
4071
|
# Canvas workflow for new compositions
|
|
4019
|
-
-
|
|
4020
|
-
-
|
|
4021
|
-
-
|
|
4022
|
-
|
|
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 {
|
|
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
|
-
|
|
30
|
-
|
|
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
|
|
818
|
+
const seenKeys = /* @__PURE__ */ new Set();
|
|
805
819
|
const uniqueStyles = styles.filter((style) => {
|
|
806
|
-
|
|
820
|
+
const key = getStyleUniqueKey(style);
|
|
821
|
+
if (seenKeys.has(key)) {
|
|
807
822
|
return false;
|
|
808
823
|
}
|
|
809
|
-
|
|
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
|
|
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 {
|
|
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
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
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(
|
|
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
|
|
4056
|
+
`Everything related to V4 ( Atomic ) canvas.
|
|
4004
4057
|
# Canvas workflow for new compositions
|
|
4005
|
-
-
|
|
4006
|
-
-
|
|
4007
|
-
-
|
|
4008
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
57
|
-
"@elementor/wp-media": "4.0.0
|
|
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
|
|
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:
|
|
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
|
}
|
package/src/legacy/types.ts
CHANGED
package/src/mcp/canvas-mcp.ts
CHANGED
|
@@ -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
|
|
13
|
+
`Everything related to V4 ( Atomic ) canvas.
|
|
14
14
|
# Canvas workflow for new compositions
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
|
|
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 {
|
|
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
|
-
|
|
38
|
-
|
|
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 {
|
|
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
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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(
|
|
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
|
|
60
|
+
const seenKeys = new Set< string >();
|
|
52
61
|
const uniqueStyles = styles.filter( ( style ) => {
|
|
53
|
-
|
|
62
|
+
const key = getStyleUniqueKey( style );
|
|
63
|
+
if ( seenKeys.has( key ) ) {
|
|
54
64
|
return false;
|
|
55
65
|
}
|
|
56
|
-
|
|
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
|
+
} );
|