@elementor/editor-canvas 4.2.0-beta1 → 4.2.0-beta2

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.mjs CHANGED
@@ -807,8 +807,7 @@ var GridEmptyCellPositioner = ({ element }) => {
807
807
 
808
808
  // src/components/grid-outline/grid-outline-overlay.tsx
809
809
  import * as React6 from "react";
810
- import { useSelectedElementSettings } from "@elementor/editor-elements";
811
- import { booleanPropTypeUtil } from "@elementor/editor-props";
810
+ import { useElementEditorSettings } from "@elementor/editor-elements";
812
811
  import { Box as Box2 } from "@elementor/ui";
813
812
  import { FloatingPortal as FloatingPortal2 } from "@floating-ui/react";
814
813
 
@@ -909,6 +908,16 @@ var useHasOverlapping = () => {
909
908
  return hasOverlapping;
910
909
  };
911
910
 
911
+ // src/utils/outline-offset-utils.ts
912
+ var THIN_ELEMENT_MAX_HEIGHT_PX = 1;
913
+ var SMALLER_OUTLINE_OFFSET_WIDGET_TYPES = /* @__PURE__ */ new Set(["e-form-input"]);
914
+ function shouldUseSmallerOutlineOffset(element, widgetType) {
915
+ if (element.offsetHeight <= THIN_ELEMENT_MAX_HEIGHT_PX) {
916
+ return true;
917
+ }
918
+ return widgetType !== void 0 && SMALLER_OUTLINE_OFFSET_WIDGET_TYPES.has(widgetType);
919
+ }
920
+
912
921
  // src/components/outline-overlay.tsx
913
922
  var CANVAS_WRAPPER_ID = "elementor-preview-responsive-wrapper";
914
923
  var OverlayBox = styled(Box, {
@@ -920,12 +929,18 @@ var OverlayBox = styled(Box, {
920
929
  pointerEvents: "none"
921
930
  })
922
931
  );
923
- var OutlineOverlay = ({ element, isSelected, id, isGlobal = false }) => {
932
+ var OutlineOverlay = ({
933
+ element,
934
+ isSelected,
935
+ id,
936
+ isGlobal = false,
937
+ widgetType
938
+ }) => {
924
939
  const { context, floating, isVisible } = useFloatingOnElement({ element, isSelected });
925
940
  const { getFloatingProps, getReferenceProps } = useInteractions([useHover(context)]);
926
941
  const hasOverlapping = useHasOverlapping();
927
942
  useBindReactPropsToElement(element, getReferenceProps);
928
- const isSmallerOffset = element.offsetHeight <= 1;
943
+ const isSmallerOffset = shouldUseSmallerOutlineOffset(element, widgetType);
929
944
  return isVisible && !hasOverlapping && /* @__PURE__ */ React.createElement(FloatingPortal, { id: CANVAS_WRAPPER_ID }, /* @__PURE__ */ React.createElement(
930
945
  OverlayBox,
931
946
  {
@@ -1081,8 +1096,8 @@ function GridOutline({ element, tracks, width, height }) {
1081
1096
 
1082
1097
  // src/components/grid-outline/grid-outline-overlay.tsx
1083
1098
  var GridOutlineOverlay = ({ element, id, isSelected }) => {
1084
- const { settings } = useSelectedElementSettings();
1085
- const enabled = booleanPropTypeUtil.extract(settings?.grid_outline);
1099
+ const settings = useElementEditorSettings(id);
1100
+ const enabled = settings?.grid_outline;
1086
1101
  const rect = useElementRect(element);
1087
1102
  const tracks = useGridTracks(element, rect);
1088
1103
  const { floating } = useFloatingOnElement({ element, isSelected });
@@ -1105,9 +1120,10 @@ var GridOutlineOverlay = ({ element, id, isSelected }) => {
1105
1120
  };
1106
1121
 
1107
1122
  // src/components/elements-overlays.tsx
1123
+ var hasGridStyleDisplay = (element) => {
1124
+ return element.computedStyleMap().get("display")?.toString() === "grid";
1125
+ };
1108
1126
  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
1127
  var overlayRegistry = [
1112
1128
  {
1113
1129
  component: OutlineOverlay,
@@ -1115,11 +1131,11 @@ var overlayRegistry = [
1115
1131
  },
1116
1132
  {
1117
1133
  component: GridEmptyCellPositioner,
1118
- shouldRender: ({ element }) => isGridElement(element)
1134
+ shouldRender: ({ element }) => hasGridStyleDisplay(element)
1119
1135
  },
1120
1136
  {
1121
1137
  component: GridOutlineOverlay,
1122
- shouldRender: ({ element, isSelected }) => isSelected && isGridElement(element)
1138
+ shouldRender: ({ isSelected, element }) => isSelected && hasGridStyleDisplay(element)
1123
1139
  }
1124
1140
  ];
1125
1141
  function ElementsOverlays() {
@@ -1132,17 +1148,18 @@ function ElementsOverlays() {
1132
1148
  if (!isActive) {
1133
1149
  return null;
1134
1150
  }
1135
- return elements.map(({ id, domElement, isGlobal }) => {
1151
+ return elements.map(({ id, domElement, isGlobal, widgetType }) => {
1136
1152
  const isSelected = selected.element?.id === id;
1137
1153
  return overlayRegistry.map(
1138
- ({ shouldRender, component: Overlay }, index) => shouldRender({ id, element: domElement, isSelected }) && /* @__PURE__ */ React7.createElement(
1154
+ ({ shouldRender, component: Overlay }, index) => shouldRender({ id, element: domElement, isSelected, widgetType }) && /* @__PURE__ */ React7.createElement(
1139
1155
  Overlay,
1140
1156
  {
1141
1157
  key: `${id}-${index}`,
1142
1158
  id,
1143
1159
  element: domElement,
1144
1160
  isSelected,
1145
- isGlobal
1161
+ isGlobal,
1162
+ widgetType
1146
1163
  }
1147
1164
  )
1148
1165
  );
@@ -1155,7 +1172,8 @@ function useElementsDom() {
1155
1172
  return getElements().filter((el) => isV4Element(el.view?.el?.dataset)).map((element) => ({
1156
1173
  id: element.id,
1157
1174
  domElement: element.view?.getDomElement?.()?.get?.(0),
1158
- isGlobal: element.model.get("isGlobal") ?? false
1175
+ isGlobal: element.model.get("isGlobal") ?? false,
1176
+ widgetType: element.model.get("widgetType")
1159
1177
  })).filter((item) => !!item.domElement);
1160
1178
  }
1161
1179
  );
@@ -1984,6 +2002,46 @@ function clipboardRootsAreAtomicForms(elements) {
1984
2002
  }
1985
2003
  return elements.every((el) => getClipboardElementType(el) === FORM_ELEMENT_TYPE);
1986
2004
  }
2005
+ function hasFormAncestor(node) {
2006
+ return node.closest(FORM_ELEMENT_TYPE) !== null;
2007
+ }
2008
+ function collectFormAncestorErrors(xml) {
2009
+ const errors = [];
2010
+ for (const node of xml.querySelectorAll("*")) {
2011
+ if (!FORM_FIELD_ELEMENT_TYPES.has(node.tagName.toLowerCase())) {
2012
+ continue;
2013
+ }
2014
+ if (hasFormAncestor(node)) {
2015
+ continue;
2016
+ }
2017
+ const id = node.getAttribute("configuration-id");
2018
+ errors.push(
2019
+ `<${node.tagName}${id ? ` configuration-id="${id}"` : ""}> must be nested inside <e-form> (any ancestor depth is allowed).`
2020
+ );
2021
+ }
2022
+ return errors;
2023
+ }
2024
+ function collectSubmitButtonErrors(xml) {
2025
+ const errors = [];
2026
+ for (const form of xml.querySelectorAll("e-form")) {
2027
+ const submitButtons = form.querySelectorAll("e-form-submit-button");
2028
+ if (submitButtons.length === 0) {
2029
+ errors.push(`<e-form> has no <e-form-submit-button>.`);
2030
+ } else if (submitButtons.length > 1) {
2031
+ errors.push(`<e-form> has ${submitButtons.length} submit buttons \u2014 only 1 is allowed.`);
2032
+ }
2033
+ }
2034
+ return errors;
2035
+ }
2036
+ function collectEmptyMessageErrors(xml) {
2037
+ const errors = [];
2038
+ for (const node of xml.querySelectorAll("e-form-success-message, e-form-error-message")) {
2039
+ if (node.children.length === 0) {
2040
+ errors.push(`<${node.tagName}> must have at least one child element (e.g. <e-atomic-paragraph>).`);
2041
+ }
2042
+ }
2043
+ return errors;
2044
+ }
1987
2045
 
1988
2046
  // src/form-structure/enforce-form-ancestor-commands.ts
1989
2047
  var FORM_FIELDS_OUTSIDE_ALERT = {
@@ -2010,7 +2068,8 @@ function blockFormFieldCreate(args) {
2010
2068
  if (!elementType || !FORM_FIELD_ELEMENT_TYPES.has(elementType)) {
2011
2069
  return false;
2012
2070
  }
2013
- if (!isWithinForm(args.container)) {
2071
+ const containers = args.containers ?? [args.container];
2072
+ if (containers.some((container) => !isWithinForm(container))) {
2014
2073
  handleBlockedFormField();
2015
2074
  return true;
2016
2075
  }
@@ -2018,10 +2077,10 @@ function blockFormFieldCreate(args) {
2018
2077
  }
2019
2078
  function blockFormFieldMove(args) {
2020
2079
  const { containers = [args.container], target } = args;
2021
- const hasFormFieldElement = containers.some(
2022
- (container) => container ? hasElementTypes(container, FORM_FIELD_ELEMENT_TYPES) : false
2080
+ const hasLooseFormFields = containers.some(
2081
+ (container) => container ? !hasElementType(container, FORM_ELEMENT_TYPE) && hasElementTypes(container, FORM_FIELD_ELEMENT_TYPES) : false
2023
2082
  );
2024
- if (hasFormFieldElement && !isWithinForm(target) && !movedContainersIncludeAtomicFormRoot(containers)) {
2083
+ if (hasLooseFormFields && !isWithinForm(target) && !movedContainersIncludeAtomicFormRoot(containers)) {
2025
2084
  handleBlockedFormField();
2026
2085
  return true;
2027
2086
  }
@@ -5158,6 +5217,11 @@ var CompositionBuilder = class _CompositionBuilder {
5158
5217
  throw new Error(`Invalid element structure:
5159
5218
  ${childTypeErrors.join("\n")}`);
5160
5219
  }
5220
+ const formErrors = [
5221
+ ...collectFormAncestorErrors(this.xml),
5222
+ ...collectSubmitButtonErrors(this.xml),
5223
+ ...collectEmptyMessageErrors(this.xml)
5224
+ ];
5161
5225
  const children = Array.from(this.xml.children);
5162
5226
  for (const childNode of children) {
5163
5227
  const modelTree = this.buildModelTree(childNode, widgetsCache);
@@ -5192,6 +5256,7 @@ ${childTypeErrors.join("\n")}`);
5192
5256
  return {
5193
5257
  configErrors,
5194
5258
  styleErrors,
5259
+ formErrors,
5195
5260
  rootContainers: [...this.rootContainers]
5196
5261
  };
5197
5262
  }
@@ -5502,7 +5567,11 @@ var initBuildCompositionsTool = (reg) => {
5502
5567
  compositionBuilder.setElementConfig(elementConfig);
5503
5568
  compositionBuilder.setStylesConfig(stylesConfig);
5504
5569
  compositionBuilder.setCustomCSS(customCSS);
5505
- const { configErrors, rootContainers: generatedRootContainers } = await compositionBuilder.build(targetContainer);
5570
+ const {
5571
+ configErrors,
5572
+ formErrors,
5573
+ rootContainers: generatedRootContainers
5574
+ } = await compositionBuilder.build(targetContainer);
5506
5575
  rootContainers.push(...generatedRootContainers);
5507
5576
  generatedXML = new XMLSerializer().serializeToString(compositionBuilder.getXML());
5508
5577
  rootContainers.forEach((container) => {
@@ -5517,6 +5586,9 @@ var initBuildCompositionsTool = (reg) => {
5517
5586
  if (configErrors.length) {
5518
5587
  errors.push(...configErrors.map((msg) => new Error(msg)));
5519
5588
  }
5589
+ if (formErrors.length) {
5590
+ errors.push(...formErrors.map((msg) => new Error(msg)));
5591
+ }
5520
5592
  } catch (error) {
5521
5593
  errors.push(error);
5522
5594
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@elementor/editor-canvas",
3
3
  "description": "Elementor Editor Canvas",
4
- "version": "4.2.0-beta1",
4
+ "version": "4.2.0-beta2",
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.0-beta1",
40
+ "@elementor/editor": "4.2.0-beta2",
41
41
  "dompurify": "^3.2.6",
42
- "@elementor/editor-controls": "4.2.0-beta1",
43
- "@elementor/editor-documents": "4.2.0-beta1",
44
- "@elementor/editor-elements": "4.2.0-beta1",
45
- "@elementor/editor-interactions": "4.2.0-beta1",
46
- "@elementor/editor-mcp": "4.2.0-beta1",
47
- "@elementor/editor-notifications": "4.2.0-beta1",
48
- "@elementor/editor-props": "4.2.0-beta1",
49
- "@elementor/editor-responsive": "4.2.0-beta1",
50
- "@elementor/editor-styles": "4.2.0-beta1",
51
- "@elementor/editor-styles-repository": "4.2.0-beta1",
52
- "@elementor/editor-ui": "4.2.0-beta1",
53
- "@elementor/editor-v1-adapters": "4.2.0-beta1",
54
- "@elementor/events": "4.2.0-beta1",
55
- "@elementor/http-client": "4.2.0-beta1",
56
- "@elementor/schema": "4.2.0-beta1",
57
- "@elementor/twing": "4.2.0-beta1",
42
+ "@elementor/editor-controls": "4.2.0-beta2",
43
+ "@elementor/editor-documents": "4.2.0-beta2",
44
+ "@elementor/editor-elements": "4.2.0-beta2",
45
+ "@elementor/editor-interactions": "4.2.0-beta2",
46
+ "@elementor/editor-mcp": "4.2.0-beta2",
47
+ "@elementor/editor-notifications": "4.2.0-beta2",
48
+ "@elementor/editor-props": "4.2.0-beta2",
49
+ "@elementor/editor-responsive": "4.2.0-beta2",
50
+ "@elementor/editor-styles": "4.2.0-beta2",
51
+ "@elementor/editor-styles-repository": "4.2.0-beta2",
52
+ "@elementor/editor-ui": "4.2.0-beta2",
53
+ "@elementor/editor-v1-adapters": "4.2.0-beta2",
54
+ "@elementor/events": "4.2.0-beta2",
55
+ "@elementor/http-client": "4.2.0-beta2",
56
+ "@elementor/schema": "4.2.0-beta2",
57
+ "@elementor/twing": "4.2.0-beta2",
58
58
  "@elementor/ui": "1.37.5",
59
- "@elementor/utils": "4.2.0-beta1",
60
- "@elementor/wp-media": "4.2.0-beta1",
59
+ "@elementor/utils": "4.2.0-beta2",
60
+ "@elementor/wp-media": "4.2.0-beta2",
61
61
  "@floating-ui/react": "^0.27.5",
62
62
  "@wordpress/i18n": "^5.13.0"
63
63
  },
@@ -0,0 +1,2 @@
1
+ export const parseXml = ( xml: string ): Document =>
2
+ new DOMParser().parseFromString( `<root>${ xml }</root>`, 'application/xml' );
@@ -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 ELEMENTS_DATA_ATTR = 'atomic';
15
- const E_GRID_TYPE = 'e-grid';
14
+ const hasGridStyleDisplay = ( element: HTMLElement ): boolean => {
15
+ return element.computedStyleMap().get( 'display' )?.toString() === 'grid';
16
+ };
16
17
 
17
- const isGridElement = ( element: HTMLElement ): boolean =>
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 } ) => isGridElement( element ),
27
+ shouldRender: ( { element } ) => hasGridStyleDisplay( element ),
28
28
  },
29
29
  {
30
30
  component: GridOutlineOverlay,
31
- shouldRender: ( { element, isSelected } ) => isSelected && isGridElement( element ),
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 { useSelectedElementSettings } from '@elementor/editor-elements';
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( value: { $$type: 'boolean'; value: boolean } | null ) {
38
- jest.mocked( useSelectedElementSettings ).mockReturnValue( {
39
- element: { id: ID, type: 'e-grid' },
40
- elementType: {} as never,
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( { $$type: 'boolean', value: true } );
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 a null setting as default-on and renders the outline', () => {
101
- mockGridOutlineSetting( null );
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( { $$type: 'boolean', value: false } );
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( { $$type: 'boolean', value: true } );
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( null );
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( null );
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( null );
158
+ mockGridOutlineSetting( undefined );
161
159
 
162
160
  renderOverlay();
163
161
 
@@ -1,6 +1,5 @@
1
1
  import * as React from 'react';
2
- import { useSelectedElementSettings } from '@elementor/editor-elements';
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 { settings } = useSelectedElementSettings();
16
- const enabled = booleanPropTypeUtil.extract( settings?.grid_outline );
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 = ( { element, isSelected, id, isGlobal = false }: Props ): React.ReactElement | false => {
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.offsetHeight <= 1;
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
  }