@elementor/editor-canvas 4.0.0-512 → 4.0.0-513

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 CHANGED
@@ -1493,8 +1493,8 @@ function escapeURL(value) {
1493
1493
 
1494
1494
  // src/legacy/create-element-type.ts
1495
1495
  function createElementType(type) {
1496
- const legacyWindow2 = window;
1497
- return class extends legacyWindow2.elementor.modules.elements.types.Widget {
1496
+ const legacyWindow = window;
1497
+ return class extends legacyWindow.elementor.modules.elements.types.Widget {
1498
1498
  getType() {
1499
1499
  return type;
1500
1500
  }
@@ -1504,8 +1504,8 @@ function createElementType(type) {
1504
1504
  };
1505
1505
  }
1506
1506
  function createElementViewClassDeclaration() {
1507
- const legacyWindow2 = window;
1508
- return class extends legacyWindow2.elementor.modules.elements.views.Widget {
1507
+ const legacyWindow = window;
1508
+ return class extends legacyWindow.elementor.modules.elements.views.Widget {
1509
1509
  // Dispatch `render` event so the overlay layer will be updated
1510
1510
  onRender(...args) {
1511
1511
  super.onRender(...args);
@@ -1551,7 +1551,7 @@ function createElementViewClassDeclaration() {
1551
1551
  );
1552
1552
  }
1553
1553
  #dispatchPreviewEvent(eventType) {
1554
- legacyWindow2.elementor?.$preview?.[0]?.contentWindow.dispatchEvent(
1554
+ legacyWindow.elementor?.$preview?.[0]?.contentWindow.dispatchEvent(
1555
1555
  new CustomEvent(eventType, {
1556
1556
  detail: {
1557
1557
  id: this.model.get("id"),
@@ -1711,14 +1711,11 @@ function createTemplatedElementView({
1711
1711
  }
1712
1712
 
1713
1713
  // src/legacy/replacements/inline-editing/inline-editing-elements.tsx
1714
- var React5 = __toESM(require("react"));
1715
- var import_react11 = require("react");
1714
+ var React6 = __toESM(require("react"));
1716
1715
  var import_client = require("react-dom/client");
1717
- var import_editor_controls2 = require("@elementor/editor-controls");
1718
1716
  var import_editor_elements3 = require("@elementor/editor-elements");
1719
1717
  var import_editor_props4 = require("@elementor/editor-props");
1720
1718
  var import_editor_v1_adapters9 = require("@elementor/editor-v1-adapters");
1721
- var import_ui4 = require("@elementor/ui");
1722
1719
  var import_i18n = require("@wordpress/i18n");
1723
1720
 
1724
1721
  // src/legacy/replacements/base.ts
@@ -1759,6 +1756,207 @@ var ReplacementBase = class {
1759
1756
  }
1760
1757
  };
1761
1758
 
1759
+ // src/legacy/replacements/inline-editing/canvas-inline-editor.tsx
1760
+ var React5 = __toESM(require("react"));
1761
+ var import_react11 = require("react");
1762
+ var import_editor_controls2 = require("@elementor/editor-controls");
1763
+ var import_ui4 = require("@elementor/ui");
1764
+ var import_react12 = require("@floating-ui/react");
1765
+
1766
+ // src/legacy/replacements/inline-editing/inline-editing-utils.ts
1767
+ var INLINE_EDITING_PROPERTY_PER_TYPE = {
1768
+ "e-form-label": "text",
1769
+ "e-heading": "title",
1770
+ "e-paragraph": "paragraph"
1771
+ };
1772
+ var calcSelectionCenterOffsets = (view) => {
1773
+ const frameWindow = view.root?.defaultView;
1774
+ const selection = frameWindow?.getSelection();
1775
+ const editorContainer = view.dom;
1776
+ if (!selection || !editorContainer) {
1777
+ return null;
1778
+ }
1779
+ const range = selection.getRangeAt(0);
1780
+ const selectionRect = range.getBoundingClientRect();
1781
+ const editorContainerRect = editorContainer.getBoundingClientRect();
1782
+ if (!selectionRect || !editorContainerRect) {
1783
+ return null;
1784
+ }
1785
+ const verticalOffset = selectionRect.top - editorContainerRect.top;
1786
+ const selectionCenter = selectionRect?.left + selectionRect?.width / 2;
1787
+ const horizontalOffset = selectionCenter - editorContainerRect.left;
1788
+ return { left: horizontalOffset, top: verticalOffset };
1789
+ };
1790
+ var getComputedStyle = (styles, offsets) => {
1791
+ const transform = extractTransformValue(styles);
1792
+ return transform ? {
1793
+ ...styles,
1794
+ marginLeft: `${offsets.left}px`,
1795
+ marginTop: `${offsets.top}px`,
1796
+ pointerEvents: "none"
1797
+ } : {
1798
+ display: "none"
1799
+ };
1800
+ };
1801
+ var extractTransformValue = (styles) => {
1802
+ const translateRegex = /translate\([^)]*\)\s?/g;
1803
+ const numericValuesRegex = /(-?\d+\.?\d*)/g;
1804
+ const translateValue = styles?.transform?.match(translateRegex)?.[0];
1805
+ const values = translateValue?.match(numericValuesRegex);
1806
+ if (!translateValue || !values) {
1807
+ return null;
1808
+ }
1809
+ const [numericX, numericY] = values.map(Number);
1810
+ if (!numericX || !numericY) {
1811
+ return null;
1812
+ }
1813
+ return styles.transform;
1814
+ };
1815
+
1816
+ // src/legacy/replacements/inline-editing/canvas-inline-editor.tsx
1817
+ var TOP_BAR_SELECTOR = "#elementor-editor-wrapper-v2";
1818
+ var NAVIGATOR_SELECTOR = "#elementor-navigator";
1819
+ var EDITING_PANEL = "#elementor-panel";
1820
+ var EDITOR_ELEMENTS_OUT_OF_IFRAME = [TOP_BAR_SELECTOR, NAVIGATOR_SELECTOR, EDITING_PANEL];
1821
+ var EDITOR_WRAPPER_SELECTOR = "inline-editor-wrapper";
1822
+ var CanvasInlineEditor = ({
1823
+ elementClasses,
1824
+ initialValue,
1825
+ expectedTag,
1826
+ rootElement,
1827
+ id,
1828
+ setValue,
1829
+ onBlur
1830
+ }) => {
1831
+ const [selectionOffsets, setSelectionOffsets] = (0, import_react11.useState)(null);
1832
+ const [editor, setEditor] = (0, import_react11.useState)(null);
1833
+ const onSelectionEnd = (view) => {
1834
+ const hasSelection = !view.state.selection.empty;
1835
+ setSelectionOffsets(hasSelection ? calcSelectionCenterOffsets(view) : null);
1836
+ };
1837
+ useOnClickOutsideIframe(onBlur);
1838
+ return /* @__PURE__ */ React5.createElement(import_ui4.ThemeProvider, null, /* @__PURE__ */ React5.createElement(InlineEditingOverlay, { expectedTag, rootElement, id }), /* @__PURE__ */ React5.createElement("style", null, `
1839
+ .${EDITOR_WRAPPER_SELECTOR}, .${EDITOR_WRAPPER_SELECTOR} > * {
1840
+ height: 100%;
1841
+ }
1842
+ .ProseMirror > * {
1843
+ height: 100%;
1844
+ }
1845
+ `), /* @__PURE__ */ React5.createElement(
1846
+ import_editor_controls2.InlineEditor,
1847
+ {
1848
+ onEditorCreate: setEditor,
1849
+ editorProps: {
1850
+ attributes: {
1851
+ style: "outline: none;overflow-wrap: normal;height:100%"
1852
+ }
1853
+ },
1854
+ elementClasses,
1855
+ value: initialValue,
1856
+ setValue,
1857
+ onBlur,
1858
+ autofocus: true,
1859
+ expectedTag,
1860
+ wrapperClassName: EDITOR_WRAPPER_SELECTOR,
1861
+ onSelectionEnd
1862
+ }
1863
+ ), selectionOffsets && editor && /* @__PURE__ */ React5.createElement(
1864
+ InlineEditingToolbarWrapper,
1865
+ {
1866
+ expectedTag,
1867
+ editor,
1868
+ rootElement,
1869
+ id,
1870
+ selectionOffsets
1871
+ }
1872
+ ));
1873
+ };
1874
+ var InlineEditingOverlay = ({
1875
+ expectedTag,
1876
+ rootElement,
1877
+ id
1878
+ }) => {
1879
+ const inlineEditedElement = getInlineEditorElement(rootElement, expectedTag);
1880
+ const [overlayRefElement, setOverlayElement] = (0, import_react11.useState)(inlineEditedElement);
1881
+ (0, import_react11.useEffect)(() => {
1882
+ setOverlayElement(getInlineEditorElement(rootElement, expectedTag));
1883
+ }, [expectedTag, rootElement]);
1884
+ return overlayRefElement ? /* @__PURE__ */ React5.createElement(OutlineOverlay, { element: overlayRefElement, id, isSelected: true }) : null;
1885
+ };
1886
+ var InlineEditingToolbarWrapper = ({
1887
+ expectedTag,
1888
+ editor,
1889
+ rootElement,
1890
+ id,
1891
+ selectionOffsets
1892
+ }) => {
1893
+ const [element, setElement] = (0, import_react11.useState)(null);
1894
+ (0, import_react11.useEffect)(() => {
1895
+ setElement(getInlineEditorElement(rootElement, expectedTag));
1896
+ }, [expectedTag, rootElement]);
1897
+ return element ? /* @__PURE__ */ React5.createElement(InlineEditingToolbar, { element, editor, id, selectionOffsets }) : null;
1898
+ };
1899
+ var InlineEditingToolbar = ({
1900
+ element,
1901
+ editor,
1902
+ id,
1903
+ selectionOffsets
1904
+ }) => {
1905
+ const { floating } = useFloatingOnElement({
1906
+ element,
1907
+ isSelected: true
1908
+ });
1909
+ const { getFloatingProps, getReferenceProps } = (0, import_react12.useInteractions)();
1910
+ const style = getComputedStyle(floating.styles, selectionOffsets);
1911
+ useBindReactPropsToElement(element, getReferenceProps);
1912
+ return /* @__PURE__ */ React5.createElement(import_react12.FloatingPortal, { id: CANVAS_WRAPPER_ID }, /* @__PURE__ */ React5.createElement(
1913
+ import_ui4.Box,
1914
+ {
1915
+ ref: floating.setRef,
1916
+ style: {
1917
+ ...floating.styles,
1918
+ pointerEvents: "none"
1919
+ },
1920
+ role: "presentation",
1921
+ ...getFloatingProps({ style })
1922
+ },
1923
+ floating.styles.transform && /* @__PURE__ */ React5.createElement(
1924
+ import_ui4.Box,
1925
+ {
1926
+ sx: {
1927
+ position: "relative",
1928
+ transform: "translateY(-100%)",
1929
+ height: "max-content"
1930
+ }
1931
+ },
1932
+ /* @__PURE__ */ React5.createElement(
1933
+ import_editor_controls2.InlineEditorToolbar,
1934
+ {
1935
+ editor,
1936
+ elementId: id,
1937
+ sx: {
1938
+ transform: "translateX(-50%)"
1939
+ }
1940
+ }
1941
+ )
1942
+ )
1943
+ ));
1944
+ };
1945
+ var getInlineEditorElement = (elementWrapper, expectedTag) => {
1946
+ return !expectedTag ? null : elementWrapper.querySelector(expectedTag);
1947
+ };
1948
+ var useOnClickOutsideIframe = (handleUnmount) => {
1949
+ const asyncUnmountInlineEditor = React5.useCallback(() => queueMicrotask(handleUnmount), [handleUnmount]);
1950
+ (0, import_react11.useEffect)(() => {
1951
+ EDITOR_ELEMENTS_OUT_OF_IFRAME.forEach(
1952
+ (selector) => document?.querySelector(selector)?.addEventListener("mousedown", asyncUnmountInlineEditor)
1953
+ );
1954
+ return () => EDITOR_ELEMENTS_OUT_OF_IFRAME.forEach(
1955
+ (selector) => document?.querySelector(selector)?.removeEventListener("mousedown", asyncUnmountInlineEditor)
1956
+ );
1957
+ }, []);
1958
+ };
1959
+
1762
1960
  // src/legacy/replacements/inline-editing/inline-editing-eligibility.ts
1763
1961
  var import_editor_props3 = require("@elementor/editor-props");
1764
1962
  var hasKey = (propType) => {
@@ -1788,32 +1986,8 @@ var isInlineEditingAllowed = ({ rawValue, propTypeFromSchema }) => {
1788
1986
  return import_editor_props3.htmlPropTypeUtil.isValid(rawValue) || import_editor_props3.stringPropTypeUtil.isValid(rawValue);
1789
1987
  };
1790
1988
 
1791
- // src/legacy/replacements/inline-editing/inline-editing-utils.ts
1792
- var INLINE_EDITING_PROPERTY_PER_TYPE = {
1793
- "e-form-label": "text",
1794
- "e-heading": "title",
1795
- "e-paragraph": "paragraph"
1796
- };
1797
- var legacyWindow = window;
1798
- var getInitialPopoverPosition = () => {
1799
- const positionFallback = { left: 0, top: 0 };
1800
- const iFrameElement = legacyWindow?.elementor?.$preview?.get(0);
1801
- const iFramePosition = iFrameElement?.getBoundingClientRect() ?? positionFallback;
1802
- const previewElement = legacyWindow?.elementor?.$previewWrapper?.get(0);
1803
- const previewPosition = previewElement ? { left: previewElement.scrollLeft, top: previewElement.scrollTop } : positionFallback;
1804
- return {
1805
- left: iFramePosition.left + previewPosition.left,
1806
- top: iFramePosition.top + previewPosition.top
1807
- };
1808
- };
1809
-
1810
1989
  // src/legacy/replacements/inline-editing/inline-editing-elements.tsx
1811
1990
  var HISTORY_DEBOUNCE_WAIT = 800;
1812
- var TOP_BAR_SELECTOR = "#elementor-editor-wrapper-v2";
1813
- var NAVIGATOR_SELECTOR = "#elementor-navigator";
1814
- var V4_EDITING_PANEL = "main.MuiBox-root";
1815
- var V3_EDITING_PANEL = "#elementor-panel-content-wrapper";
1816
- var BLUR_TRIGGERING_SELECTORS = [TOP_BAR_SELECTOR, NAVIGATOR_SELECTOR, V4_EDITING_PANEL, V3_EDITING_PANEL];
1817
1991
  var InlineEditingReplacement = class extends ReplacementBase {
1818
1992
  inlineEditorRoot = null;
1819
1993
  handlerAttached = false;
@@ -1827,7 +2001,7 @@ var InlineEditingReplacement = class extends ReplacementBase {
1827
2001
  return !!this.inlineEditorRoot;
1828
2002
  }
1829
2003
  shouldRenderReplacement() {
1830
- return this.isInlineEditingEligible();
2004
+ return this.isInlineEditingEligible() && (0, import_editor_v1_adapters9.getCurrentEditMode)() === "edit";
1831
2005
  }
1832
2006
  handleRenderInlineEditor = () => {
1833
2007
  if (this.isEditingModeActive() || !this.isInlineEditingEligible()) {
@@ -1973,65 +2147,26 @@ var InlineEditingReplacement = class extends ReplacementBase {
1973
2147
  if (this.isEditingModeActive()) {
1974
2148
  this.resetInlineEditorRoot();
1975
2149
  }
1976
- const InlineEditorApp = this.InlineEditorApp;
1977
- const wrapperClasses = "elementor";
1978
2150
  const elementClasses = this.element.children?.[0]?.classList.toString() ?? "";
2151
+ const propValue = this.getExtractedContentValue();
2152
+ const expectedTag = this.getExpectedTag();
1979
2153
  this.element.innerHTML = "";
1980
2154
  this.inlineEditorRoot = (0, import_client.createRoot)(this.element);
1981
2155
  this.inlineEditorRoot.render(
1982
- /* @__PURE__ */ React5.createElement(InlineEditorApp, { wrapperClasses, elementClasses })
1983
- );
1984
- }
1985
- InlineEditorApp = ({ wrapperClasses, elementClasses }) => {
1986
- const propValue = this.getExtractedContentValue();
1987
- const expectedTag = this.getExpectedTag();
1988
- const wrapperRef = (0, import_react11.useRef)(null);
1989
- const [isWrapperRendered, setIsWrapperRendered] = (0, import_react11.useState)(false);
1990
- (0, import_react11.useEffect)(() => {
1991
- setIsWrapperRendered(!!wrapperRef.current);
1992
- BLUR_TRIGGERING_SELECTORS.forEach(
1993
- (selector) => document?.querySelector(selector)?.addEventListener("mousedown", asyncUnmountInlineEditor)
1994
- );
1995
- return () => BLUR_TRIGGERING_SELECTORS.forEach(
1996
- (selector) => document?.querySelector(selector)?.removeEventListener("mousedown", asyncUnmountInlineEditor)
1997
- );
1998
- }, []);
1999
- const asyncUnmountInlineEditor = React5.useCallback(
2000
- () => queueMicrotask(this.unmountInlineEditor.bind(this)),
2001
- []
2002
- );
2003
- return /* @__PURE__ */ React5.createElement(import_ui4.ThemeProvider, null, /* @__PURE__ */ React5.createElement(
2004
- import_ui4.Box,
2005
- {
2006
- ref: wrapperRef,
2007
- sx: {
2008
- "& .elementor-inline-editor-reset": {
2009
- margin: 0,
2010
- padding: 0
2011
- }
2012
- }
2013
- },
2014
- isWrapperRendered && /* @__PURE__ */ React5.createElement(OutlineOverlay, { element: wrapperRef.current, id: this.id, isSelected: true }),
2015
- /* @__PURE__ */ React5.createElement(
2016
- import_editor_controls2.InlineEditor,
2156
+ /* @__PURE__ */ React6.createElement(
2157
+ CanvasInlineEditor,
2017
2158
  {
2018
- attributes: {
2019
- class: wrapperClasses,
2020
- style: "outline: none;"
2021
- },
2022
2159
  elementClasses,
2023
- value: propValue,
2024
- setValue: this.setContentValue.bind(this),
2025
- onBlur: this.unmountInlineEditor.bind(this),
2026
- autofocus: true,
2027
- showToolbar: true,
2028
- getInitialPopoverPosition,
2160
+ initialValue: propValue,
2029
2161
  expectedTag,
2030
- elementId: this.id
2162
+ rootElement: this.element,
2163
+ id: this.id,
2164
+ setValue: this.setContentValue.bind(this),
2165
+ onBlur: this.unmountInlineEditor.bind(this)
2031
2166
  }
2032
2167
  )
2033
- ));
2034
- };
2168
+ );
2169
+ }
2035
2170
  };
2036
2171
 
2037
2172
  // src/legacy/replacements/manager.ts
@@ -2116,13 +2251,13 @@ var createTemplatedElementTypeWithReplacements = ({
2116
2251
  renderer,
2117
2252
  element
2118
2253
  }) => {
2119
- const legacyWindow2 = window;
2254
+ const legacyWindow = window;
2120
2255
  const view = createViewWithReplacements({
2121
2256
  type,
2122
2257
  renderer,
2123
2258
  element
2124
2259
  });
2125
- return class extends legacyWindow2.elementor.modules.elements.types.Widget {
2260
+ return class extends legacyWindow.elementor.modules.elements.types.Widget {
2126
2261
  getType() {
2127
2262
  return type;
2128
2263
  }
@@ -2140,7 +2275,7 @@ function registerElementType(type, elementTypeGenerator) {
2140
2275
  function initLegacyViews() {
2141
2276
  (0, import_editor_v1_adapters10.__privateListenTo)((0, import_editor_v1_adapters10.v1ReadyEvent)(), () => {
2142
2277
  const config = (0, import_editor_elements4.getWidgetsCache)() ?? {};
2143
- const legacyWindow2 = window;
2278
+ const legacyWindow = window;
2144
2279
  const renderer = createDomRenderer();
2145
2280
  Object.entries(config).forEach(([type, element]) => {
2146
2281
  if (!element.atomic) {
@@ -2154,7 +2289,7 @@ function initLegacyViews() {
2154
2289
  } else {
2155
2290
  ElementType = createElementType(type);
2156
2291
  }
2157
- legacyWindow2.elementor.elementsManager.registerElementType(new ElementType());
2292
+ legacyWindow.elementor.elementsManager.registerElementType(new ElementType());
2158
2293
  });
2159
2294
  });
2160
2295
  }
package/dist/index.mjs CHANGED
@@ -1464,8 +1464,8 @@ function escapeURL(value) {
1464
1464
 
1465
1465
  // src/legacy/create-element-type.ts
1466
1466
  function createElementType(type) {
1467
- const legacyWindow2 = window;
1468
- return class extends legacyWindow2.elementor.modules.elements.types.Widget {
1467
+ const legacyWindow = window;
1468
+ return class extends legacyWindow.elementor.modules.elements.types.Widget {
1469
1469
  getType() {
1470
1470
  return type;
1471
1471
  }
@@ -1475,8 +1475,8 @@ function createElementType(type) {
1475
1475
  };
1476
1476
  }
1477
1477
  function createElementViewClassDeclaration() {
1478
- const legacyWindow2 = window;
1479
- return class extends legacyWindow2.elementor.modules.elements.views.Widget {
1478
+ const legacyWindow = window;
1479
+ return class extends legacyWindow.elementor.modules.elements.views.Widget {
1480
1480
  // Dispatch `render` event so the overlay layer will be updated
1481
1481
  onRender(...args) {
1482
1482
  super.onRender(...args);
@@ -1522,7 +1522,7 @@ function createElementViewClassDeclaration() {
1522
1522
  );
1523
1523
  }
1524
1524
  #dispatchPreviewEvent(eventType) {
1525
- legacyWindow2.elementor?.$preview?.[0]?.contentWindow.dispatchEvent(
1525
+ legacyWindow.elementor?.$preview?.[0]?.contentWindow.dispatchEvent(
1526
1526
  new CustomEvent(eventType, {
1527
1527
  detail: {
1528
1528
  id: this.model.get("id"),
@@ -1682,14 +1682,11 @@ function createTemplatedElementView({
1682
1682
  }
1683
1683
 
1684
1684
  // src/legacy/replacements/inline-editing/inline-editing-elements.tsx
1685
- import * as React5 from "react";
1686
- import { useEffect as useEffect7, useRef as useRef2, useState as useState4 } from "react";
1685
+ import * as React6 from "react";
1687
1686
  import { createRoot } from "react-dom/client";
1688
- import { InlineEditor } from "@elementor/editor-controls";
1689
1687
  import { getContainer, getElementLabel, getElementType } from "@elementor/editor-elements";
1690
1688
  import { htmlPropTypeUtil as htmlPropTypeUtil2, stringPropTypeUtil as stringPropTypeUtil2 } from "@elementor/editor-props";
1691
- import { __privateRunCommandSync as runCommandSync, undoable } from "@elementor/editor-v1-adapters";
1692
- import { Box as Box2, ThemeProvider } from "@elementor/ui";
1689
+ import { __privateRunCommandSync as runCommandSync, getCurrentEditMode, undoable } from "@elementor/editor-v1-adapters";
1693
1690
  import { __ } from "@wordpress/i18n";
1694
1691
 
1695
1692
  // src/legacy/replacements/base.ts
@@ -1730,6 +1727,207 @@ var ReplacementBase = class {
1730
1727
  }
1731
1728
  };
1732
1729
 
1730
+ // src/legacy/replacements/inline-editing/canvas-inline-editor.tsx
1731
+ import * as React5 from "react";
1732
+ import { useEffect as useEffect7, useState as useState4 } from "react";
1733
+ import { InlineEditor, InlineEditorToolbar } from "@elementor/editor-controls";
1734
+ import { Box as Box2, ThemeProvider } from "@elementor/ui";
1735
+ import { FloatingPortal as FloatingPortal2, useInteractions as useInteractions2 } from "@floating-ui/react";
1736
+
1737
+ // src/legacy/replacements/inline-editing/inline-editing-utils.ts
1738
+ var INLINE_EDITING_PROPERTY_PER_TYPE = {
1739
+ "e-form-label": "text",
1740
+ "e-heading": "title",
1741
+ "e-paragraph": "paragraph"
1742
+ };
1743
+ var calcSelectionCenterOffsets = (view) => {
1744
+ const frameWindow = view.root?.defaultView;
1745
+ const selection = frameWindow?.getSelection();
1746
+ const editorContainer = view.dom;
1747
+ if (!selection || !editorContainer) {
1748
+ return null;
1749
+ }
1750
+ const range = selection.getRangeAt(0);
1751
+ const selectionRect = range.getBoundingClientRect();
1752
+ const editorContainerRect = editorContainer.getBoundingClientRect();
1753
+ if (!selectionRect || !editorContainerRect) {
1754
+ return null;
1755
+ }
1756
+ const verticalOffset = selectionRect.top - editorContainerRect.top;
1757
+ const selectionCenter = selectionRect?.left + selectionRect?.width / 2;
1758
+ const horizontalOffset = selectionCenter - editorContainerRect.left;
1759
+ return { left: horizontalOffset, top: verticalOffset };
1760
+ };
1761
+ var getComputedStyle = (styles, offsets) => {
1762
+ const transform = extractTransformValue(styles);
1763
+ return transform ? {
1764
+ ...styles,
1765
+ marginLeft: `${offsets.left}px`,
1766
+ marginTop: `${offsets.top}px`,
1767
+ pointerEvents: "none"
1768
+ } : {
1769
+ display: "none"
1770
+ };
1771
+ };
1772
+ var extractTransformValue = (styles) => {
1773
+ const translateRegex = /translate\([^)]*\)\s?/g;
1774
+ const numericValuesRegex = /(-?\d+\.?\d*)/g;
1775
+ const translateValue = styles?.transform?.match(translateRegex)?.[0];
1776
+ const values = translateValue?.match(numericValuesRegex);
1777
+ if (!translateValue || !values) {
1778
+ return null;
1779
+ }
1780
+ const [numericX, numericY] = values.map(Number);
1781
+ if (!numericX || !numericY) {
1782
+ return null;
1783
+ }
1784
+ return styles.transform;
1785
+ };
1786
+
1787
+ // src/legacy/replacements/inline-editing/canvas-inline-editor.tsx
1788
+ var TOP_BAR_SELECTOR = "#elementor-editor-wrapper-v2";
1789
+ var NAVIGATOR_SELECTOR = "#elementor-navigator";
1790
+ var EDITING_PANEL = "#elementor-panel";
1791
+ var EDITOR_ELEMENTS_OUT_OF_IFRAME = [TOP_BAR_SELECTOR, NAVIGATOR_SELECTOR, EDITING_PANEL];
1792
+ var EDITOR_WRAPPER_SELECTOR = "inline-editor-wrapper";
1793
+ var CanvasInlineEditor = ({
1794
+ elementClasses,
1795
+ initialValue,
1796
+ expectedTag,
1797
+ rootElement,
1798
+ id,
1799
+ setValue,
1800
+ onBlur
1801
+ }) => {
1802
+ const [selectionOffsets, setSelectionOffsets] = useState4(null);
1803
+ const [editor, setEditor] = useState4(null);
1804
+ const onSelectionEnd = (view) => {
1805
+ const hasSelection = !view.state.selection.empty;
1806
+ setSelectionOffsets(hasSelection ? calcSelectionCenterOffsets(view) : null);
1807
+ };
1808
+ useOnClickOutsideIframe(onBlur);
1809
+ return /* @__PURE__ */ React5.createElement(ThemeProvider, null, /* @__PURE__ */ React5.createElement(InlineEditingOverlay, { expectedTag, rootElement, id }), /* @__PURE__ */ React5.createElement("style", null, `
1810
+ .${EDITOR_WRAPPER_SELECTOR}, .${EDITOR_WRAPPER_SELECTOR} > * {
1811
+ height: 100%;
1812
+ }
1813
+ .ProseMirror > * {
1814
+ height: 100%;
1815
+ }
1816
+ `), /* @__PURE__ */ React5.createElement(
1817
+ InlineEditor,
1818
+ {
1819
+ onEditorCreate: setEditor,
1820
+ editorProps: {
1821
+ attributes: {
1822
+ style: "outline: none;overflow-wrap: normal;height:100%"
1823
+ }
1824
+ },
1825
+ elementClasses,
1826
+ value: initialValue,
1827
+ setValue,
1828
+ onBlur,
1829
+ autofocus: true,
1830
+ expectedTag,
1831
+ wrapperClassName: EDITOR_WRAPPER_SELECTOR,
1832
+ onSelectionEnd
1833
+ }
1834
+ ), selectionOffsets && editor && /* @__PURE__ */ React5.createElement(
1835
+ InlineEditingToolbarWrapper,
1836
+ {
1837
+ expectedTag,
1838
+ editor,
1839
+ rootElement,
1840
+ id,
1841
+ selectionOffsets
1842
+ }
1843
+ ));
1844
+ };
1845
+ var InlineEditingOverlay = ({
1846
+ expectedTag,
1847
+ rootElement,
1848
+ id
1849
+ }) => {
1850
+ const inlineEditedElement = getInlineEditorElement(rootElement, expectedTag);
1851
+ const [overlayRefElement, setOverlayElement] = useState4(inlineEditedElement);
1852
+ useEffect7(() => {
1853
+ setOverlayElement(getInlineEditorElement(rootElement, expectedTag));
1854
+ }, [expectedTag, rootElement]);
1855
+ return overlayRefElement ? /* @__PURE__ */ React5.createElement(OutlineOverlay, { element: overlayRefElement, id, isSelected: true }) : null;
1856
+ };
1857
+ var InlineEditingToolbarWrapper = ({
1858
+ expectedTag,
1859
+ editor,
1860
+ rootElement,
1861
+ id,
1862
+ selectionOffsets
1863
+ }) => {
1864
+ const [element, setElement] = useState4(null);
1865
+ useEffect7(() => {
1866
+ setElement(getInlineEditorElement(rootElement, expectedTag));
1867
+ }, [expectedTag, rootElement]);
1868
+ return element ? /* @__PURE__ */ React5.createElement(InlineEditingToolbar, { element, editor, id, selectionOffsets }) : null;
1869
+ };
1870
+ var InlineEditingToolbar = ({
1871
+ element,
1872
+ editor,
1873
+ id,
1874
+ selectionOffsets
1875
+ }) => {
1876
+ const { floating } = useFloatingOnElement({
1877
+ element,
1878
+ isSelected: true
1879
+ });
1880
+ const { getFloatingProps, getReferenceProps } = useInteractions2();
1881
+ const style = getComputedStyle(floating.styles, selectionOffsets);
1882
+ useBindReactPropsToElement(element, getReferenceProps);
1883
+ return /* @__PURE__ */ React5.createElement(FloatingPortal2, { id: CANVAS_WRAPPER_ID }, /* @__PURE__ */ React5.createElement(
1884
+ Box2,
1885
+ {
1886
+ ref: floating.setRef,
1887
+ style: {
1888
+ ...floating.styles,
1889
+ pointerEvents: "none"
1890
+ },
1891
+ role: "presentation",
1892
+ ...getFloatingProps({ style })
1893
+ },
1894
+ floating.styles.transform && /* @__PURE__ */ React5.createElement(
1895
+ Box2,
1896
+ {
1897
+ sx: {
1898
+ position: "relative",
1899
+ transform: "translateY(-100%)",
1900
+ height: "max-content"
1901
+ }
1902
+ },
1903
+ /* @__PURE__ */ React5.createElement(
1904
+ InlineEditorToolbar,
1905
+ {
1906
+ editor,
1907
+ elementId: id,
1908
+ sx: {
1909
+ transform: "translateX(-50%)"
1910
+ }
1911
+ }
1912
+ )
1913
+ )
1914
+ ));
1915
+ };
1916
+ var getInlineEditorElement = (elementWrapper, expectedTag) => {
1917
+ return !expectedTag ? null : elementWrapper.querySelector(expectedTag);
1918
+ };
1919
+ var useOnClickOutsideIframe = (handleUnmount) => {
1920
+ const asyncUnmountInlineEditor = React5.useCallback(() => queueMicrotask(handleUnmount), [handleUnmount]);
1921
+ useEffect7(() => {
1922
+ EDITOR_ELEMENTS_OUT_OF_IFRAME.forEach(
1923
+ (selector) => document?.querySelector(selector)?.addEventListener("mousedown", asyncUnmountInlineEditor)
1924
+ );
1925
+ return () => EDITOR_ELEMENTS_OUT_OF_IFRAME.forEach(
1926
+ (selector) => document?.querySelector(selector)?.removeEventListener("mousedown", asyncUnmountInlineEditor)
1927
+ );
1928
+ }, []);
1929
+ };
1930
+
1733
1931
  // src/legacy/replacements/inline-editing/inline-editing-eligibility.ts
1734
1932
  import { htmlPropTypeUtil, stringPropTypeUtil } from "@elementor/editor-props";
1735
1933
  var hasKey = (propType) => {
@@ -1759,32 +1957,8 @@ var isInlineEditingAllowed = ({ rawValue, propTypeFromSchema }) => {
1759
1957
  return htmlPropTypeUtil.isValid(rawValue) || stringPropTypeUtil.isValid(rawValue);
1760
1958
  };
1761
1959
 
1762
- // src/legacy/replacements/inline-editing/inline-editing-utils.ts
1763
- var INLINE_EDITING_PROPERTY_PER_TYPE = {
1764
- "e-form-label": "text",
1765
- "e-heading": "title",
1766
- "e-paragraph": "paragraph"
1767
- };
1768
- var legacyWindow = window;
1769
- var getInitialPopoverPosition = () => {
1770
- const positionFallback = { left: 0, top: 0 };
1771
- const iFrameElement = legacyWindow?.elementor?.$preview?.get(0);
1772
- const iFramePosition = iFrameElement?.getBoundingClientRect() ?? positionFallback;
1773
- const previewElement = legacyWindow?.elementor?.$previewWrapper?.get(0);
1774
- const previewPosition = previewElement ? { left: previewElement.scrollLeft, top: previewElement.scrollTop } : positionFallback;
1775
- return {
1776
- left: iFramePosition.left + previewPosition.left,
1777
- top: iFramePosition.top + previewPosition.top
1778
- };
1779
- };
1780
-
1781
1960
  // src/legacy/replacements/inline-editing/inline-editing-elements.tsx
1782
1961
  var HISTORY_DEBOUNCE_WAIT = 800;
1783
- var TOP_BAR_SELECTOR = "#elementor-editor-wrapper-v2";
1784
- var NAVIGATOR_SELECTOR = "#elementor-navigator";
1785
- var V4_EDITING_PANEL = "main.MuiBox-root";
1786
- var V3_EDITING_PANEL = "#elementor-panel-content-wrapper";
1787
- var BLUR_TRIGGERING_SELECTORS = [TOP_BAR_SELECTOR, NAVIGATOR_SELECTOR, V4_EDITING_PANEL, V3_EDITING_PANEL];
1788
1962
  var InlineEditingReplacement = class extends ReplacementBase {
1789
1963
  inlineEditorRoot = null;
1790
1964
  handlerAttached = false;
@@ -1798,7 +1972,7 @@ var InlineEditingReplacement = class extends ReplacementBase {
1798
1972
  return !!this.inlineEditorRoot;
1799
1973
  }
1800
1974
  shouldRenderReplacement() {
1801
- return this.isInlineEditingEligible();
1975
+ return this.isInlineEditingEligible() && getCurrentEditMode() === "edit";
1802
1976
  }
1803
1977
  handleRenderInlineEditor = () => {
1804
1978
  if (this.isEditingModeActive() || !this.isInlineEditingEligible()) {
@@ -1944,65 +2118,26 @@ var InlineEditingReplacement = class extends ReplacementBase {
1944
2118
  if (this.isEditingModeActive()) {
1945
2119
  this.resetInlineEditorRoot();
1946
2120
  }
1947
- const InlineEditorApp = this.InlineEditorApp;
1948
- const wrapperClasses = "elementor";
1949
2121
  const elementClasses = this.element.children?.[0]?.classList.toString() ?? "";
2122
+ const propValue = this.getExtractedContentValue();
2123
+ const expectedTag = this.getExpectedTag();
1950
2124
  this.element.innerHTML = "";
1951
2125
  this.inlineEditorRoot = createRoot(this.element);
1952
2126
  this.inlineEditorRoot.render(
1953
- /* @__PURE__ */ React5.createElement(InlineEditorApp, { wrapperClasses, elementClasses })
1954
- );
1955
- }
1956
- InlineEditorApp = ({ wrapperClasses, elementClasses }) => {
1957
- const propValue = this.getExtractedContentValue();
1958
- const expectedTag = this.getExpectedTag();
1959
- const wrapperRef = useRef2(null);
1960
- const [isWrapperRendered, setIsWrapperRendered] = useState4(false);
1961
- useEffect7(() => {
1962
- setIsWrapperRendered(!!wrapperRef.current);
1963
- BLUR_TRIGGERING_SELECTORS.forEach(
1964
- (selector) => document?.querySelector(selector)?.addEventListener("mousedown", asyncUnmountInlineEditor)
1965
- );
1966
- return () => BLUR_TRIGGERING_SELECTORS.forEach(
1967
- (selector) => document?.querySelector(selector)?.removeEventListener("mousedown", asyncUnmountInlineEditor)
1968
- );
1969
- }, []);
1970
- const asyncUnmountInlineEditor = React5.useCallback(
1971
- () => queueMicrotask(this.unmountInlineEditor.bind(this)),
1972
- []
1973
- );
1974
- return /* @__PURE__ */ React5.createElement(ThemeProvider, null, /* @__PURE__ */ React5.createElement(
1975
- Box2,
1976
- {
1977
- ref: wrapperRef,
1978
- sx: {
1979
- "& .elementor-inline-editor-reset": {
1980
- margin: 0,
1981
- padding: 0
1982
- }
1983
- }
1984
- },
1985
- isWrapperRendered && /* @__PURE__ */ React5.createElement(OutlineOverlay, { element: wrapperRef.current, id: this.id, isSelected: true }),
1986
- /* @__PURE__ */ React5.createElement(
1987
- InlineEditor,
2127
+ /* @__PURE__ */ React6.createElement(
2128
+ CanvasInlineEditor,
1988
2129
  {
1989
- attributes: {
1990
- class: wrapperClasses,
1991
- style: "outline: none;"
1992
- },
1993
2130
  elementClasses,
1994
- value: propValue,
1995
- setValue: this.setContentValue.bind(this),
1996
- onBlur: this.unmountInlineEditor.bind(this),
1997
- autofocus: true,
1998
- showToolbar: true,
1999
- getInitialPopoverPosition,
2131
+ initialValue: propValue,
2000
2132
  expectedTag,
2001
- elementId: this.id
2133
+ rootElement: this.element,
2134
+ id: this.id,
2135
+ setValue: this.setContentValue.bind(this),
2136
+ onBlur: this.unmountInlineEditor.bind(this)
2002
2137
  }
2003
2138
  )
2004
- ));
2005
- };
2139
+ );
2140
+ }
2006
2141
  };
2007
2142
 
2008
2143
  // src/legacy/replacements/manager.ts
@@ -2087,13 +2222,13 @@ var createTemplatedElementTypeWithReplacements = ({
2087
2222
  renderer,
2088
2223
  element
2089
2224
  }) => {
2090
- const legacyWindow2 = window;
2225
+ const legacyWindow = window;
2091
2226
  const view = createViewWithReplacements({
2092
2227
  type,
2093
2228
  renderer,
2094
2229
  element
2095
2230
  });
2096
- return class extends legacyWindow2.elementor.modules.elements.types.Widget {
2231
+ return class extends legacyWindow.elementor.modules.elements.types.Widget {
2097
2232
  getType() {
2098
2233
  return type;
2099
2234
  }
@@ -2111,7 +2246,7 @@ function registerElementType(type, elementTypeGenerator) {
2111
2246
  function initLegacyViews() {
2112
2247
  __privateListenTo(v1ReadyEvent2(), () => {
2113
2248
  const config = getWidgetsCache2() ?? {};
2114
- const legacyWindow2 = window;
2249
+ const legacyWindow = window;
2115
2250
  const renderer = createDomRenderer();
2116
2251
  Object.entries(config).forEach(([type, element]) => {
2117
2252
  if (!element.atomic) {
@@ -2125,7 +2260,7 @@ function initLegacyViews() {
2125
2260
  } else {
2126
2261
  ElementType = createElementType(type);
2127
2262
  }
2128
- legacyWindow2.elementor.elementsManager.registerElementType(new ElementType());
2263
+ legacyWindow.elementor.elementsManager.registerElementType(new ElementType());
2129
2264
  });
2130
2265
  });
2131
2266
  }
@@ -2207,7 +2342,7 @@ function extractElementData(element) {
2207
2342
 
2208
2343
  // src/mcp/tools/build-composition/tool.ts
2209
2344
  import {
2210
- createElement as createElement7,
2345
+ createElement as createElement8,
2211
2346
  deleteElement,
2212
2347
  getContainer as getContainer3,
2213
2348
  getWidgetsCache as getWidgetsCache6
@@ -2215,7 +2350,7 @@ import {
2215
2350
 
2216
2351
  // src/composition-builder/composition-builder.ts
2217
2352
  import {
2218
- createElement as createElement6,
2353
+ createElement as createElement7,
2219
2354
  generateElementId,
2220
2355
  getContainer as getContainer2,
2221
2356
  getWidgetsCache as getWidgetsCache5
@@ -2433,7 +2568,7 @@ var CompositionBuilder = class _CompositionBuilder {
2433
2568
  rootContainers = [];
2434
2569
  containerElements = [];
2435
2570
  api = {
2436
- createElement: createElement6,
2571
+ createElement: createElement7,
2437
2572
  getWidgetsCache: getWidgetsCache5,
2438
2573
  generateElementId,
2439
2574
  getContainer: getContainer2,
@@ -2822,7 +2957,7 @@ var initBuildCompositionsTool = (reg) => {
2822
2957
  const documentContainer = getContainer3("document");
2823
2958
  try {
2824
2959
  const compositionBuilder = CompositionBuilder.fromXMLString(xmlStructure, {
2825
- createElement: createElement7,
2960
+ createElement: createElement8,
2826
2961
  getWidgetsCache: getWidgetsCache6
2827
2962
  });
2828
2963
  compositionBuilder.setElementConfig(elementConfig);
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-512",
4
+ "version": "4.0.0-513",
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-512",
41
- "@elementor/editor-controls": "4.0.0-512",
42
- "@elementor/editor-documents": "4.0.0-512",
43
- "@elementor/editor-elements": "4.0.0-512",
44
- "@elementor/editor-interactions": "4.0.0-512",
45
- "@elementor/editor-mcp": "4.0.0-512",
46
- "@elementor/editor-notifications": "4.0.0-512",
47
- "@elementor/editor-props": "4.0.0-512",
48
- "@elementor/editor-responsive": "4.0.0-512",
49
- "@elementor/editor-styles": "4.0.0-512",
50
- "@elementor/editor-styles-repository": "4.0.0-512",
51
- "@elementor/editor-ui": "4.0.0-512",
52
- "@elementor/editor-v1-adapters": "4.0.0-512",
53
- "@elementor/schema": "4.0.0-512",
54
- "@elementor/twing": "4.0.0-512",
40
+ "@elementor/editor": "4.0.0-513",
41
+ "@elementor/editor-controls": "4.0.0-513",
42
+ "@elementor/editor-documents": "4.0.0-513",
43
+ "@elementor/editor-elements": "4.0.0-513",
44
+ "@elementor/editor-interactions": "4.0.0-513",
45
+ "@elementor/editor-mcp": "4.0.0-513",
46
+ "@elementor/editor-notifications": "4.0.0-513",
47
+ "@elementor/editor-props": "4.0.0-513",
48
+ "@elementor/editor-responsive": "4.0.0-513",
49
+ "@elementor/editor-styles": "4.0.0-513",
50
+ "@elementor/editor-styles-repository": "4.0.0-513",
51
+ "@elementor/editor-ui": "4.0.0-513",
52
+ "@elementor/editor-v1-adapters": "4.0.0-513",
53
+ "@elementor/schema": "4.0.0-513",
54
+ "@elementor/twing": "4.0.0-513",
55
55
  "@elementor/ui": "1.36.17",
56
- "@elementor/utils": "4.0.0-512",
57
- "@elementor/wp-media": "4.0.0-512",
56
+ "@elementor/utils": "4.0.0-513",
57
+ "@elementor/wp-media": "4.0.0-513",
58
58
  "@floating-ui/react": "^0.27.5",
59
59
  "@wordpress/i18n": "^5.13.0"
60
60
  },
@@ -0,0 +1,215 @@
1
+ import * as React from 'react';
2
+ import { useEffect, useState } from 'react';
3
+ import { InlineEditor, InlineEditorToolbar } from '@elementor/editor-controls';
4
+ import { Box, ThemeProvider } from '@elementor/ui';
5
+ import { FloatingPortal, useInteractions } from '@floating-ui/react';
6
+
7
+ import { CANVAS_WRAPPER_ID, OutlineOverlay } from '../../../components/outline-overlay';
8
+ import { useBindReactPropsToElement } from '../../../hooks/use-bind-react-props-to-element';
9
+ import { useFloatingOnElement } from '../../../hooks/use-floating-on-element';
10
+ import {
11
+ calcSelectionCenterOffsets,
12
+ type Editor,
13
+ type EditorView,
14
+ getComputedStyle,
15
+ type Offsets,
16
+ } from './inline-editing-utils';
17
+
18
+ const TOP_BAR_SELECTOR = '#elementor-editor-wrapper-v2';
19
+ const NAVIGATOR_SELECTOR = '#elementor-navigator';
20
+ const EDITING_PANEL = '#elementor-panel';
21
+
22
+ const EDITOR_ELEMENTS_OUT_OF_IFRAME = [ TOP_BAR_SELECTOR, NAVIGATOR_SELECTOR, EDITING_PANEL ];
23
+
24
+ const EDITOR_WRAPPER_SELECTOR = 'inline-editor-wrapper';
25
+
26
+ export const CanvasInlineEditor = ( {
27
+ elementClasses,
28
+ initialValue,
29
+ expectedTag,
30
+ rootElement,
31
+ id,
32
+ setValue,
33
+ onBlur,
34
+ }: {
35
+ elementClasses: string;
36
+ initialValue: string | null;
37
+ expectedTag: string | null;
38
+ rootElement: HTMLElement;
39
+ id: string;
40
+ setValue: ( value: string | null ) => void;
41
+ onBlur: () => void;
42
+ } ) => {
43
+ const [ selectionOffsets, setSelectionOffsets ] = useState< Offsets | null >( null );
44
+ const [ editor, setEditor ] = useState< Editor | null >( null );
45
+
46
+ const onSelectionEnd = ( view: EditorView ) => {
47
+ const hasSelection = ! view.state.selection.empty;
48
+
49
+ setSelectionOffsets( hasSelection ? calcSelectionCenterOffsets( view ) : null );
50
+ };
51
+
52
+ useOnClickOutsideIframe( onBlur );
53
+
54
+ return (
55
+ <ThemeProvider>
56
+ <InlineEditingOverlay expectedTag={ expectedTag } rootElement={ rootElement } id={ id } />
57
+ <style>
58
+ { `
59
+ .${ EDITOR_WRAPPER_SELECTOR }, .${ EDITOR_WRAPPER_SELECTOR } > * {
60
+ height: 100%;
61
+ }
62
+ .ProseMirror > * {
63
+ height: 100%;
64
+ }
65
+ ` }
66
+ </style>
67
+ <InlineEditor
68
+ onEditorCreate={ setEditor }
69
+ editorProps={ {
70
+ attributes: {
71
+ style: 'outline: none;overflow-wrap: normal;height:100%',
72
+ },
73
+ } }
74
+ elementClasses={ elementClasses }
75
+ value={ initialValue }
76
+ setValue={ setValue }
77
+ onBlur={ onBlur }
78
+ autofocus
79
+ expectedTag={ expectedTag }
80
+ wrapperClassName={ EDITOR_WRAPPER_SELECTOR }
81
+ onSelectionEnd={ onSelectionEnd }
82
+ />
83
+ { selectionOffsets && editor && (
84
+ <InlineEditingToolbarWrapper
85
+ expectedTag={ expectedTag }
86
+ editor={ editor }
87
+ rootElement={ rootElement }
88
+ id={ id }
89
+ selectionOffsets={ selectionOffsets }
90
+ />
91
+ ) }
92
+ </ThemeProvider>
93
+ );
94
+ };
95
+
96
+ const InlineEditingOverlay = ( {
97
+ expectedTag,
98
+ rootElement,
99
+ id,
100
+ }: {
101
+ expectedTag: string | null;
102
+ rootElement: HTMLElement;
103
+ id: string;
104
+ } ) => {
105
+ const inlineEditedElement = getInlineEditorElement( rootElement, expectedTag );
106
+ const [ overlayRefElement, setOverlayElement ] = useState< HTMLDivElement | null >( inlineEditedElement );
107
+
108
+ useEffect( () => {
109
+ setOverlayElement( getInlineEditorElement( rootElement, expectedTag ) );
110
+ }, [ expectedTag, rootElement ] );
111
+
112
+ return overlayRefElement ? <OutlineOverlay element={ overlayRefElement } id={ id } isSelected /> : null;
113
+ };
114
+
115
+ const InlineEditingToolbarWrapper = ( {
116
+ expectedTag,
117
+ editor,
118
+ rootElement,
119
+ id,
120
+ selectionOffsets,
121
+ }: {
122
+ expectedTag: string | null;
123
+ editor: Editor;
124
+ rootElement: HTMLElement;
125
+ id: string;
126
+ selectionOffsets: Offsets;
127
+ } ) => {
128
+ const [ element, setElement ] = useState< HTMLElement | null >( null );
129
+
130
+ useEffect( () => {
131
+ setElement( getInlineEditorElement( rootElement, expectedTag ) );
132
+ }, [ expectedTag, rootElement ] );
133
+
134
+ return element ? (
135
+ <InlineEditingToolbar element={ element } editor={ editor } id={ id } selectionOffsets={ selectionOffsets } />
136
+ ) : null;
137
+ };
138
+
139
+ const InlineEditingToolbar = ( {
140
+ element,
141
+ editor,
142
+ id,
143
+ selectionOffsets,
144
+ }: {
145
+ element: HTMLElement;
146
+ editor: Editor;
147
+ id: string;
148
+ selectionOffsets: Offsets;
149
+ } ) => {
150
+ const { floating } = useFloatingOnElement( {
151
+ element,
152
+ isSelected: true,
153
+ } );
154
+ const { getFloatingProps, getReferenceProps } = useInteractions();
155
+ const style = getComputedStyle( floating.styles, selectionOffsets );
156
+
157
+ useBindReactPropsToElement( element, getReferenceProps );
158
+
159
+ return (
160
+ <FloatingPortal id={ CANVAS_WRAPPER_ID }>
161
+ <Box
162
+ ref={ floating.setRef }
163
+ style={ {
164
+ ...floating.styles,
165
+ pointerEvents: 'none',
166
+ } }
167
+ role="presentation"
168
+ { ...getFloatingProps( { style } ) }
169
+ >
170
+ { floating.styles.transform && (
171
+ <Box
172
+ sx={ {
173
+ position: 'relative',
174
+ transform: 'translateY(-100%)',
175
+ height: 'max-content',
176
+ } }
177
+ >
178
+ <InlineEditorToolbar
179
+ editor={ editor }
180
+ elementId={ id }
181
+ sx={ {
182
+ transform: 'translateX(-50%)',
183
+ } }
184
+ />
185
+ </Box>
186
+ ) }
187
+ </Box>
188
+ </FloatingPortal>
189
+ );
190
+ };
191
+
192
+ const getInlineEditorElement = ( elementWrapper: HTMLElement, expectedTag: string | null ) => {
193
+ return ! expectedTag ? null : ( elementWrapper.querySelector( expectedTag ) as HTMLDivElement );
194
+ };
195
+
196
+ // Elements out of iframe and canvas don't trigger "onClickAway" which unmounts the editor
197
+ // since they are not part of the iframes owner document.
198
+ // We need to manually add listeners to these elements to unmount the editor when they are clicked.
199
+ const useOnClickOutsideIframe = ( handleUnmount: () => void ) => {
200
+ const asyncUnmountInlineEditor = React.useCallback( () => queueMicrotask( handleUnmount ), [ handleUnmount ] );
201
+
202
+ useEffect( () => {
203
+ EDITOR_ELEMENTS_OUT_OF_IFRAME.forEach(
204
+ ( selector ) =>
205
+ document?.querySelector( selector )?.addEventListener( 'mousedown', asyncUnmountInlineEditor )
206
+ );
207
+
208
+ return () =>
209
+ EDITOR_ELEMENTS_OUT_OF_IFRAME.forEach(
210
+ ( selector ) =>
211
+ document?.querySelector( selector )?.removeEventListener( 'mousedown', asyncUnmountInlineEditor )
212
+ );
213
+ // eslint-disable-next-line react-hooks/exhaustive-deps
214
+ }, [] );
215
+ };
@@ -1,17 +1,14 @@
1
1
  import * as React from 'react';
2
- import { useEffect, useRef, useState } from 'react';
3
2
  import { createRoot, type Root } from 'react-dom/client';
4
- import { InlineEditor } from '@elementor/editor-controls';
5
3
  import { getContainer, getElementLabel, getElementType } from '@elementor/editor-elements';
6
4
  import { htmlPropTypeUtil, type PropType, type PropValue, stringPropTypeUtil } from '@elementor/editor-props';
7
- import { __privateRunCommandSync as runCommandSync, undoable } from '@elementor/editor-v1-adapters';
8
- import { Box, ThemeProvider } from '@elementor/ui';
5
+ import { __privateRunCommandSync as runCommandSync, getCurrentEditMode, undoable } from '@elementor/editor-v1-adapters';
9
6
  import { __ } from '@wordpress/i18n';
10
7
 
11
- import { OutlineOverlay } from '../../../components/outline-overlay';
12
8
  import { ReplacementBase, TRIGGER_TIMING } from '../base';
9
+ import { CanvasInlineEditor } from './canvas-inline-editor';
13
10
  import { isInlineEditingAllowed } from './inline-editing-eligibility';
14
- import { getInitialPopoverPosition, INLINE_EDITING_PROPERTY_PER_TYPE } from './inline-editing-utils';
11
+ import { INLINE_EDITING_PROPERTY_PER_TYPE } from './inline-editing-utils';
15
12
 
16
13
  type TagPropType = PropType< 'tag' > & {
17
14
  settings?: {
@@ -21,13 +18,6 @@ type TagPropType = PropType< 'tag' > & {
21
18
 
22
19
  const HISTORY_DEBOUNCE_WAIT = 800;
23
20
 
24
- const TOP_BAR_SELECTOR = '#elementor-editor-wrapper-v2';
25
- const NAVIGATOR_SELECTOR = '#elementor-navigator';
26
- const V4_EDITING_PANEL = 'main.MuiBox-root';
27
- const V3_EDITING_PANEL = '#elementor-panel-content-wrapper';
28
-
29
- const BLUR_TRIGGERING_SELECTORS = [ TOP_BAR_SELECTOR, NAVIGATOR_SELECTOR, V4_EDITING_PANEL, V3_EDITING_PANEL ];
30
-
31
21
  export default class InlineEditingReplacement extends ReplacementBase {
32
22
  private inlineEditorRoot: Root | null = null;
33
23
  private handlerAttached = false;
@@ -45,7 +35,7 @@ export default class InlineEditingReplacement extends ReplacementBase {
45
35
  }
46
36
 
47
37
  shouldRenderReplacement() {
48
- return this.isInlineEditingEligible();
38
+ return this.isInlineEditingEligible() && getCurrentEditMode() === 'edit';
49
39
  }
50
40
 
51
41
  handleRenderInlineEditor = () => {
@@ -236,77 +226,23 @@ export default class InlineEditingReplacement extends ReplacementBase {
236
226
  this.resetInlineEditorRoot();
237
227
  }
238
228
 
239
- const InlineEditorApp = this.InlineEditorApp;
240
- const wrapperClasses = 'elementor';
241
229
  const elementClasses = this.element.children?.[ 0 ]?.classList.toString() ?? '';
230
+ const propValue = this.getExtractedContentValue();
231
+ const expectedTag = this.getExpectedTag();
242
232
 
243
233
  this.element.innerHTML = '';
244
234
 
245
235
  this.inlineEditorRoot = createRoot( this.element );
246
236
  this.inlineEditorRoot.render(
247
- <InlineEditorApp wrapperClasses={ wrapperClasses } elementClasses={ elementClasses } />
237
+ <CanvasInlineEditor
238
+ elementClasses={ elementClasses }
239
+ initialValue={ propValue }
240
+ expectedTag={ expectedTag }
241
+ rootElement={ this.element }
242
+ id={ this.id }
243
+ setValue={ this.setContentValue.bind( this ) }
244
+ onBlur={ this.unmountInlineEditor.bind( this ) }
245
+ />
248
246
  );
249
247
  }
250
-
251
- InlineEditorApp = ( { wrapperClasses, elementClasses }: { wrapperClasses: string; elementClasses: string } ) => {
252
- const propValue = this.getExtractedContentValue();
253
- const expectedTag = this.getExpectedTag();
254
- const wrapperRef = useRef< HTMLDivElement | null >( null );
255
- const [ isWrapperRendered, setIsWrapperRendered ] = useState( false );
256
-
257
- useEffect( () => {
258
- setIsWrapperRendered( !! wrapperRef.current );
259
- BLUR_TRIGGERING_SELECTORS.forEach(
260
- ( selector ) =>
261
- document?.querySelector( selector )?.addEventListener( 'mousedown', asyncUnmountInlineEditor )
262
- );
263
-
264
- return () =>
265
- BLUR_TRIGGERING_SELECTORS.forEach(
266
- ( selector ) =>
267
- document
268
- ?.querySelector( selector )
269
- ?.removeEventListener( 'mousedown', asyncUnmountInlineEditor )
270
- );
271
- // eslint-disable-next-line react-hooks/exhaustive-deps
272
- }, [] );
273
-
274
- const asyncUnmountInlineEditor = React.useCallback(
275
- () => queueMicrotask( this.unmountInlineEditor.bind( this ) ),
276
- []
277
- );
278
-
279
- return (
280
- <ThemeProvider>
281
- <Box
282
- ref={ wrapperRef }
283
- sx={ {
284
- '& .elementor-inline-editor-reset': {
285
- margin: 0,
286
- padding: 0,
287
- },
288
- } }
289
- >
290
- { isWrapperRendered && (
291
- <OutlineOverlay element={ wrapperRef.current as HTMLDivElement } id={ this.id } isSelected />
292
- ) }
293
- <InlineEditor
294
- attributes={ {
295
- class: wrapperClasses,
296
- style: 'outline: none;',
297
- } }
298
- elementClasses={ elementClasses }
299
- value={ propValue }
300
- setValue={ this.setContentValue.bind( this ) }
301
- onBlur={ this.unmountInlineEditor.bind( this ) }
302
- autofocus
303
- showToolbar
304
- getInitialPopoverPosition={ getInitialPopoverPosition }
305
- expectedTag={ expectedTag }
306
- elementId={ this.id }
307
- />
308
- </Box>
309
- </ThemeProvider>
310
- );
311
- };
312
248
  }
@@ -1,7 +1,17 @@
1
+ import { type CSSProperties } from 'react';
2
+ import { type InlineEditorToolbarProps } from '@elementor/editor-controls';
1
3
  import { type V1Element } from '@elementor/editor-elements';
2
4
 
3
5
  import { type LegacyWindow } from '../../types';
4
6
 
7
+ export type Editor = InlineEditorToolbarProps[ 'editor' ];
8
+ export type EditorView = Editor[ 'view' ];
9
+
10
+ export type Offsets = {
11
+ left: number;
12
+ top: number;
13
+ };
14
+
5
15
  export const INLINE_EDITING_PROPERTY_PER_TYPE: Record< string, string > = {
6
16
  'e-form-label': 'text',
7
17
  'e-heading': 'title',
@@ -14,19 +24,62 @@ export const getWidgetType = ( container: V1Element | null ) => {
14
24
  return container?.model?.get( 'widgetType' ) ?? container?.model?.get( 'elType' ) ?? null;
15
25
  };
16
26
 
17
- export const getInitialPopoverPosition = () => {
18
- const positionFallback = { left: 0, top: 0 };
27
+ export const calcSelectionCenterOffsets = ( view: EditorView ): Offsets | null => {
28
+ const frameWindow = ( view.root as Document )?.defaultView;
29
+ const selection = frameWindow?.getSelection();
30
+ const editorContainer = view.dom;
31
+
32
+ if ( ! selection || ! editorContainer ) {
33
+ return null;
34
+ }
35
+
36
+ const range = selection.getRangeAt( 0 );
37
+ const selectionRect = range.getBoundingClientRect();
38
+ const editorContainerRect = editorContainer.getBoundingClientRect();
39
+
40
+ if ( ! selectionRect || ! editorContainerRect ) {
41
+ return null;
42
+ }
43
+
44
+ const verticalOffset = selectionRect.top - editorContainerRect.top;
45
+
46
+ const selectionCenter = selectionRect?.left + selectionRect?.width / 2;
47
+ const horizontalOffset = selectionCenter - editorContainerRect.left;
48
+
49
+ return { left: horizontalOffset, top: verticalOffset };
50
+ };
51
+
52
+ export const getComputedStyle = ( styles: CSSProperties, offsets: Offsets ): CSSProperties => {
53
+ const transform = extractTransformValue( styles );
54
+
55
+ return transform
56
+ ? {
57
+ ...styles,
58
+ marginLeft: `${ offsets.left }px`,
59
+ marginTop: `${ offsets.top }px`,
60
+ pointerEvents: 'none',
61
+ }
62
+ : {
63
+ display: 'none',
64
+ };
65
+ };
66
+
67
+ const extractTransformValue = ( styles: CSSProperties ) => {
68
+ const translateRegex = /translate\([^)]*\)\s?/g;
69
+ const numericValuesRegex = /(-?\d+\.?\d*)/g;
70
+
71
+ const translateValue = styles?.transform?.match( translateRegex )?.[ 0 ];
72
+ const values = translateValue?.match( numericValuesRegex );
73
+
74
+ if ( ! translateValue || ! values ) {
75
+ return null;
76
+ }
19
77
 
20
- const iFrameElement = legacyWindow?.elementor?.$preview?.get( 0 );
21
- const iFramePosition = iFrameElement?.getBoundingClientRect() ?? positionFallback;
78
+ const [ numericX, numericY ] = values.map( Number );
22
79
 
23
- const previewElement = legacyWindow?.elementor?.$previewWrapper?.get( 0 );
24
- const previewPosition = previewElement
25
- ? { left: previewElement.scrollLeft, top: previewElement.scrollTop }
26
- : positionFallback;
80
+ if ( ! numericX || ! numericY ) {
81
+ return null;
82
+ }
27
83
 
28
- return {
29
- left: iFramePosition.left + previewPosition.left,
30
- top: iFramePosition.top + previewPosition.top,
31
- };
84
+ return styles.transform;
32
85
  };