@elementor/editor-canvas 4.2.0-beta1 → 4.2.3
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 +138 -58
- package/dist/index.mjs +103 -23
- package/package.json +20 -20
- package/src/__tests__/parse-xml.ts +2 -0
- package/src/components/__tests__/elements-overlays.test.tsx +7 -0
- package/src/components/elements-overlays.tsx +13 -10
- package/src/components/grid-outline/__tests__/grid-outline-overlay.test.tsx +13 -15
- package/src/components/grid-outline/grid-outline-overlay.tsx +3 -4
- package/src/components/outline-overlay.tsx +9 -2
- package/src/composition-builder/composition-builder.ts +12 -0
- package/src/form-structure/__tests__/form-structure-utils.test.ts +218 -0
- package/src/form-structure/enforce-form-ancestor-commands.ts +11 -4
- package/src/form-structure/utils.ts +47 -0
- package/src/init-style-transformers.ts +2 -2
- package/src/mcp/tools/build-composition/tool.ts +9 -2
- package/src/types/element-overlay.ts +2 -0
- package/src/utils/__tests__/grid-outline-utils.test.ts +39 -0
- package/src/utils/__tests__/outline-offset-utils.test.ts +39 -0
- package/src/utils/grid-outline-utils.ts +13 -2
- package/src/utils/outline-offset-utils.ts +11 -0
package/dist/index.mjs
CHANGED
|
@@ -460,8 +460,8 @@ function toGridTracks(computedStyle) {
|
|
|
460
460
|
return {
|
|
461
461
|
columns: parseTrackList(computedStyle.gridTemplateColumns),
|
|
462
462
|
rows: parseTrackList(computedStyle.gridTemplateRows),
|
|
463
|
-
columnGap:
|
|
464
|
-
rowGap:
|
|
463
|
+
columnGap: resolveGapPx(computedStyle.columnGap, computedStyle.width),
|
|
464
|
+
rowGap: resolveGapPx(computedStyle.rowGap, computedStyle.height),
|
|
465
465
|
padding: {
|
|
466
466
|
top: toPx(computedStyle.paddingTop),
|
|
467
467
|
right: toPx(computedStyle.paddingRight),
|
|
@@ -536,6 +536,14 @@ function toPx(value) {
|
|
|
536
536
|
const parsed = parseFloat(value);
|
|
537
537
|
return Number.isFinite(parsed) ? parsed : 0;
|
|
538
538
|
}
|
|
539
|
+
function resolveGapPx(value, referenceSize) {
|
|
540
|
+
if (value.trim().endsWith("%")) {
|
|
541
|
+
const percent = parseFloat(value);
|
|
542
|
+
const reference = parseFloat(referenceSize);
|
|
543
|
+
return Number.isFinite(percent) && Number.isFinite(reference) ? percent / 100 * reference : 0;
|
|
544
|
+
}
|
|
545
|
+
return toPx(value);
|
|
546
|
+
}
|
|
539
547
|
|
|
540
548
|
// src/hooks/use-grid-children.ts
|
|
541
549
|
import { useEffect as useEffect3, useState as useState2 } from "react";
|
|
@@ -807,8 +815,7 @@ var GridEmptyCellPositioner = ({ element }) => {
|
|
|
807
815
|
|
|
808
816
|
// src/components/grid-outline/grid-outline-overlay.tsx
|
|
809
817
|
import * as React6 from "react";
|
|
810
|
-
import {
|
|
811
|
-
import { booleanPropTypeUtil } from "@elementor/editor-props";
|
|
818
|
+
import { useElementEditorSettings } from "@elementor/editor-elements";
|
|
812
819
|
import { Box as Box2 } from "@elementor/ui";
|
|
813
820
|
import { FloatingPortal as FloatingPortal2 } from "@floating-ui/react";
|
|
814
821
|
|
|
@@ -909,6 +916,16 @@ var useHasOverlapping = () => {
|
|
|
909
916
|
return hasOverlapping;
|
|
910
917
|
};
|
|
911
918
|
|
|
919
|
+
// src/utils/outline-offset-utils.ts
|
|
920
|
+
var THIN_ELEMENT_MAX_HEIGHT_PX = 1;
|
|
921
|
+
var SMALLER_OUTLINE_OFFSET_WIDGET_TYPES = /* @__PURE__ */ new Set(["e-form-input"]);
|
|
922
|
+
function shouldUseSmallerOutlineOffset(element, widgetType) {
|
|
923
|
+
if (element.offsetHeight <= THIN_ELEMENT_MAX_HEIGHT_PX) {
|
|
924
|
+
return true;
|
|
925
|
+
}
|
|
926
|
+
return widgetType !== void 0 && SMALLER_OUTLINE_OFFSET_WIDGET_TYPES.has(widgetType);
|
|
927
|
+
}
|
|
928
|
+
|
|
912
929
|
// src/components/outline-overlay.tsx
|
|
913
930
|
var CANVAS_WRAPPER_ID = "elementor-preview-responsive-wrapper";
|
|
914
931
|
var OverlayBox = styled(Box, {
|
|
@@ -920,12 +937,18 @@ var OverlayBox = styled(Box, {
|
|
|
920
937
|
pointerEvents: "none"
|
|
921
938
|
})
|
|
922
939
|
);
|
|
923
|
-
var OutlineOverlay = ({
|
|
940
|
+
var OutlineOverlay = ({
|
|
941
|
+
element,
|
|
942
|
+
isSelected,
|
|
943
|
+
id,
|
|
944
|
+
isGlobal = false,
|
|
945
|
+
widgetType
|
|
946
|
+
}) => {
|
|
924
947
|
const { context, floating, isVisible } = useFloatingOnElement({ element, isSelected });
|
|
925
948
|
const { getFloatingProps, getReferenceProps } = useInteractions([useHover(context)]);
|
|
926
949
|
const hasOverlapping = useHasOverlapping();
|
|
927
950
|
useBindReactPropsToElement(element, getReferenceProps);
|
|
928
|
-
const isSmallerOffset = element
|
|
951
|
+
const isSmallerOffset = shouldUseSmallerOutlineOffset(element, widgetType);
|
|
929
952
|
return isVisible && !hasOverlapping && /* @__PURE__ */ React.createElement(FloatingPortal, { id: CANVAS_WRAPPER_ID }, /* @__PURE__ */ React.createElement(
|
|
930
953
|
OverlayBox,
|
|
931
954
|
{
|
|
@@ -1081,8 +1104,8 @@ function GridOutline({ element, tracks, width, height }) {
|
|
|
1081
1104
|
|
|
1082
1105
|
// src/components/grid-outline/grid-outline-overlay.tsx
|
|
1083
1106
|
var GridOutlineOverlay = ({ element, id, isSelected }) => {
|
|
1084
|
-
const
|
|
1085
|
-
const enabled =
|
|
1107
|
+
const settings = useElementEditorSettings(id);
|
|
1108
|
+
const enabled = settings?.grid_outline;
|
|
1086
1109
|
const rect = useElementRect(element);
|
|
1087
1110
|
const tracks = useGridTracks(element, rect);
|
|
1088
1111
|
const { floating } = useFloatingOnElement({ element, isSelected });
|
|
@@ -1105,9 +1128,10 @@ var GridOutlineOverlay = ({ element, id, isSelected }) => {
|
|
|
1105
1128
|
};
|
|
1106
1129
|
|
|
1107
1130
|
// src/components/elements-overlays.tsx
|
|
1131
|
+
var hasGridStyleDisplay = (element) => {
|
|
1132
|
+
return element.computedStyleMap().get("display")?.toString() === "grid";
|
|
1133
|
+
};
|
|
1108
1134
|
var ELEMENTS_DATA_ATTR = "atomic";
|
|
1109
|
-
var E_GRID_TYPE = "e-grid";
|
|
1110
|
-
var isGridElement = (element) => [element.dataset.eType, element.dataset.element_type].includes(E_GRID_TYPE);
|
|
1111
1135
|
var overlayRegistry = [
|
|
1112
1136
|
{
|
|
1113
1137
|
component: OutlineOverlay,
|
|
@@ -1115,11 +1139,11 @@ var overlayRegistry = [
|
|
|
1115
1139
|
},
|
|
1116
1140
|
{
|
|
1117
1141
|
component: GridEmptyCellPositioner,
|
|
1118
|
-
shouldRender: ({ element }) =>
|
|
1142
|
+
shouldRender: ({ element }) => hasGridStyleDisplay(element)
|
|
1119
1143
|
},
|
|
1120
1144
|
{
|
|
1121
1145
|
component: GridOutlineOverlay,
|
|
1122
|
-
shouldRender: ({
|
|
1146
|
+
shouldRender: ({ isSelected, element }) => isSelected && hasGridStyleDisplay(element)
|
|
1123
1147
|
}
|
|
1124
1148
|
];
|
|
1125
1149
|
function ElementsOverlays() {
|
|
@@ -1132,17 +1156,18 @@ function ElementsOverlays() {
|
|
|
1132
1156
|
if (!isActive) {
|
|
1133
1157
|
return null;
|
|
1134
1158
|
}
|
|
1135
|
-
return elements.map(({ id, domElement, isGlobal }) => {
|
|
1159
|
+
return elements.map(({ id, domElement, isGlobal, widgetType }) => {
|
|
1136
1160
|
const isSelected = selected.element?.id === id;
|
|
1137
1161
|
return overlayRegistry.map(
|
|
1138
|
-
({ shouldRender, component: Overlay }, index) => shouldRender({ id, element: domElement, isSelected }) && /* @__PURE__ */ React7.createElement(
|
|
1162
|
+
({ shouldRender, component: Overlay }, index) => shouldRender({ id, element: domElement, isSelected, widgetType }) && /* @__PURE__ */ React7.createElement(
|
|
1139
1163
|
Overlay,
|
|
1140
1164
|
{
|
|
1141
1165
|
key: `${id}-${index}`,
|
|
1142
1166
|
id,
|
|
1143
1167
|
element: domElement,
|
|
1144
1168
|
isSelected,
|
|
1145
|
-
isGlobal
|
|
1169
|
+
isGlobal,
|
|
1170
|
+
widgetType
|
|
1146
1171
|
}
|
|
1147
1172
|
)
|
|
1148
1173
|
);
|
|
@@ -1155,7 +1180,8 @@ function useElementsDom() {
|
|
|
1155
1180
|
return getElements().filter((el) => isV4Element(el.view?.el?.dataset)).map((element) => ({
|
|
1156
1181
|
id: element.id,
|
|
1157
1182
|
domElement: element.view?.getDomElement?.()?.get?.(0),
|
|
1158
|
-
isGlobal: element.model.get("isGlobal") ?? false
|
|
1183
|
+
isGlobal: element.model.get("isGlobal") ?? false,
|
|
1184
|
+
widgetType: element.model.get("widgetType")
|
|
1159
1185
|
})).filter((item) => !!item.domElement);
|
|
1160
1186
|
}
|
|
1161
1187
|
);
|
|
@@ -1984,6 +2010,46 @@ function clipboardRootsAreAtomicForms(elements) {
|
|
|
1984
2010
|
}
|
|
1985
2011
|
return elements.every((el) => getClipboardElementType(el) === FORM_ELEMENT_TYPE);
|
|
1986
2012
|
}
|
|
2013
|
+
function hasFormAncestor(node) {
|
|
2014
|
+
return node.closest(FORM_ELEMENT_TYPE) !== null;
|
|
2015
|
+
}
|
|
2016
|
+
function collectFormAncestorErrors(xml) {
|
|
2017
|
+
const errors = [];
|
|
2018
|
+
for (const node of xml.querySelectorAll("*")) {
|
|
2019
|
+
if (!FORM_FIELD_ELEMENT_TYPES.has(node.tagName.toLowerCase())) {
|
|
2020
|
+
continue;
|
|
2021
|
+
}
|
|
2022
|
+
if (hasFormAncestor(node)) {
|
|
2023
|
+
continue;
|
|
2024
|
+
}
|
|
2025
|
+
const id = node.getAttribute("configuration-id");
|
|
2026
|
+
errors.push(
|
|
2027
|
+
`<${node.tagName}${id ? ` configuration-id="${id}"` : ""}> must be nested inside <e-form> (any ancestor depth is allowed).`
|
|
2028
|
+
);
|
|
2029
|
+
}
|
|
2030
|
+
return errors;
|
|
2031
|
+
}
|
|
2032
|
+
function collectSubmitButtonErrors(xml) {
|
|
2033
|
+
const errors = [];
|
|
2034
|
+
for (const form of xml.querySelectorAll("e-form")) {
|
|
2035
|
+
const submitButtons = form.querySelectorAll("e-form-submit-button");
|
|
2036
|
+
if (submitButtons.length === 0) {
|
|
2037
|
+
errors.push(`<e-form> has no <e-form-submit-button>.`);
|
|
2038
|
+
} else if (submitButtons.length > 1) {
|
|
2039
|
+
errors.push(`<e-form> has ${submitButtons.length} submit buttons \u2014 only 1 is allowed.`);
|
|
2040
|
+
}
|
|
2041
|
+
}
|
|
2042
|
+
return errors;
|
|
2043
|
+
}
|
|
2044
|
+
function collectEmptyMessageErrors(xml) {
|
|
2045
|
+
const errors = [];
|
|
2046
|
+
for (const node of xml.querySelectorAll("e-form-success-message, e-form-error-message")) {
|
|
2047
|
+
if (node.children.length === 0) {
|
|
2048
|
+
errors.push(`<${node.tagName}> must have at least one child element (e.g. <e-atomic-paragraph>).`);
|
|
2049
|
+
}
|
|
2050
|
+
}
|
|
2051
|
+
return errors;
|
|
2052
|
+
}
|
|
1987
2053
|
|
|
1988
2054
|
// src/form-structure/enforce-form-ancestor-commands.ts
|
|
1989
2055
|
var FORM_FIELDS_OUTSIDE_ALERT = {
|
|
@@ -2010,7 +2076,8 @@ function blockFormFieldCreate(args) {
|
|
|
2010
2076
|
if (!elementType || !FORM_FIELD_ELEMENT_TYPES.has(elementType)) {
|
|
2011
2077
|
return false;
|
|
2012
2078
|
}
|
|
2013
|
-
|
|
2079
|
+
const containers = args.containers ?? [args.container];
|
|
2080
|
+
if (containers.some((container) => !isWithinForm(container))) {
|
|
2014
2081
|
handleBlockedFormField();
|
|
2015
2082
|
return true;
|
|
2016
2083
|
}
|
|
@@ -2018,10 +2085,10 @@ function blockFormFieldCreate(args) {
|
|
|
2018
2085
|
}
|
|
2019
2086
|
function blockFormFieldMove(args) {
|
|
2020
2087
|
const { containers = [args.container], target } = args;
|
|
2021
|
-
const
|
|
2022
|
-
(container) => container ? hasElementTypes(container, FORM_FIELD_ELEMENT_TYPES) : false
|
|
2088
|
+
const hasLooseFormFields = containers.some(
|
|
2089
|
+
(container) => container ? !hasElementType(container, FORM_ELEMENT_TYPE) && hasElementTypes(container, FORM_FIELD_ELEMENT_TYPES) : false
|
|
2023
2090
|
);
|
|
2024
|
-
if (
|
|
2091
|
+
if (hasLooseFormFields && !isWithinForm(target) && !movedContainersIncludeAtomicFormRoot(containers)) {
|
|
2025
2092
|
handleBlockedFormField();
|
|
2026
2093
|
return true;
|
|
2027
2094
|
}
|
|
@@ -2677,13 +2744,13 @@ function initStyleTransformers() {
|
|
|
2677
2744
|
"layout-direction",
|
|
2678
2745
|
createMultiPropsTransformer(["row", "column"], ({ propKey, key }) => `${key}-${propKey}`)
|
|
2679
2746
|
).register("flex", flexTransformer).register(
|
|
2680
|
-
"border-width",
|
|
2747
|
+
"border-width-v2",
|
|
2681
2748
|
createMultiPropsTransformer(
|
|
2682
2749
|
["block-start", "block-end", "inline-start", "inline-end"],
|
|
2683
2750
|
({ key }) => `border-${key}-width`
|
|
2684
2751
|
)
|
|
2685
2752
|
).register(
|
|
2686
|
-
"border-radius",
|
|
2753
|
+
"border-radius-v2",
|
|
2687
2754
|
createMultiPropsTransformer(
|
|
2688
2755
|
["start-start", "start-end", "end-start", "end-end"],
|
|
2689
2756
|
({ key }) => `border-${key}-radius`
|
|
@@ -5158,6 +5225,11 @@ var CompositionBuilder = class _CompositionBuilder {
|
|
|
5158
5225
|
throw new Error(`Invalid element structure:
|
|
5159
5226
|
${childTypeErrors.join("\n")}`);
|
|
5160
5227
|
}
|
|
5228
|
+
const formErrors = [
|
|
5229
|
+
...collectFormAncestorErrors(this.xml),
|
|
5230
|
+
...collectSubmitButtonErrors(this.xml),
|
|
5231
|
+
...collectEmptyMessageErrors(this.xml)
|
|
5232
|
+
];
|
|
5161
5233
|
const children = Array.from(this.xml.children);
|
|
5162
5234
|
for (const childNode of children) {
|
|
5163
5235
|
const modelTree = this.buildModelTree(childNode, widgetsCache);
|
|
@@ -5192,6 +5264,7 @@ ${childTypeErrors.join("\n")}`);
|
|
|
5192
5264
|
return {
|
|
5193
5265
|
configErrors,
|
|
5194
5266
|
styleErrors,
|
|
5267
|
+
formErrors,
|
|
5195
5268
|
rootContainers: [...this.rootContainers]
|
|
5196
5269
|
};
|
|
5197
5270
|
}
|
|
@@ -5502,7 +5575,11 @@ var initBuildCompositionsTool = (reg) => {
|
|
|
5502
5575
|
compositionBuilder.setElementConfig(elementConfig);
|
|
5503
5576
|
compositionBuilder.setStylesConfig(stylesConfig);
|
|
5504
5577
|
compositionBuilder.setCustomCSS(customCSS);
|
|
5505
|
-
const {
|
|
5578
|
+
const {
|
|
5579
|
+
configErrors,
|
|
5580
|
+
formErrors,
|
|
5581
|
+
rootContainers: generatedRootContainers
|
|
5582
|
+
} = await compositionBuilder.build(targetContainer);
|
|
5506
5583
|
rootContainers.push(...generatedRootContainers);
|
|
5507
5584
|
generatedXML = new XMLSerializer().serializeToString(compositionBuilder.getXML());
|
|
5508
5585
|
rootContainers.forEach((container) => {
|
|
@@ -5517,6 +5594,9 @@ var initBuildCompositionsTool = (reg) => {
|
|
|
5517
5594
|
if (configErrors.length) {
|
|
5518
5595
|
errors.push(...configErrors.map((msg) => new Error(msg)));
|
|
5519
5596
|
}
|
|
5597
|
+
if (formErrors.length) {
|
|
5598
|
+
errors.push(...formErrors.map((msg) => new Error(msg)));
|
|
5599
|
+
}
|
|
5520
5600
|
} catch (error) {
|
|
5521
5601
|
errors.push(error);
|
|
5522
5602
|
}
|
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.
|
|
4
|
+
"version": "4.2.3",
|
|
5
5
|
"private": false,
|
|
6
6
|
"author": "Elementor Team",
|
|
7
7
|
"homepage": "https://elementor.com/",
|
|
@@ -37,27 +37,27 @@
|
|
|
37
37
|
"react-dom": "^18.3.1"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@elementor/editor": "4.2.
|
|
40
|
+
"@elementor/editor": "4.2.3",
|
|
41
41
|
"dompurify": "^3.2.6",
|
|
42
|
-
"@elementor/editor-controls": "4.2.
|
|
43
|
-
"@elementor/editor-documents": "4.2.
|
|
44
|
-
"@elementor/editor-elements": "4.2.
|
|
45
|
-
"@elementor/editor-interactions": "4.2.
|
|
46
|
-
"@elementor/editor-mcp": "4.2.
|
|
47
|
-
"@elementor/editor-notifications": "4.2.
|
|
48
|
-
"@elementor/editor-props": "4.2.
|
|
49
|
-
"@elementor/editor-responsive": "4.2.
|
|
50
|
-
"@elementor/editor-styles": "4.2.
|
|
51
|
-
"@elementor/editor-styles-repository": "4.2.
|
|
52
|
-
"@elementor/editor-ui": "4.2.
|
|
53
|
-
"@elementor/editor-v1-adapters": "4.2.
|
|
54
|
-
"@elementor/events": "4.2.
|
|
55
|
-
"@elementor/http-client": "4.2.
|
|
56
|
-
"@elementor/schema": "4.2.
|
|
57
|
-
"@elementor/twing": "4.2.
|
|
42
|
+
"@elementor/editor-controls": "4.2.3",
|
|
43
|
+
"@elementor/editor-documents": "4.2.3",
|
|
44
|
+
"@elementor/editor-elements": "4.2.3",
|
|
45
|
+
"@elementor/editor-interactions": "4.2.3",
|
|
46
|
+
"@elementor/editor-mcp": "4.2.3",
|
|
47
|
+
"@elementor/editor-notifications": "4.2.3",
|
|
48
|
+
"@elementor/editor-props": "4.2.3",
|
|
49
|
+
"@elementor/editor-responsive": "4.2.3",
|
|
50
|
+
"@elementor/editor-styles": "4.2.3",
|
|
51
|
+
"@elementor/editor-styles-repository": "4.2.3",
|
|
52
|
+
"@elementor/editor-ui": "4.2.3",
|
|
53
|
+
"@elementor/editor-v1-adapters": "4.2.3",
|
|
54
|
+
"@elementor/events": "4.2.3",
|
|
55
|
+
"@elementor/http-client": "4.2.3",
|
|
56
|
+
"@elementor/schema": "4.2.3",
|
|
57
|
+
"@elementor/twing": "4.2.3",
|
|
58
58
|
"@elementor/ui": "1.37.5",
|
|
59
|
-
"@elementor/utils": "4.2.
|
|
60
|
-
"@elementor/wp-media": "4.2.
|
|
59
|
+
"@elementor/utils": "4.2.3",
|
|
60
|
+
"@elementor/wp-media": "4.2.3",
|
|
61
61
|
"@floating-ui/react": "^0.27.5",
|
|
62
62
|
"@wordpress/i18n": "^5.13.0"
|
|
63
63
|
},
|
|
@@ -19,6 +19,10 @@ jest.mock( '@elementor/editor-v1-adapters', () => ( {
|
|
|
19
19
|
isExperimentActive: jest.fn(),
|
|
20
20
|
} ) );
|
|
21
21
|
|
|
22
|
+
function mockGridComputedStyle( element: HTMLElement ) {
|
|
23
|
+
element.computedStyleMap = jest.fn().mockReturnValue( new Map( [ [ 'display', { toString: () => 'grid' } ] ] ) );
|
|
24
|
+
}
|
|
25
|
+
|
|
22
26
|
describe( '<ElementsOverlays />', () => {
|
|
23
27
|
beforeEach( () => {
|
|
24
28
|
const documentEl = createDOMElement( { tag: 'div' } );
|
|
@@ -26,6 +30,9 @@ describe( '<ElementsOverlays />', () => {
|
|
|
26
30
|
const atomic1El = createDOMElement( { tag: 'div', attrs: { 'data-atomic': '', id: '10' } } );
|
|
27
31
|
const atomic2El = createDOMElement( { tag: 'div', attrs: { 'data-atomic': '', id: '20' } } );
|
|
28
32
|
|
|
33
|
+
mockGridComputedStyle( atomic1El );
|
|
34
|
+
mockGridComputedStyle( atomic2El );
|
|
35
|
+
|
|
29
36
|
jest.mocked( useEditMode ).mockReturnValue( 'edit' );
|
|
30
37
|
jest.mocked( useIsRouteActive ).mockReturnValue( false );
|
|
31
38
|
jest.mocked( isExperimentActive ).mockReturnValue( true );
|
|
@@ -11,11 +11,11 @@ import type { ElementOverlayConfig } from '../types/element-overlay';
|
|
|
11
11
|
import { GridEmptyCellPositioner, GridOutlineOverlay } from './grid-outline';
|
|
12
12
|
import { OutlineOverlay } from './outline-overlay';
|
|
13
13
|
|
|
14
|
-
const
|
|
15
|
-
|
|
14
|
+
const hasGridStyleDisplay = ( element: HTMLElement ): boolean => {
|
|
15
|
+
return element.computedStyleMap().get( 'display' )?.toString() === 'grid';
|
|
16
|
+
};
|
|
16
17
|
|
|
17
|
-
const
|
|
18
|
-
[ element.dataset.eType, element.dataset.element_type ].includes( E_GRID_TYPE );
|
|
18
|
+
const ELEMENTS_DATA_ATTR = 'atomic';
|
|
19
19
|
|
|
20
20
|
const overlayRegistry: ElementOverlayConfig[] = [
|
|
21
21
|
{
|
|
@@ -24,11 +24,11 @@ const overlayRegistry: ElementOverlayConfig[] = [
|
|
|
24
24
|
},
|
|
25
25
|
{
|
|
26
26
|
component: GridEmptyCellPositioner,
|
|
27
|
-
shouldRender: ( { element } ) =>
|
|
27
|
+
shouldRender: ( { element } ) => hasGridStyleDisplay( element ),
|
|
28
28
|
},
|
|
29
29
|
{
|
|
30
30
|
component: GridOutlineOverlay,
|
|
31
|
-
shouldRender: ( {
|
|
31
|
+
shouldRender: ( { isSelected, element } ) => isSelected && hasGridStyleDisplay( element ),
|
|
32
32
|
},
|
|
33
33
|
];
|
|
34
34
|
|
|
@@ -45,18 +45,19 @@ export function ElementsOverlays() {
|
|
|
45
45
|
return null;
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
return elements.map( ( { id, domElement, isGlobal } ) => {
|
|
48
|
+
return elements.map( ( { id, domElement, isGlobal, widgetType } ) => {
|
|
49
49
|
const isSelected = selected.element?.id === id;
|
|
50
50
|
|
|
51
51
|
return overlayRegistry.map(
|
|
52
52
|
( { shouldRender, component: Overlay }, index ) =>
|
|
53
|
-
shouldRender( { id, element: domElement, isSelected } ) && (
|
|
53
|
+
shouldRender( { id, element: domElement, isSelected, widgetType } ) && (
|
|
54
54
|
<Overlay
|
|
55
55
|
key={ `${ id }-${ index }` }
|
|
56
56
|
id={ id }
|
|
57
57
|
element={ domElement }
|
|
58
58
|
isSelected={ isSelected }
|
|
59
59
|
isGlobal={ isGlobal }
|
|
60
|
+
widgetType={ widgetType }
|
|
60
61
|
/>
|
|
61
62
|
)
|
|
62
63
|
);
|
|
@@ -67,18 +68,20 @@ type ElementData = {
|
|
|
67
68
|
id: string;
|
|
68
69
|
domElement: HTMLElement;
|
|
69
70
|
isGlobal: boolean;
|
|
71
|
+
widgetType: string | undefined;
|
|
70
72
|
};
|
|
71
73
|
|
|
72
|
-
function useElementsDom() {
|
|
74
|
+
function useElementsDom(): ElementData[] {
|
|
73
75
|
return useListenTo(
|
|
74
76
|
[ windowEvent( 'elementor/editor/element-rendered' ), windowEvent( 'elementor/editor/element-destroyed' ) ],
|
|
75
|
-
() => {
|
|
77
|
+
(): ElementData[] => {
|
|
76
78
|
return getElements()
|
|
77
79
|
.filter( ( el ) => isV4Element( el.view?.el?.dataset ) )
|
|
78
80
|
.map( ( element ) => ( {
|
|
79
81
|
id: element.id,
|
|
80
82
|
domElement: element.view?.getDomElement?.()?.get?.( 0 ),
|
|
81
83
|
isGlobal: element.model.get( 'isGlobal' ) ?? false,
|
|
84
|
+
widgetType: element.model.get( 'widgetType' ),
|
|
82
85
|
} ) )
|
|
83
86
|
.filter( ( item ): item is ElementData => !! item.domElement );
|
|
84
87
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import { createDOMElement, renderWithTheme } from 'test-utils';
|
|
3
|
-
import {
|
|
3
|
+
import { useElementEditorSettings } from '@elementor/editor-elements';
|
|
4
4
|
import { screen } from '@testing-library/react';
|
|
5
5
|
|
|
6
6
|
import { useElementRect } from '../../../hooks/use-element-rect';
|
|
@@ -34,12 +34,10 @@ const EMPTY_TRACKS: GridTracks = {
|
|
|
34
34
|
borderColor: '',
|
|
35
35
|
};
|
|
36
36
|
|
|
37
|
-
function mockGridOutlineSetting(
|
|
38
|
-
jest.mocked(
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
settings: { grid_outline: value },
|
|
42
|
-
} );
|
|
37
|
+
function mockGridOutlineSetting( gridOutline: boolean | undefined ) {
|
|
38
|
+
jest.mocked( useElementEditorSettings ).mockReturnValue(
|
|
39
|
+
gridOutline === undefined ? {} : { grid_outline: gridOutline }
|
|
40
|
+
);
|
|
43
41
|
}
|
|
44
42
|
|
|
45
43
|
function mockFloating() {
|
|
@@ -87,7 +85,7 @@ describe( '<GridOutlineOverlay />', () => {
|
|
|
87
85
|
} );
|
|
88
86
|
|
|
89
87
|
it( 'renders the outline svg with the element id when the setting is enabled', () => {
|
|
90
|
-
mockGridOutlineSetting(
|
|
88
|
+
mockGridOutlineSetting( true );
|
|
91
89
|
|
|
92
90
|
renderOverlay();
|
|
93
91
|
|
|
@@ -97,8 +95,8 @@ describe( '<GridOutlineOverlay />', () => {
|
|
|
97
95
|
expect( overlay.querySelector( 'svg' ) ).toBeInTheDocument();
|
|
98
96
|
} );
|
|
99
97
|
|
|
100
|
-
it( 'treats
|
|
101
|
-
mockGridOutlineSetting(
|
|
98
|
+
it( 'treats an unset setting as default-on and renders the outline', () => {
|
|
99
|
+
mockGridOutlineSetting( undefined );
|
|
102
100
|
|
|
103
101
|
renderOverlay();
|
|
104
102
|
|
|
@@ -106,7 +104,7 @@ describe( '<GridOutlineOverlay />', () => {
|
|
|
106
104
|
} );
|
|
107
105
|
|
|
108
106
|
it( 'renders nothing when the setting is explicitly disabled', () => {
|
|
109
|
-
mockGridOutlineSetting(
|
|
107
|
+
mockGridOutlineSetting( false );
|
|
110
108
|
|
|
111
109
|
renderOverlay();
|
|
112
110
|
|
|
@@ -114,7 +112,7 @@ describe( '<GridOutlineOverlay />', () => {
|
|
|
114
112
|
} );
|
|
115
113
|
|
|
116
114
|
it( 'renders nothing while tracks have not resolved yet', () => {
|
|
117
|
-
mockGridOutlineSetting(
|
|
115
|
+
mockGridOutlineSetting( true );
|
|
118
116
|
jest.mocked( useGridTracks ).mockReturnValue( EMPTY_TRACKS );
|
|
119
117
|
|
|
120
118
|
renderOverlay();
|
|
@@ -123,7 +121,7 @@ describe( '<GridOutlineOverlay />', () => {
|
|
|
123
121
|
} );
|
|
124
122
|
|
|
125
123
|
it( 'renders one rect per cell when the grid has a gap', () => {
|
|
126
|
-
mockGridOutlineSetting(
|
|
124
|
+
mockGridOutlineSetting( undefined );
|
|
127
125
|
jest.mocked( useGridTracks ).mockReturnValue( {
|
|
128
126
|
...NON_EMPTY_TRACKS,
|
|
129
127
|
columns: [ 100, 100, 100 ],
|
|
@@ -140,7 +138,7 @@ describe( '<GridOutlineOverlay />', () => {
|
|
|
140
138
|
} );
|
|
141
139
|
|
|
142
140
|
it( 'renders boundary lines when the grid has no gap', () => {
|
|
143
|
-
mockGridOutlineSetting(
|
|
141
|
+
mockGridOutlineSetting( undefined );
|
|
144
142
|
jest.mocked( useGridTracks ).mockReturnValue( {
|
|
145
143
|
...NON_EMPTY_TRACKS,
|
|
146
144
|
columns: [ 100, 100, 100 ],
|
|
@@ -157,7 +155,7 @@ describe( '<GridOutlineOverlay />', () => {
|
|
|
157
155
|
} );
|
|
158
156
|
|
|
159
157
|
it( 'mounts inside the canvas wrapper portal', () => {
|
|
160
|
-
mockGridOutlineSetting(
|
|
158
|
+
mockGridOutlineSetting( undefined );
|
|
161
159
|
|
|
162
160
|
renderOverlay();
|
|
163
161
|
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import {
|
|
3
|
-
import { booleanPropTypeUtil } from '@elementor/editor-props';
|
|
2
|
+
import { useElementEditorSettings } from '@elementor/editor-elements';
|
|
4
3
|
import { Box } from '@elementor/ui';
|
|
5
4
|
import { FloatingPortal } from '@floating-ui/react';
|
|
6
5
|
|
|
@@ -12,8 +11,8 @@ import { CANVAS_WRAPPER_ID } from '../outline-overlay';
|
|
|
12
11
|
import { GridOutline } from './grid-outline';
|
|
13
12
|
|
|
14
13
|
export const GridOutlineOverlay = ( { element, id, isSelected }: ElementOverlayProps ): React.ReactElement | null => {
|
|
15
|
-
const
|
|
16
|
-
const enabled =
|
|
14
|
+
const settings = useElementEditorSettings( id );
|
|
15
|
+
const enabled = settings?.grid_outline;
|
|
17
16
|
const rect = useElementRect( element );
|
|
18
17
|
const tracks = useGridTracks( element, rect );
|
|
19
18
|
const { floating } = useFloatingOnElement( { element, isSelected } );
|
|
@@ -6,6 +6,7 @@ import { useBindReactPropsToElement } from '../hooks/use-bind-react-props-to-ele
|
|
|
6
6
|
import { useFloatingOnElement } from '../hooks/use-floating-on-element';
|
|
7
7
|
import { useHasOverlapping } from '../hooks/use-has-overlapping';
|
|
8
8
|
import type { ElementOverlayProps } from '../types/element-overlay';
|
|
9
|
+
import { shouldUseSmallerOutlineOffset } from '../utils/outline-offset-utils';
|
|
9
10
|
|
|
10
11
|
export const CANVAS_WRAPPER_ID = 'elementor-preview-responsive-wrapper';
|
|
11
12
|
|
|
@@ -25,13 +26,19 @@ const OverlayBox = styled( Box, {
|
|
|
25
26
|
} )
|
|
26
27
|
);
|
|
27
28
|
|
|
28
|
-
export const OutlineOverlay = ( {
|
|
29
|
+
export const OutlineOverlay = ( {
|
|
30
|
+
element,
|
|
31
|
+
isSelected,
|
|
32
|
+
id,
|
|
33
|
+
isGlobal = false,
|
|
34
|
+
widgetType,
|
|
35
|
+
}: Props ): React.ReactElement | false => {
|
|
29
36
|
const { context, floating, isVisible } = useFloatingOnElement( { element, isSelected } );
|
|
30
37
|
const { getFloatingProps, getReferenceProps } = useInteractions( [ useHover( context ) ] );
|
|
31
38
|
const hasOverlapping = useHasOverlapping();
|
|
32
39
|
|
|
33
40
|
useBindReactPropsToElement( element, getReferenceProps );
|
|
34
|
-
const isSmallerOffset = element
|
|
41
|
+
const isSmallerOffset = shouldUseSmallerOutlineOffset( element, widgetType );
|
|
35
42
|
|
|
36
43
|
return (
|
|
37
44
|
isVisible &&
|
|
@@ -11,6 +11,11 @@ import {
|
|
|
11
11
|
} from '@elementor/editor-elements';
|
|
12
12
|
import { type z } from '@elementor/schema';
|
|
13
13
|
|
|
14
|
+
import {
|
|
15
|
+
collectEmptyMessageErrors,
|
|
16
|
+
collectFormAncestorErrors,
|
|
17
|
+
collectSubmitButtonErrors,
|
|
18
|
+
} from '../form-structure/utils';
|
|
14
19
|
import { doUpdateElementProperty } from '../mcp/utils/do-update-element-property';
|
|
15
20
|
import { mergeCustomCssText } from '../mcp/utils/merge-custom-css';
|
|
16
21
|
import { RequiredChildrenEnforcer } from './utils/required-children-enforcer';
|
|
@@ -294,6 +299,12 @@ export class CompositionBuilder {
|
|
|
294
299
|
throw new Error( `Invalid element structure:\n${ childTypeErrors.join( '\n' ) }` );
|
|
295
300
|
}
|
|
296
301
|
|
|
302
|
+
const formErrors = [
|
|
303
|
+
...collectFormAncestorErrors( this.xml ),
|
|
304
|
+
...collectSubmitButtonErrors( this.xml ),
|
|
305
|
+
...collectEmptyMessageErrors( this.xml ),
|
|
306
|
+
];
|
|
307
|
+
|
|
297
308
|
const children = Array.from( this.xml.children );
|
|
298
309
|
for ( const childNode of children ) {
|
|
299
310
|
const modelTree = this.buildModelTree( childNode, widgetsCache );
|
|
@@ -332,6 +343,7 @@ export class CompositionBuilder {
|
|
|
332
343
|
return {
|
|
333
344
|
configErrors,
|
|
334
345
|
styleErrors,
|
|
346
|
+
formErrors,
|
|
335
347
|
rootContainers: [ ...this.rootContainers ],
|
|
336
348
|
};
|
|
337
349
|
}
|