@elementor/editor-canvas 4.2.0-946 → 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
@@ -384,12 +384,8 @@ import {
384
384
  windowEvent as windowEvent2
385
385
  } from "@elementor/editor-v1-adapters";
386
386
 
387
- // src/components/grid-outline/grid-outline-overlay.tsx
388
- import * as React6 from "react";
389
- import { useSelectedElementSettings } from "@elementor/editor-elements";
390
- import { booleanPropTypeUtil } from "@elementor/editor-props";
391
- import { Box as Box2 } from "@elementor/ui";
392
- import { FloatingPortal as FloatingPortal2 } from "@floating-ui/react";
387
+ // src/components/grid-outline/grid-empty-cell-positioner.tsx
388
+ import { useEffect as useEffect5 } from "react";
393
389
 
394
390
  // src/hooks/use-element-rect.ts
395
391
  import { useEffect as useEffect2, useState } from "react";
@@ -454,49 +450,8 @@ function useMutationsListener({ element, onChange }) {
454
450
  }, [element, onChange]);
455
451
  }
456
452
 
457
- // src/hooks/use-floating-on-element.ts
458
- import { useEffect as useEffect3, useState as useState2 } from "react";
459
- import { autoUpdate, offset, size, useFloating } from "@floating-ui/react";
460
- function useFloatingOnElement({ element, isSelected }) {
461
- const [isOpen, setIsOpen] = useState2(false);
462
- const sizeModifier = 2;
463
- const { refs, floatingStyles, context } = useFloating({
464
- // Must be controlled for interactions (like hover) to work.
465
- open: isOpen || isSelected,
466
- onOpenChange: setIsOpen,
467
- whileElementsMounted: autoUpdate,
468
- middleware: [
469
- // Match the floating element's size to the reference element.
470
- size(() => {
471
- return {
472
- apply({ elements, rects }) {
473
- Object.assign(elements.floating.style, {
474
- width: `${rects.reference.width + sizeModifier}px`,
475
- height: `${rects.reference.height + sizeModifier}px`
476
- });
477
- }
478
- };
479
- }),
480
- // Center the floating element on the reference element.
481
- offset(({ rects }) => -rects.reference.height / 2 - rects.floating.height / 2)
482
- ]
483
- });
484
- useEffect3(() => {
485
- refs.setReference(element);
486
- }, [element, refs]);
487
- return {
488
- isVisible: isOpen || isSelected,
489
- context,
490
- floating: {
491
- setRef: refs.setFloating,
492
- ref: refs.floating,
493
- styles: floatingStyles
494
- }
495
- };
496
- }
497
-
498
453
  // src/hooks/use-grid-tracks.ts
499
- import { useEffect as useEffect5, useState as useState4 } from "react";
454
+ import { useEffect as useEffect4, useState as useState3 } from "react";
500
455
  import { ELEMENT_STYLE_CHANGE_EVENT } from "@elementor/editor-elements";
501
456
  import { __privateUseListenTo as useListenTo, windowEvent } from "@elementor/editor-v1-adapters";
502
457
 
@@ -583,10 +538,10 @@ function toPx(value) {
583
538
  }
584
539
 
585
540
  // src/hooks/use-grid-children.ts
586
- import { useEffect as useEffect4, useState as useState3 } from "react";
541
+ import { useEffect as useEffect3, useState as useState2 } from "react";
587
542
  function useGridChildren(element) {
588
- const [signal, setSignal] = useState3(0);
589
- useEffect4(() => {
543
+ const [signal, setSignal] = useState2(0);
544
+ useEffect3(() => {
590
545
  if (!element) {
591
546
  return;
592
547
  }
@@ -633,13 +588,13 @@ var EMPTY = {
633
588
  };
634
589
  var DEVICE_MODE_CHANGE_EVENT = "elementor/device-mode/change";
635
590
  function useGridTracks(element, rect) {
636
- const [tracks, setTracks] = useState4(EMPTY);
591
+ const [tracks, setTracks] = useState3(EMPTY);
637
592
  const trigger = useListenTo(
638
593
  [windowEvent(ELEMENT_STYLE_CHANGE_EVENT), windowEvent(DEVICE_MODE_CHANGE_EVENT)],
639
594
  () => ({})
640
595
  );
641
596
  const childrenTrigger = useGridChildren(element);
642
- useEffect5(() => {
597
+ useEffect4(() => {
643
598
  const previewWindow = element?.ownerDocument?.defaultView;
644
599
  if (!element || !previewWindow) {
645
600
  setTracks(EMPTY);
@@ -655,98 +610,6 @@ function useGridTracks(element, rect) {
655
610
  return tracks;
656
611
  }
657
612
 
658
- // src/components/outline-overlay.tsx
659
- import * as React from "react";
660
- import { Box, styled } from "@elementor/ui";
661
- import { FloatingPortal, useHover, useInteractions } from "@floating-ui/react";
662
-
663
- // src/hooks/use-bind-react-props-to-element.ts
664
- import { useEffect as useEffect6 } from "react";
665
- function useBindReactPropsToElement(element, getProps) {
666
- useEffect6(() => {
667
- const el = element;
668
- const { events, attrs } = groupProps(getProps());
669
- events.forEach(([eventName, listener]) => el.addEventListener(eventName, listener));
670
- attrs.forEach(([attrName, attrValue]) => el.setAttribute(attrName, attrValue));
671
- return () => {
672
- events.forEach(([eventName, listener]) => el.removeEventListener(eventName, listener));
673
- attrs.forEach(([attrName]) => el.removeAttribute(attrName));
674
- };
675
- }, [getProps, element]);
676
- }
677
- function groupProps(props) {
678
- const eventRegex = /^on(?=[A-Z])/;
679
- return Object.entries(props).reduce(
680
- (acc, [propName, propValue]) => {
681
- if (!eventRegex.test(propName)) {
682
- acc.attrs.push([propName, propValue]);
683
- return acc;
684
- }
685
- const eventName = propName.replace(eventRegex, "").toLowerCase();
686
- const listener = propValue;
687
- acc.events.push([eventName, listener]);
688
- return acc;
689
- },
690
- {
691
- events: [],
692
- attrs: []
693
- }
694
- );
695
- }
696
-
697
- // src/hooks/use-has-overlapping.ts
698
- var possibleOverlappingSelectors = [".e-off-canvas"];
699
- var useHasOverlapping = () => {
700
- const preview = window.elementor?.$preview?.[0];
701
- if (!preview) {
702
- return false;
703
- }
704
- const hasOverlapping = possibleOverlappingSelectors.map((selector) => Array.from(preview?.contentWindow?.document.body.querySelectorAll(selector) ?? [])).flat().some(
705
- (elem) => elem.checkVisibility({
706
- opacityProperty: true,
707
- visibilityProperty: true,
708
- contentVisibilityAuto: true
709
- })
710
- );
711
- return hasOverlapping;
712
- };
713
-
714
- // src/components/outline-overlay.tsx
715
- var CANVAS_WRAPPER_ID = "elementor-preview-responsive-wrapper";
716
- var OverlayBox = styled(Box, {
717
- shouldForwardProp: (prop) => prop !== "isSelected" && prop !== "isSmallerOffset" && prop !== "isGlobal"
718
- })(
719
- ({ theme, isSelected, isSmallerOffset, isGlobal }) => ({
720
- outline: `${isSelected ? "2px" : "1px"} solid ${isGlobal ? theme.palette.global.main : theme.palette.primary.light}`,
721
- outlineOffset: isSelected && !isSmallerOffset ? "-2px" : "-1px",
722
- pointerEvents: "none"
723
- })
724
- );
725
- var OutlineOverlay = ({ element, isSelected, id, isGlobal = false }) => {
726
- const { context, floating, isVisible } = useFloatingOnElement({ element, isSelected });
727
- const { getFloatingProps, getReferenceProps } = useInteractions([useHover(context)]);
728
- const hasOverlapping = useHasOverlapping();
729
- useBindReactPropsToElement(element, getReferenceProps);
730
- const isSmallerOffset = element.offsetHeight <= 1;
731
- return isVisible && !hasOverlapping && /* @__PURE__ */ React.createElement(FloatingPortal, { id: CANVAS_WRAPPER_ID }, /* @__PURE__ */ React.createElement(
732
- OverlayBox,
733
- {
734
- ref: floating.setRef,
735
- isSelected,
736
- isGlobal,
737
- style: floating.styles,
738
- "data-element-overlay": id,
739
- role: "presentation",
740
- isSmallerOffset,
741
- ...getFloatingProps()
742
- }
743
- ));
744
- };
745
-
746
- // src/components/grid-outline/grid-outline.tsx
747
- import * as React5 from "react";
748
- import { useMemo } from "react";
749
-
750
613
  // src/utils/find-first-empty-cell.ts
751
614
  function findFirstEmptyCell(element, columnCount, rowCount) {
752
615
  if (!element || columnCount === 0 || rowCount === 0) {
@@ -909,6 +772,194 @@ function scanFirstEmpty(matrix, flowsByColumn) {
909
772
  return null;
910
773
  }
911
774
 
775
+ // src/components/grid-outline/grid-empty-cell-positioner.tsx
776
+ var CSS_VAR_ROW = "--e-grid-empty-cell-row";
777
+ var CSS_VAR_COL = "--e-grid-empty-cell-col";
778
+ var CSS_VAR_VISIBILITY = "--e-grid-empty-cell-visibility";
779
+ var clearGridEmptyCellStyles = (target) => {
780
+ target.style.removeProperty(CSS_VAR_ROW);
781
+ target.style.removeProperty(CSS_VAR_COL);
782
+ target.style.removeProperty(CSS_VAR_VISIBILITY);
783
+ };
784
+ var GridEmptyCellPositioner = ({ element }) => {
785
+ const rect = useElementRect(element);
786
+ const tracks = useGridTracks(element, rect);
787
+ useEffect5(() => {
788
+ if (!element) {
789
+ return;
790
+ }
791
+ const firstEmpty = findFirstEmptyCell(element, tracks.columns.length, tracks.rows.length);
792
+ if (!firstEmpty) {
793
+ element.style.removeProperty(CSS_VAR_ROW);
794
+ element.style.removeProperty(CSS_VAR_COL);
795
+ element.style.setProperty(CSS_VAR_VISIBILITY, "hidden");
796
+ return () => clearGridEmptyCellStyles(element);
797
+ }
798
+ element.style.setProperty(CSS_VAR_ROW, String(firstEmpty.row + 1));
799
+ element.style.setProperty(CSS_VAR_COL, String(firstEmpty.col + 1));
800
+ element.style.setProperty(CSS_VAR_VISIBILITY, "visible");
801
+ return () => {
802
+ clearGridEmptyCellStyles(element);
803
+ };
804
+ }, [element, tracks]);
805
+ return null;
806
+ };
807
+
808
+ // src/components/grid-outline/grid-outline-overlay.tsx
809
+ import * as React6 from "react";
810
+ import { useElementEditorSettings } from "@elementor/editor-elements";
811
+ import { Box as Box2 } from "@elementor/ui";
812
+ import { FloatingPortal as FloatingPortal2 } from "@floating-ui/react";
813
+
814
+ // src/hooks/use-floating-on-element.ts
815
+ import { useEffect as useEffect6, useState as useState4 } from "react";
816
+ import { autoUpdate, offset, size, useFloating } from "@floating-ui/react";
817
+ function useFloatingOnElement({ element, isSelected }) {
818
+ const [isOpen, setIsOpen] = useState4(false);
819
+ const sizeModifier = 2;
820
+ const { refs, floatingStyles, context } = useFloating({
821
+ // Must be controlled for interactions (like hover) to work.
822
+ open: isOpen || isSelected,
823
+ onOpenChange: setIsOpen,
824
+ whileElementsMounted: autoUpdate,
825
+ middleware: [
826
+ // Match the floating element's size to the reference element.
827
+ size(() => {
828
+ return {
829
+ apply({ elements, rects }) {
830
+ Object.assign(elements.floating.style, {
831
+ width: `${rects.reference.width + sizeModifier}px`,
832
+ height: `${rects.reference.height + sizeModifier}px`
833
+ });
834
+ }
835
+ };
836
+ }),
837
+ // Center the floating element on the reference element.
838
+ offset(({ rects }) => -rects.reference.height / 2 - rects.floating.height / 2)
839
+ ]
840
+ });
841
+ useEffect6(() => {
842
+ refs.setReference(element);
843
+ }, [element, refs]);
844
+ return {
845
+ isVisible: isOpen || isSelected,
846
+ context,
847
+ floating: {
848
+ setRef: refs.setFloating,
849
+ ref: refs.floating,
850
+ styles: floatingStyles
851
+ }
852
+ };
853
+ }
854
+
855
+ // src/components/outline-overlay.tsx
856
+ import * as React from "react";
857
+ import { Box, styled } from "@elementor/ui";
858
+ import { FloatingPortal, useHover, useInteractions } from "@floating-ui/react";
859
+
860
+ // src/hooks/use-bind-react-props-to-element.ts
861
+ import { useEffect as useEffect7 } from "react";
862
+ function useBindReactPropsToElement(element, getProps) {
863
+ useEffect7(() => {
864
+ const el = element;
865
+ const { events, attrs } = groupProps(getProps());
866
+ events.forEach(([eventName, listener]) => el.addEventListener(eventName, listener));
867
+ attrs.forEach(([attrName, attrValue]) => el.setAttribute(attrName, attrValue));
868
+ return () => {
869
+ events.forEach(([eventName, listener]) => el.removeEventListener(eventName, listener));
870
+ attrs.forEach(([attrName]) => el.removeAttribute(attrName));
871
+ };
872
+ }, [getProps, element]);
873
+ }
874
+ function groupProps(props) {
875
+ const eventRegex = /^on(?=[A-Z])/;
876
+ return Object.entries(props).reduce(
877
+ (acc, [propName, propValue]) => {
878
+ if (!eventRegex.test(propName)) {
879
+ acc.attrs.push([propName, propValue]);
880
+ return acc;
881
+ }
882
+ const eventName = propName.replace(eventRegex, "").toLowerCase();
883
+ const listener = propValue;
884
+ acc.events.push([eventName, listener]);
885
+ return acc;
886
+ },
887
+ {
888
+ events: [],
889
+ attrs: []
890
+ }
891
+ );
892
+ }
893
+
894
+ // src/hooks/use-has-overlapping.ts
895
+ var possibleOverlappingSelectors = [".e-off-canvas"];
896
+ var useHasOverlapping = () => {
897
+ const preview = window.elementor?.$preview?.[0];
898
+ if (!preview) {
899
+ return false;
900
+ }
901
+ const hasOverlapping = possibleOverlappingSelectors.map((selector) => Array.from(preview?.contentWindow?.document.body.querySelectorAll(selector) ?? [])).flat().some(
902
+ (elem) => elem.checkVisibility({
903
+ opacityProperty: true,
904
+ visibilityProperty: true,
905
+ contentVisibilityAuto: true
906
+ })
907
+ );
908
+ return hasOverlapping;
909
+ };
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
+
921
+ // src/components/outline-overlay.tsx
922
+ var CANVAS_WRAPPER_ID = "elementor-preview-responsive-wrapper";
923
+ var OverlayBox = styled(Box, {
924
+ shouldForwardProp: (prop) => prop !== "isSelected" && prop !== "isSmallerOffset" && prop !== "isGlobal"
925
+ })(
926
+ ({ theme, isSelected, isSmallerOffset, isGlobal }) => ({
927
+ outline: `${isSelected ? "2px" : "1px"} solid ${isGlobal ? theme.palette.global.main : theme.palette.primary.light}`,
928
+ outlineOffset: isSelected && !isSmallerOffset ? "-2px" : "-1px",
929
+ pointerEvents: "none"
930
+ })
931
+ );
932
+ var OutlineOverlay = ({
933
+ element,
934
+ isSelected,
935
+ id,
936
+ isGlobal = false,
937
+ widgetType
938
+ }) => {
939
+ const { context, floating, isVisible } = useFloatingOnElement({ element, isSelected });
940
+ const { getFloatingProps, getReferenceProps } = useInteractions([useHover(context)]);
941
+ const hasOverlapping = useHasOverlapping();
942
+ useBindReactPropsToElement(element, getReferenceProps);
943
+ const isSmallerOffset = shouldUseSmallerOutlineOffset(element, widgetType);
944
+ return isVisible && !hasOverlapping && /* @__PURE__ */ React.createElement(FloatingPortal, { id: CANVAS_WRAPPER_ID }, /* @__PURE__ */ React.createElement(
945
+ OverlayBox,
946
+ {
947
+ ref: floating.setRef,
948
+ isSelected,
949
+ isGlobal,
950
+ style: floating.styles,
951
+ "data-element-overlay": id,
952
+ role: "presentation",
953
+ isSmallerOffset,
954
+ ...getFloatingProps()
955
+ }
956
+ ));
957
+ };
958
+
959
+ // src/components/grid-outline/grid-outline.tsx
960
+ import * as React5 from "react";
961
+ import { useMemo } from "react";
962
+
912
963
  // src/components/grid-outline/cell.tsx
913
964
  import * as React2 from "react";
914
965
  var FALLBACK_COLOR = "rgba(0, 0, 0, 0.12)";
@@ -1016,6 +1067,13 @@ var renderLines = (tracks, width, height) => {
1016
1067
  ))
1017
1068
  ];
1018
1069
  };
1070
+ var isDragActiveFromDom = (element) => {
1071
+ return Boolean(
1072
+ element?.querySelector(
1073
+ ".e-dragging-over, .elementor-dragging-on-child, .elementor-draggable-over, .elementor-widget-placeholder, .elementor-sortable-placeholder"
1074
+ )
1075
+ );
1076
+ };
1019
1077
  function GridOutline({ element, tracks, width, height }) {
1020
1078
  const cells = useMemo(() => computeCellRects(tracks, width, height), [tracks, width, height]);
1021
1079
  const hasGap = tracks.columnGap > 0 || tracks.rowGap > 0;
@@ -1033,13 +1091,13 @@ function GridOutline({ element, tracks, width, height }) {
1033
1091
  xmlns: "http://www.w3.org/2000/svg"
1034
1092
  },
1035
1093
  hasGap ? renderCells(cells, tracks.borderColor) : renderLines(tracks, width, height)
1036
- ), emptyCellRect && /* @__PURE__ */ React5.createElement(FirstEmptyCell, { rect: emptyCellRect, color: tracks.borderColor }));
1094
+ ), emptyCellRect && !isDragActiveFromDom(element) && /* @__PURE__ */ React5.createElement(FirstEmptyCell, { rect: emptyCellRect, color: tracks.borderColor }));
1037
1095
  }
1038
1096
 
1039
1097
  // src/components/grid-outline/grid-outline-overlay.tsx
1040
1098
  var GridOutlineOverlay = ({ element, id, isSelected }) => {
1041
- const { settings } = useSelectedElementSettings();
1042
- const enabled = booleanPropTypeUtil.extract(settings?.grid_outline);
1099
+ const settings = useElementEditorSettings(id);
1100
+ const enabled = settings?.grid_outline;
1043
1101
  const rect = useElementRect(element);
1044
1102
  const tracks = useGridTracks(element, rect);
1045
1103
  const { floating } = useFloatingOnElement({ element, isSelected });
@@ -1062,15 +1120,22 @@ var GridOutlineOverlay = ({ element, id, isSelected }) => {
1062
1120
  };
1063
1121
 
1064
1122
  // src/components/elements-overlays.tsx
1123
+ var hasGridStyleDisplay = (element) => {
1124
+ return element.computedStyleMap().get("display")?.toString() === "grid";
1125
+ };
1065
1126
  var ELEMENTS_DATA_ATTR = "atomic";
1066
1127
  var overlayRegistry = [
1067
1128
  {
1068
1129
  component: OutlineOverlay,
1069
1130
  shouldRender: () => true
1070
1131
  },
1132
+ {
1133
+ component: GridEmptyCellPositioner,
1134
+ shouldRender: ({ element }) => hasGridStyleDisplay(element)
1135
+ },
1071
1136
  {
1072
1137
  component: GridOutlineOverlay,
1073
- shouldRender: ({ element, isSelected }) => isSelected && element.dataset.eType === "e-grid"
1138
+ shouldRender: ({ isSelected, element }) => isSelected && hasGridStyleDisplay(element)
1074
1139
  }
1075
1140
  ];
1076
1141
  function ElementsOverlays() {
@@ -1083,17 +1148,18 @@ function ElementsOverlays() {
1083
1148
  if (!isActive) {
1084
1149
  return null;
1085
1150
  }
1086
- return elements.map(({ id, domElement, isGlobal }) => {
1151
+ return elements.map(({ id, domElement, isGlobal, widgetType }) => {
1087
1152
  const isSelected = selected.element?.id === id;
1088
1153
  return overlayRegistry.map(
1089
- ({ 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(
1090
1155
  Overlay,
1091
1156
  {
1092
1157
  key: `${id}-${index}`,
1093
1158
  id,
1094
1159
  element: domElement,
1095
1160
  isSelected,
1096
- isGlobal
1161
+ isGlobal,
1162
+ widgetType
1097
1163
  }
1098
1164
  )
1099
1165
  );
@@ -1106,7 +1172,8 @@ function useElementsDom() {
1106
1172
  return getElements().filter((el) => isV4Element(el.view?.el?.dataset)).map((element) => ({
1107
1173
  id: element.id,
1108
1174
  domElement: element.view?.getDomElement?.()?.get?.(0),
1109
- isGlobal: element.model.get("isGlobal") ?? false
1175
+ isGlobal: element.model.get("isGlobal") ?? false,
1176
+ widgetType: element.model.get("widgetType")
1110
1177
  })).filter((item) => !!item.domElement);
1111
1178
  }
1112
1179
  );
@@ -1128,15 +1195,15 @@ import {
1128
1195
  import { Portal } from "@elementor/ui";
1129
1196
 
1130
1197
  // src/hooks/use-interactions-items.ts
1131
- import { useEffect as useEffect8, useMemo as useMemo2, useState as useState5 } from "react";
1198
+ import { useEffect as useEffect9, useMemo as useMemo2, useState as useState5 } from "react";
1132
1199
  import { interactionsRepository } from "@elementor/editor-interactions";
1133
1200
  import { registerDataHook } from "@elementor/editor-v1-adapters";
1134
1201
 
1135
1202
  // src/hooks/use-on-mount.ts
1136
- import { useEffect as useEffect7, useRef } from "react";
1203
+ import { useEffect as useEffect8, useRef } from "react";
1137
1204
  function useOnMount(cb) {
1138
1205
  const mounted = useRef(false);
1139
- useEffect7(() => {
1206
+ useEffect8(() => {
1140
1207
  if (!mounted.current) {
1141
1208
  mounted.current = true;
1142
1209
  cb();
@@ -1164,7 +1231,7 @@ function useInteractionsItems() {
1164
1231
  return [];
1165
1232
  }
1166
1233
  }, []);
1167
- useEffect8(() => {
1234
+ useEffect9(() => {
1168
1235
  if (providerAndSubscribers.length === 0) {
1169
1236
  return;
1170
1237
  }
@@ -1301,7 +1368,7 @@ function getLinkAttrs(el) {
1301
1368
  }
1302
1369
 
1303
1370
  // src/hooks/use-style-items.ts
1304
- import { useEffect as useEffect9, useMemo as useMemo5, useRef as useRef2, useState as useState6 } from "react";
1371
+ import { useEffect as useEffect10, useMemo as useMemo5, useRef as useRef2, useState as useState6 } from "react";
1305
1372
  import { useBreakpoints } from "@elementor/editor-responsive";
1306
1373
  import { isClassState } from "@elementor/editor-styles";
1307
1374
  import { stylesRepository as stylesRepository2 } from "@elementor/editor-styles-repository";
@@ -1679,7 +1746,7 @@ function useStyleItems() {
1679
1746
  })
1680
1747
  );
1681
1748
  }, [renderStyles]);
1682
- useEffect9(() => {
1749
+ useEffect10(() => {
1683
1750
  const unsubscribes = providerAndSubscribers.map(
1684
1751
  ({ provider, subscriber }) => provider.subscribe(subscriber)
1685
1752
  );
@@ -1935,6 +2002,46 @@ function clipboardRootsAreAtomicForms(elements) {
1935
2002
  }
1936
2003
  return elements.every((el) => getClipboardElementType(el) === FORM_ELEMENT_TYPE);
1937
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
+ }
1938
2045
 
1939
2046
  // src/form-structure/enforce-form-ancestor-commands.ts
1940
2047
  var FORM_FIELDS_OUTSIDE_ALERT = {
@@ -1961,7 +2068,8 @@ function blockFormFieldCreate(args) {
1961
2068
  if (!elementType || !FORM_FIELD_ELEMENT_TYPES.has(elementType)) {
1962
2069
  return false;
1963
2070
  }
1964
- if (!isWithinForm(args.container)) {
2071
+ const containers = args.containers ?? [args.container];
2072
+ if (containers.some((container) => !isWithinForm(container))) {
1965
2073
  handleBlockedFormField();
1966
2074
  return true;
1967
2075
  }
@@ -1969,10 +2077,10 @@ function blockFormFieldCreate(args) {
1969
2077
  }
1970
2078
  function blockFormFieldMove(args) {
1971
2079
  const { containers = [args.container], target } = args;
1972
- const hasFormFieldElement = containers.some(
1973
- (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
1974
2082
  );
1975
- if (hasFormFieldElement && !isWithinForm(target) && !movedContainersIncludeAtomicFormRoot(containers)) {
2083
+ if (hasLooseFormFields && !isWithinForm(target) && !movedContainersIncludeAtomicFormRoot(containers)) {
1976
2084
  handleBlockedFormField();
1977
2085
  return true;
1978
2086
  }
@@ -3387,13 +3495,13 @@ var ReplacementBase = class {
3387
3495
 
3388
3496
  // src/legacy/replacements/inline-editing/canvas-inline-editor.tsx
3389
3497
  import * as React10 from "react";
3390
- import { useCallback as useCallback2, useEffect as useEffect11, useLayoutEffect, useState as useState8 } from "react";
3498
+ import { useCallback as useCallback2, useEffect as useEffect12, useLayoutEffect, useState as useState8 } from "react";
3391
3499
  import { InlineEditor, InlineEditorToolbar } from "@elementor/editor-controls";
3392
3500
  import { Box as Box3, ThemeProvider } from "@elementor/ui";
3393
3501
  import { autoUpdate as autoUpdate2, flip, FloatingPortal as FloatingPortal3, useFloating as useFloating2 } from "@floating-ui/react";
3394
3502
 
3395
3503
  // src/legacy/replacements/inline-editing/inline-editing-utils.ts
3396
- import { useCallback, useEffect as useEffect10, useState as useState7 } from "react";
3504
+ import { useCallback, useEffect as useEffect11, useState as useState7 } from "react";
3397
3505
  var TOP_BAR_SELECTOR = "#elementor-editor-wrapper-v2";
3398
3506
  var NAVIGATOR_SELECTOR = "#elementor-navigator";
3399
3507
  var EDITING_PANEL = "#elementor-panel";
@@ -3425,7 +3533,7 @@ var getInlineEditorElement = (elementWrapper, expectedTag) => {
3425
3533
  };
3426
3534
  var useOnClickOutsideIframe = (handleUnmount) => {
3427
3535
  const asyncUnmountInlineEditor = useCallback(() => queueMicrotask(handleUnmount), [handleUnmount]);
3428
- useEffect10(() => {
3536
+ useEffect11(() => {
3429
3537
  EDITOR_ELEMENTS_OUT_OF_IFRAME.forEach(
3430
3538
  (selector) => document?.querySelector(selector)?.addEventListener("mousedown", asyncUnmountInlineEditor)
3431
3539
  );
@@ -3436,7 +3544,7 @@ var useOnClickOutsideIframe = (handleUnmount) => {
3436
3544
  };
3437
3545
  var useRenderToolbar = (ownerDocument, id) => {
3438
3546
  const [anchor, setAnchor] = useState7(null);
3439
- useEffect10(() => {
3547
+ useEffect11(() => {
3440
3548
  if (!anchor) {
3441
3549
  removeToolbarAnchor(ownerDocument, id);
3442
3550
  }
@@ -3533,7 +3641,7 @@ var CanvasInlineEditor = ({
3533
3641
  const [active, setActive] = useState8(true);
3534
3642
  const [editor, setEditor] = useState8(null);
3535
3643
  const { onSelectionEnd, anchor: toolbarAnchor, clearAnchor } = useRenderToolbar(rootElement.ownerDocument, id);
3536
- useEffect11(() => {
3644
+ useEffect12(() => {
3537
3645
  if (!active) {
3538
3646
  clearAnchor();
3539
3647
  requestDestroy();
@@ -3544,7 +3652,7 @@ var CanvasInlineEditor = ({
3544
3652
  setActive(false);
3545
3653
  }, []);
3546
3654
  useOnClickOutsideIframe(dismiss);
3547
- useEffect11(() => {
3655
+ useEffect12(() => {
3548
3656
  const ownerDocument = contentElement.ownerDocument;
3549
3657
  const handleClickAway = (event) => {
3550
3658
  if (contentElement.contains(event.target)) {
@@ -3584,7 +3692,7 @@ var InlineEditingOverlay = ({
3584
3692
  }) => {
3585
3693
  const inlineEditedElement = getInlineEditorElement(rootElement, expectedTag);
3586
3694
  const [overlayRefElement, setOverlayElement] = useState8(inlineEditedElement);
3587
- useEffect11(() => {
3695
+ useEffect12(() => {
3588
3696
  setOverlayElement(getInlineEditorElement(rootElement, expectedTag));
3589
3697
  }, [expectedTag, rootElement]);
3590
3698
  return overlayRefElement ? /* @__PURE__ */ React10.createElement(OutlineOverlay, { element: overlayRefElement, id, isSelected: true }) : null;
@@ -4630,6 +4738,7 @@ import {
4630
4738
  getContainer as getContainer5,
4631
4739
  getWidgetsCache as getWidgetsCache9
4632
4740
  } from "@elementor/editor-elements";
4741
+ import { dispatchMcpStylesAppliedEvent } from "@elementor/editor-mcp";
4633
4742
 
4634
4743
  // src/composition-builder/composition-builder.ts
4635
4744
  import {
@@ -5108,6 +5217,11 @@ var CompositionBuilder = class _CompositionBuilder {
5108
5217
  throw new Error(`Invalid element structure:
5109
5218
  ${childTypeErrors.join("\n")}`);
5110
5219
  }
5220
+ const formErrors = [
5221
+ ...collectFormAncestorErrors(this.xml),
5222
+ ...collectSubmitButtonErrors(this.xml),
5223
+ ...collectEmptyMessageErrors(this.xml)
5224
+ ];
5111
5225
  const children = Array.from(this.xml.children);
5112
5226
  for (const childNode of children) {
5113
5227
  const modelTree = this.buildModelTree(childNode, widgetsCache);
@@ -5142,11 +5256,18 @@ ${childTypeErrors.join("\n")}`);
5142
5256
  return {
5143
5257
  configErrors,
5144
5258
  styleErrors,
5259
+ formErrors,
5145
5260
  rootContainers: [...this.rootContainers]
5146
5261
  };
5147
5262
  }
5148
5263
  };
5149
5264
 
5265
+ // src/utils/tracking.ts
5266
+ import { trackEvent } from "@elementor/events";
5267
+ var trackCanvasEvent = (data) => {
5268
+ trackEvent(data);
5269
+ };
5270
+
5150
5271
  // src/mcp/utils/get-composition-target-container.ts
5151
5272
  import { COMPONENT_DOCUMENT_TYPE } from "@elementor/editor-documents";
5152
5273
  function getCompositionTargetContainer(documentContainer, documentType) {
@@ -5394,6 +5515,7 @@ function serializeWrapped(doc, rootElement, wrapperConfigId) {
5394
5515
  }
5395
5516
 
5396
5517
  // src/mcp/tools/build-composition/tool.ts
5518
+ var ELEMENT_ADDED_EVENT = "elementor/canvas/element-added";
5397
5519
  var initBuildCompositionsTool = (reg) => {
5398
5520
  const { addTool, resource } = reg;
5399
5521
  resource(
@@ -5445,12 +5567,28 @@ var initBuildCompositionsTool = (reg) => {
5445
5567
  compositionBuilder.setElementConfig(elementConfig);
5446
5568
  compositionBuilder.setStylesConfig(stylesConfig);
5447
5569
  compositionBuilder.setCustomCSS(customCSS);
5448
- const { configErrors, rootContainers: generatedRootContainers } = await compositionBuilder.build(targetContainer);
5570
+ const {
5571
+ configErrors,
5572
+ formErrors,
5573
+ rootContainers: generatedRootContainers
5574
+ } = await compositionBuilder.build(targetContainer);
5449
5575
  rootContainers.push(...generatedRootContainers);
5450
5576
  generatedXML = new XMLSerializer().serializeToString(compositionBuilder.getXML());
5577
+ rootContainers.forEach((container) => {
5578
+ const elementData = container.model?.toJSON();
5579
+ if (elementData) {
5580
+ onElementAdded(elementData);
5581
+ }
5582
+ });
5583
+ Object.values(stylesConfig).forEach((styleValue) => {
5584
+ dispatchMcpStylesAppliedEvent({ styleValue });
5585
+ });
5451
5586
  if (configErrors.length) {
5452
5587
  errors.push(...configErrors.map((msg) => new Error(msg)));
5453
5588
  }
5589
+ if (formErrors.length) {
5590
+ errors.push(...formErrors.map((msg) => new Error(msg)));
5591
+ }
5454
5592
  } catch (error) {
5455
5593
  errors.push(error);
5456
5594
  }
@@ -5540,9 +5678,32 @@ function assertCompositionXmlUsesV4WidgetsOnly(xmlStructure) {
5540
5678
  }
5541
5679
  }
5542
5680
  }
5681
+ function onElementAdded(element) {
5682
+ const elType = element.elType ?? "";
5683
+ const widgetType = element.widgetType ?? "";
5684
+ const elementName = elType === "widget" ? widgetType : elType;
5685
+ trackCanvasEvent({
5686
+ eventName: "add_element",
5687
+ executed_by: "mcp_tool",
5688
+ element_name: elementName,
5689
+ element_type: elType,
5690
+ widget_type: widgetType
5691
+ });
5692
+ const event = {
5693
+ element,
5694
+ executedBy: "mcp_tool"
5695
+ };
5696
+ window.dispatchEvent(new CustomEvent(ELEMENT_ADDED_EVENT, { detail: event }));
5697
+ if (element.elements?.length) {
5698
+ element.elements?.forEach((childElement) => {
5699
+ onElementAdded(childElement);
5700
+ });
5701
+ }
5702
+ }
5543
5703
 
5544
5704
  // src/mcp/tools/configure-element/tool.ts
5545
5705
  import { getContainer as getContainer6, getWidgetsCache as getWidgetsCache10 } from "@elementor/editor-elements";
5706
+ import { dispatchMcpStylesAppliedEvent as dispatchMcpStylesAppliedEvent2 } from "@elementor/editor-mcp";
5546
5707
  import { Schema as Schema4 } from "@elementor/editor-props";
5547
5708
 
5548
5709
  // src/mcp/tools/configure-element/prompt.ts
@@ -5781,6 +5942,7 @@ async function applyStyleFromCss(opts) {
5781
5942
  propertyValue: styleValue,
5782
5943
  customCssWriteMode: "merge-with-stored"
5783
5944
  });
5945
+ dispatchMcpStylesAppliedEvent2({ styleValue });
5784
5946
  } catch (error) {
5785
5947
  throw new Error(
5786
5948
  createUpdateErrorMessage({
@@ -6522,9 +6684,9 @@ function useCanvasDocument() {
6522
6684
  }
6523
6685
 
6524
6686
  // src/hooks/use-escape-on-canvas.ts
6525
- import { useEffect as useEffect12 } from "react";
6687
+ import { useEffect as useEffect13 } from "react";
6526
6688
  function useEscapeOnCanvas(canvasDocument, onEscape) {
6527
- useEffect12(() => {
6689
+ useEffect13(() => {
6528
6690
  if (!canvasDocument) {
6529
6691
  return;
6530
6692
  }
@@ -6563,6 +6725,7 @@ export {
6563
6725
  BREAKPOINTS_SCHEMA_FULL_URI,
6564
6726
  BREAKPOINTS_SCHEMA_URI,
6565
6727
  DOCUMENT_STRUCTURE_URI,
6728
+ ELEMENT_ADDED_EVENT,
6566
6729
  GLOBAL_STYLES_IMPORTED_EVENT,
6567
6730
  SpotlightBackdrop,
6568
6731
  UnknownStyleStateError,