@elementor/editor-canvas 3.35.5 → 3.35.6

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
@@ -1767,66 +1767,129 @@ var ReplacementBase = class {
1767
1767
 
1768
1768
  // src/legacy/replacements/inline-editing/canvas-inline-editor.tsx
1769
1769
  var React5 = __toESM(require("react"));
1770
- var import_react11 = require("react");
1770
+ var import_react12 = require("react");
1771
1771
  var import_editor_controls2 = require("@elementor/editor-controls");
1772
1772
  var import_ui4 = require("@elementor/ui");
1773
- var import_react12 = require("@floating-ui/react");
1773
+ var import_react13 = require("@floating-ui/react");
1774
1774
 
1775
1775
  // src/legacy/replacements/inline-editing/inline-editing-utils.ts
1776
+ var import_react11 = require("react");
1777
+ var TOP_BAR_SELECTOR = "#elementor-editor-wrapper-v2";
1778
+ var NAVIGATOR_SELECTOR = "#elementor-navigator";
1779
+ var EDITING_PANEL = "#elementor-panel";
1780
+ var EDITOR_ELEMENTS_OUT_OF_IFRAME = [TOP_BAR_SELECTOR, NAVIGATOR_SELECTOR, EDITING_PANEL];
1781
+ var TOOLBAR_ANCHOR_ID_PREFIX = "inline-editing-toolbar-anchor";
1782
+ var TOOLBAR_ANCHOR_STATIC_STYLES = {
1783
+ backgroundColor: "transparent",
1784
+ border: "none",
1785
+ outline: "none",
1786
+ boxShadow: "none",
1787
+ padding: "0",
1788
+ margin: "0",
1789
+ borderRadius: "0",
1790
+ overflow: "hidden",
1791
+ opacity: "0",
1792
+ pointerEvents: "none",
1793
+ position: "absolute",
1794
+ display: "block"
1795
+ };
1776
1796
  var INLINE_EDITING_PROPERTY_PER_TYPE = {
1777
1797
  "e-form-label": "text",
1778
1798
  "e-heading": "title",
1779
1799
  "e-paragraph": "paragraph"
1780
1800
  };
1781
- var calcSelectionCenterOffsets = (view) => {
1782
- const frameWindow = view.root?.defaultView;
1801
+ var getInlineEditorElement = (elementWrapper, expectedTag) => {
1802
+ return !expectedTag ? null : elementWrapper.querySelector(expectedTag);
1803
+ };
1804
+ var useOnClickOutsideIframe = (handleUnmount) => {
1805
+ const asyncUnmountInlineEditor = (0, import_react11.useCallback)(() => queueMicrotask(handleUnmount), [handleUnmount]);
1806
+ (0, import_react11.useEffect)(() => {
1807
+ EDITOR_ELEMENTS_OUT_OF_IFRAME.forEach(
1808
+ (selector) => document?.querySelector(selector)?.addEventListener("mousedown", asyncUnmountInlineEditor)
1809
+ );
1810
+ return () => EDITOR_ELEMENTS_OUT_OF_IFRAME.forEach(
1811
+ (selector) => document?.querySelector(selector)?.removeEventListener("mousedown", asyncUnmountInlineEditor)
1812
+ );
1813
+ }, []);
1814
+ };
1815
+ var useRenderToolbar = (ownerDocument, id) => {
1816
+ const [anchor, setAnchor] = (0, import_react11.useState)(null);
1817
+ const onSelectionEnd = (view) => {
1818
+ const hasSelection = !view.state.selection.empty;
1819
+ removeToolbarAnchor(ownerDocument, id);
1820
+ if (hasSelection) {
1821
+ setAnchor(createAnchorBasedOnSelection(ownerDocument, id));
1822
+ } else {
1823
+ setAnchor(null);
1824
+ }
1825
+ };
1826
+ return { onSelectionEnd, anchor };
1827
+ };
1828
+ var createAnchorBasedOnSelection = (ownerDocument, id) => {
1829
+ const frameWindow = ownerDocument.defaultView;
1783
1830
  const selection = frameWindow?.getSelection();
1784
- const editorContainer = view.dom;
1785
- if (!selection || !editorContainer) {
1831
+ if (!selection) {
1786
1832
  return null;
1787
1833
  }
1788
1834
  const range = selection.getRangeAt(0);
1789
1835
  const selectionRect = range.getBoundingClientRect();
1790
- const editorContainerRect = editorContainer.getBoundingClientRect();
1791
- if (!selectionRect || !editorContainerRect) {
1792
- return null;
1793
- }
1794
- const verticalOffset = selectionRect.top - editorContainerRect.top;
1795
- const selectionCenter = selectionRect?.left + selectionRect?.width / 2;
1796
- const horizontalOffset = selectionCenter - editorContainerRect.left;
1797
- return { left: horizontalOffset, top: verticalOffset };
1798
- };
1799
- var getComputedStyle = (styles, offsets) => {
1800
- const transform = extractTransformValue(styles);
1801
- return transform ? {
1802
- ...styles,
1803
- marginLeft: `${offsets.left}px`,
1804
- marginTop: `${offsets.top}px`,
1805
- pointerEvents: "none"
1806
- } : {
1807
- display: "none"
1808
- };
1836
+ const bodyRect = ownerDocument.body.getBoundingClientRect();
1837
+ const toolbarAnchor = ownerDocument.createElement("span");
1838
+ styleToolbarAnchor(toolbarAnchor, selectionRect, bodyRect);
1839
+ toolbarAnchor.setAttribute("id", getToolbarAnchorId(id));
1840
+ ownerDocument.body.appendChild(toolbarAnchor);
1841
+ return toolbarAnchor;
1842
+ };
1843
+ var removeToolbarAnchor = (ownerDocument, id) => {
1844
+ const toolbarAnchor = getToolbarAnchor(ownerDocument, id);
1845
+ if (toolbarAnchor) {
1846
+ ownerDocument.body.removeChild(toolbarAnchor);
1847
+ }
1848
+ };
1849
+ var getToolbarAnchorId = (id) => `${TOOLBAR_ANCHOR_ID_PREFIX}-${id}`;
1850
+ var getToolbarAnchor = (ownerDocument, id) => ownerDocument.getElementById(getToolbarAnchorId(id));
1851
+ var styleToolbarAnchor = (anchor, selectionRect, bodyRect) => {
1852
+ const { width, height } = selectionRect;
1853
+ Object.assign(anchor.style, {
1854
+ ...TOOLBAR_ANCHOR_STATIC_STYLES,
1855
+ top: `${selectionRect.top - bodyRect.top}px`,
1856
+ left: `${selectionRect.left - bodyRect.left}px`,
1857
+ width: `${width}px`,
1858
+ height: `${height}px`
1859
+ });
1809
1860
  };
1810
- var extractTransformValue = (styles) => {
1811
- const translateRegex = /translate\([^)]*\)\s?/g;
1812
- const numericValuesRegex = /(-?\d+\.?\d*)/g;
1813
- const translateValue = styles?.transform?.match(translateRegex)?.[0];
1814
- const values = translateValue?.match(numericValuesRegex);
1815
- if (!translateValue || !values) {
1816
- return null;
1817
- }
1818
- const [numericX, numericY] = values.map(Number);
1819
- if (!numericX || !numericY) {
1820
- return null;
1861
+ var horizontalShifterMiddleware = {
1862
+ name: "horizontalShifter",
1863
+ fn(state) {
1864
+ const {
1865
+ x: left,
1866
+ y: top,
1867
+ elements: { reference: anchor, floating }
1868
+ } = state;
1869
+ const newState = {
1870
+ ...state,
1871
+ x: left,
1872
+ y: top
1873
+ };
1874
+ const isLeftOverflown = left < 0;
1875
+ if (isLeftOverflown) {
1876
+ newState.x = 0;
1877
+ return newState;
1878
+ }
1879
+ const anchorRect = anchor.getBoundingClientRect();
1880
+ const right = left + floating.offsetWidth;
1881
+ const documentWidth = anchor.ownerDocument.body.offsetWidth;
1882
+ const isRightOverflown = right > documentWidth && anchorRect.right < right;
1883
+ if (isRightOverflown) {
1884
+ const diff = right - documentWidth;
1885
+ newState.x = left - diff;
1886
+ return newState;
1887
+ }
1888
+ return newState;
1821
1889
  }
1822
- return styles.transform;
1823
1890
  };
1824
1891
 
1825
1892
  // src/legacy/replacements/inline-editing/canvas-inline-editor.tsx
1826
- var TOP_BAR_SELECTOR = "#elementor-editor-wrapper-v2";
1827
- var NAVIGATOR_SELECTOR = "#elementor-navigator";
1828
- var EDITING_PANEL = "#elementor-panel";
1829
- var EDITOR_ELEMENTS_OUT_OF_IFRAME = [TOP_BAR_SELECTOR, NAVIGATOR_SELECTOR, EDITING_PANEL];
1830
1893
  var EDITOR_WRAPPER_SELECTOR = "inline-editor-wrapper";
1831
1894
  var CanvasInlineEditor = ({
1832
1895
  elementClasses,
@@ -1835,19 +1898,23 @@ var CanvasInlineEditor = ({
1835
1898
  rootElement,
1836
1899
  id,
1837
1900
  setValue,
1838
- onBlur
1901
+ ...props
1839
1902
  }) => {
1840
- const [selectionOffsets, setSelectionOffsets] = (0, import_react11.useState)(null);
1841
- const [editor, setEditor] = (0, import_react11.useState)(null);
1842
- const onSelectionEnd = (view) => {
1843
- const hasSelection = !view.state.selection.empty;
1844
- setSelectionOffsets(hasSelection ? calcSelectionCenterOffsets(view) : null);
1903
+ const [editor, setEditor] = (0, import_react12.useState)(null);
1904
+ const { onSelectionEnd, anchor: toolbarAnchor } = useRenderToolbar(rootElement.ownerDocument, id);
1905
+ const onBlur = () => {
1906
+ removeToolbarAnchor(rootElement.ownerDocument, id);
1907
+ props.onBlur();
1845
1908
  };
1846
1909
  useOnClickOutsideIframe(onBlur);
1847
1910
  return /* @__PURE__ */ React5.createElement(import_ui4.ThemeProvider, null, /* @__PURE__ */ React5.createElement(InlineEditingOverlay, { expectedTag, rootElement, id }), /* @__PURE__ */ React5.createElement("style", null, `
1848
1911
  .ProseMirror > * {
1849
1912
  height: 100%;
1850
1913
  }
1914
+ .${EDITOR_WRAPPER_SELECTOR} .ProseMirror > button[contenteditable="true"] {
1915
+ height: auto;
1916
+ cursor: text;
1917
+ }
1851
1918
  `), /* @__PURE__ */ React5.createElement(
1852
1919
  import_editor_controls2.InlineEditor,
1853
1920
  {
@@ -1863,19 +1930,9 @@ var CanvasInlineEditor = ({
1863
1930
  onBlur,
1864
1931
  autofocus: true,
1865
1932
  expectedTag,
1866
- wrapperClassName: EDITOR_WRAPPER_SELECTOR,
1867
1933
  onSelectionEnd
1868
1934
  }
1869
- ), selectionOffsets && editor && /* @__PURE__ */ React5.createElement(
1870
- InlineEditingToolbarWrapper,
1871
- {
1872
- expectedTag,
1873
- editor,
1874
- rootElement,
1875
- id,
1876
- selectionOffsets
1877
- }
1878
- ));
1935
+ ), toolbarAnchor && editor && /* @__PURE__ */ React5.createElement(InlineEditingToolbar, { anchor: toolbarAnchor, editor, id }));
1879
1936
  };
1880
1937
  var InlineEditingOverlay = ({
1881
1938
  expectedTag,
@@ -1883,84 +1940,25 @@ var InlineEditingOverlay = ({
1883
1940
  id
1884
1941
  }) => {
1885
1942
  const inlineEditedElement = getInlineEditorElement(rootElement, expectedTag);
1886
- const [overlayRefElement, setOverlayElement] = (0, import_react11.useState)(inlineEditedElement);
1887
- (0, import_react11.useEffect)(() => {
1943
+ const [overlayRefElement, setOverlayElement] = (0, import_react12.useState)(inlineEditedElement);
1944
+ (0, import_react12.useEffect)(() => {
1888
1945
  setOverlayElement(getInlineEditorElement(rootElement, expectedTag));
1889
1946
  }, [expectedTag, rootElement]);
1890
1947
  return overlayRefElement ? /* @__PURE__ */ React5.createElement(OutlineOverlay, { element: overlayRefElement, id, isSelected: true }) : null;
1891
1948
  };
1892
- var InlineEditingToolbarWrapper = ({
1893
- expectedTag,
1894
- editor,
1895
- rootElement,
1896
- id,
1897
- selectionOffsets
1898
- }) => {
1899
- const [element, setElement] = (0, import_react11.useState)(null);
1900
- (0, import_react11.useEffect)(() => {
1901
- setElement(getInlineEditorElement(rootElement, expectedTag));
1902
- }, [expectedTag, rootElement]);
1903
- return element ? /* @__PURE__ */ React5.createElement(InlineEditingToolbar, { element, editor, id, selectionOffsets }) : null;
1904
- };
1905
- var InlineEditingToolbar = ({
1906
- element,
1907
- editor,
1908
- id,
1909
- selectionOffsets
1910
- }) => {
1911
- const { floating } = useFloatingOnElement({
1912
- element,
1913
- isSelected: true
1949
+ var InlineEditingToolbar = ({ anchor, editor, id }) => {
1950
+ const { refs, floatingStyles } = (0, import_react13.useFloating)({
1951
+ placement: "top",
1952
+ strategy: "fixed",
1953
+ transform: false,
1954
+ whileElementsMounted: import_react13.autoUpdate,
1955
+ middleware: [horizontalShifterMiddleware, (0, import_react13.flip)()]
1914
1956
  });
1915
- const { getFloatingProps, getReferenceProps } = (0, import_react12.useInteractions)();
1916
- const style = getComputedStyle(floating.styles, selectionOffsets);
1917
- useBindReactPropsToElement(element, getReferenceProps);
1918
- return /* @__PURE__ */ React5.createElement(import_react12.FloatingPortal, { id: CANVAS_WRAPPER_ID }, /* @__PURE__ */ React5.createElement(
1919
- import_ui4.Box,
1920
- {
1921
- ref: floating.setRef,
1922
- style: {
1923
- ...floating.styles,
1924
- pointerEvents: "none"
1925
- },
1926
- role: "presentation",
1927
- ...getFloatingProps({ style })
1928
- },
1929
- floating.styles.transform && /* @__PURE__ */ React5.createElement(
1930
- import_ui4.Box,
1931
- {
1932
- sx: {
1933
- position: "relative",
1934
- transform: "translateY(-100%)",
1935
- height: "max-content"
1936
- }
1937
- },
1938
- /* @__PURE__ */ React5.createElement(
1939
- import_editor_controls2.InlineEditorToolbar,
1940
- {
1941
- editor,
1942
- elementId: id,
1943
- sx: {
1944
- transform: "translateX(-50%)"
1945
- }
1946
- }
1947
- )
1948
- )
1949
- ));
1950
- };
1951
- var getInlineEditorElement = (elementWrapper, expectedTag) => {
1952
- return !expectedTag ? null : elementWrapper.querySelector(expectedTag);
1953
- };
1954
- var useOnClickOutsideIframe = (handleUnmount) => {
1955
- const asyncUnmountInlineEditor = React5.useCallback(() => queueMicrotask(handleUnmount), [handleUnmount]);
1956
- (0, import_react11.useEffect)(() => {
1957
- EDITOR_ELEMENTS_OUT_OF_IFRAME.forEach(
1958
- (selector) => document?.querySelector(selector)?.addEventListener("mousedown", asyncUnmountInlineEditor)
1959
- );
1960
- return () => EDITOR_ELEMENTS_OUT_OF_IFRAME.forEach(
1961
- (selector) => document?.querySelector(selector)?.removeEventListener("mousedown", asyncUnmountInlineEditor)
1962
- );
1963
- }, []);
1957
+ (0, import_react12.useLayoutEffect)(() => {
1958
+ refs.setReference(anchor);
1959
+ return () => refs.setReference(null);
1960
+ }, [anchor, refs]);
1961
+ return /* @__PURE__ */ React5.createElement(import_react13.FloatingPortal, { id: CANVAS_WRAPPER_ID }, /* @__PURE__ */ React5.createElement(import_ui4.Box, { ref: refs.setFloating, role: "presentation", style: { ...floatingStyles, pointerEvents: "none" } }, /* @__PURE__ */ React5.createElement(import_editor_controls2.InlineEditorToolbar, { editor, elementId: id })));
1964
1962
  };
1965
1963
 
1966
1964
  // src/legacy/replacements/inline-editing/inline-editing-eligibility.ts
package/dist/index.mjs CHANGED
@@ -1738,66 +1738,129 @@ var ReplacementBase = class {
1738
1738
 
1739
1739
  // src/legacy/replacements/inline-editing/canvas-inline-editor.tsx
1740
1740
  import * as React5 from "react";
1741
- import { useEffect as useEffect7, useState as useState4 } from "react";
1741
+ import { useEffect as useEffect8, useLayoutEffect, useState as useState5 } from "react";
1742
1742
  import { InlineEditor, InlineEditorToolbar } from "@elementor/editor-controls";
1743
1743
  import { Box as Box2, ThemeProvider } from "@elementor/ui";
1744
- import { FloatingPortal as FloatingPortal2, useInteractions as useInteractions2 } from "@floating-ui/react";
1744
+ import { autoUpdate as autoUpdate2, flip, FloatingPortal as FloatingPortal2, useFloating as useFloating2 } from "@floating-ui/react";
1745
1745
 
1746
1746
  // src/legacy/replacements/inline-editing/inline-editing-utils.ts
1747
+ import { useCallback, useEffect as useEffect7, useState as useState4 } from "react";
1748
+ var TOP_BAR_SELECTOR = "#elementor-editor-wrapper-v2";
1749
+ var NAVIGATOR_SELECTOR = "#elementor-navigator";
1750
+ var EDITING_PANEL = "#elementor-panel";
1751
+ var EDITOR_ELEMENTS_OUT_OF_IFRAME = [TOP_BAR_SELECTOR, NAVIGATOR_SELECTOR, EDITING_PANEL];
1752
+ var TOOLBAR_ANCHOR_ID_PREFIX = "inline-editing-toolbar-anchor";
1753
+ var TOOLBAR_ANCHOR_STATIC_STYLES = {
1754
+ backgroundColor: "transparent",
1755
+ border: "none",
1756
+ outline: "none",
1757
+ boxShadow: "none",
1758
+ padding: "0",
1759
+ margin: "0",
1760
+ borderRadius: "0",
1761
+ overflow: "hidden",
1762
+ opacity: "0",
1763
+ pointerEvents: "none",
1764
+ position: "absolute",
1765
+ display: "block"
1766
+ };
1747
1767
  var INLINE_EDITING_PROPERTY_PER_TYPE = {
1748
1768
  "e-form-label": "text",
1749
1769
  "e-heading": "title",
1750
1770
  "e-paragraph": "paragraph"
1751
1771
  };
1752
- var calcSelectionCenterOffsets = (view) => {
1753
- const frameWindow = view.root?.defaultView;
1772
+ var getInlineEditorElement = (elementWrapper, expectedTag) => {
1773
+ return !expectedTag ? null : elementWrapper.querySelector(expectedTag);
1774
+ };
1775
+ var useOnClickOutsideIframe = (handleUnmount) => {
1776
+ const asyncUnmountInlineEditor = useCallback(() => queueMicrotask(handleUnmount), [handleUnmount]);
1777
+ useEffect7(() => {
1778
+ EDITOR_ELEMENTS_OUT_OF_IFRAME.forEach(
1779
+ (selector) => document?.querySelector(selector)?.addEventListener("mousedown", asyncUnmountInlineEditor)
1780
+ );
1781
+ return () => EDITOR_ELEMENTS_OUT_OF_IFRAME.forEach(
1782
+ (selector) => document?.querySelector(selector)?.removeEventListener("mousedown", asyncUnmountInlineEditor)
1783
+ );
1784
+ }, []);
1785
+ };
1786
+ var useRenderToolbar = (ownerDocument, id) => {
1787
+ const [anchor, setAnchor] = useState4(null);
1788
+ const onSelectionEnd = (view) => {
1789
+ const hasSelection = !view.state.selection.empty;
1790
+ removeToolbarAnchor(ownerDocument, id);
1791
+ if (hasSelection) {
1792
+ setAnchor(createAnchorBasedOnSelection(ownerDocument, id));
1793
+ } else {
1794
+ setAnchor(null);
1795
+ }
1796
+ };
1797
+ return { onSelectionEnd, anchor };
1798
+ };
1799
+ var createAnchorBasedOnSelection = (ownerDocument, id) => {
1800
+ const frameWindow = ownerDocument.defaultView;
1754
1801
  const selection = frameWindow?.getSelection();
1755
- const editorContainer = view.dom;
1756
- if (!selection || !editorContainer) {
1802
+ if (!selection) {
1757
1803
  return null;
1758
1804
  }
1759
1805
  const range = selection.getRangeAt(0);
1760
1806
  const selectionRect = range.getBoundingClientRect();
1761
- const editorContainerRect = editorContainer.getBoundingClientRect();
1762
- if (!selectionRect || !editorContainerRect) {
1763
- return null;
1764
- }
1765
- const verticalOffset = selectionRect.top - editorContainerRect.top;
1766
- const selectionCenter = selectionRect?.left + selectionRect?.width / 2;
1767
- const horizontalOffset = selectionCenter - editorContainerRect.left;
1768
- return { left: horizontalOffset, top: verticalOffset };
1769
- };
1770
- var getComputedStyle = (styles, offsets) => {
1771
- const transform = extractTransformValue(styles);
1772
- return transform ? {
1773
- ...styles,
1774
- marginLeft: `${offsets.left}px`,
1775
- marginTop: `${offsets.top}px`,
1776
- pointerEvents: "none"
1777
- } : {
1778
- display: "none"
1779
- };
1807
+ const bodyRect = ownerDocument.body.getBoundingClientRect();
1808
+ const toolbarAnchor = ownerDocument.createElement("span");
1809
+ styleToolbarAnchor(toolbarAnchor, selectionRect, bodyRect);
1810
+ toolbarAnchor.setAttribute("id", getToolbarAnchorId(id));
1811
+ ownerDocument.body.appendChild(toolbarAnchor);
1812
+ return toolbarAnchor;
1813
+ };
1814
+ var removeToolbarAnchor = (ownerDocument, id) => {
1815
+ const toolbarAnchor = getToolbarAnchor(ownerDocument, id);
1816
+ if (toolbarAnchor) {
1817
+ ownerDocument.body.removeChild(toolbarAnchor);
1818
+ }
1819
+ };
1820
+ var getToolbarAnchorId = (id) => `${TOOLBAR_ANCHOR_ID_PREFIX}-${id}`;
1821
+ var getToolbarAnchor = (ownerDocument, id) => ownerDocument.getElementById(getToolbarAnchorId(id));
1822
+ var styleToolbarAnchor = (anchor, selectionRect, bodyRect) => {
1823
+ const { width, height } = selectionRect;
1824
+ Object.assign(anchor.style, {
1825
+ ...TOOLBAR_ANCHOR_STATIC_STYLES,
1826
+ top: `${selectionRect.top - bodyRect.top}px`,
1827
+ left: `${selectionRect.left - bodyRect.left}px`,
1828
+ width: `${width}px`,
1829
+ height: `${height}px`
1830
+ });
1780
1831
  };
1781
- var extractTransformValue = (styles) => {
1782
- const translateRegex = /translate\([^)]*\)\s?/g;
1783
- const numericValuesRegex = /(-?\d+\.?\d*)/g;
1784
- const translateValue = styles?.transform?.match(translateRegex)?.[0];
1785
- const values = translateValue?.match(numericValuesRegex);
1786
- if (!translateValue || !values) {
1787
- return null;
1788
- }
1789
- const [numericX, numericY] = values.map(Number);
1790
- if (!numericX || !numericY) {
1791
- return null;
1832
+ var horizontalShifterMiddleware = {
1833
+ name: "horizontalShifter",
1834
+ fn(state) {
1835
+ const {
1836
+ x: left,
1837
+ y: top,
1838
+ elements: { reference: anchor, floating }
1839
+ } = state;
1840
+ const newState = {
1841
+ ...state,
1842
+ x: left,
1843
+ y: top
1844
+ };
1845
+ const isLeftOverflown = left < 0;
1846
+ if (isLeftOverflown) {
1847
+ newState.x = 0;
1848
+ return newState;
1849
+ }
1850
+ const anchorRect = anchor.getBoundingClientRect();
1851
+ const right = left + floating.offsetWidth;
1852
+ const documentWidth = anchor.ownerDocument.body.offsetWidth;
1853
+ const isRightOverflown = right > documentWidth && anchorRect.right < right;
1854
+ if (isRightOverflown) {
1855
+ const diff = right - documentWidth;
1856
+ newState.x = left - diff;
1857
+ return newState;
1858
+ }
1859
+ return newState;
1792
1860
  }
1793
- return styles.transform;
1794
1861
  };
1795
1862
 
1796
1863
  // src/legacy/replacements/inline-editing/canvas-inline-editor.tsx
1797
- var TOP_BAR_SELECTOR = "#elementor-editor-wrapper-v2";
1798
- var NAVIGATOR_SELECTOR = "#elementor-navigator";
1799
- var EDITING_PANEL = "#elementor-panel";
1800
- var EDITOR_ELEMENTS_OUT_OF_IFRAME = [TOP_BAR_SELECTOR, NAVIGATOR_SELECTOR, EDITING_PANEL];
1801
1864
  var EDITOR_WRAPPER_SELECTOR = "inline-editor-wrapper";
1802
1865
  var CanvasInlineEditor = ({
1803
1866
  elementClasses,
@@ -1806,19 +1869,23 @@ var CanvasInlineEditor = ({
1806
1869
  rootElement,
1807
1870
  id,
1808
1871
  setValue,
1809
- onBlur
1872
+ ...props
1810
1873
  }) => {
1811
- const [selectionOffsets, setSelectionOffsets] = useState4(null);
1812
- const [editor, setEditor] = useState4(null);
1813
- const onSelectionEnd = (view) => {
1814
- const hasSelection = !view.state.selection.empty;
1815
- setSelectionOffsets(hasSelection ? calcSelectionCenterOffsets(view) : null);
1874
+ const [editor, setEditor] = useState5(null);
1875
+ const { onSelectionEnd, anchor: toolbarAnchor } = useRenderToolbar(rootElement.ownerDocument, id);
1876
+ const onBlur = () => {
1877
+ removeToolbarAnchor(rootElement.ownerDocument, id);
1878
+ props.onBlur();
1816
1879
  };
1817
1880
  useOnClickOutsideIframe(onBlur);
1818
1881
  return /* @__PURE__ */ React5.createElement(ThemeProvider, null, /* @__PURE__ */ React5.createElement(InlineEditingOverlay, { expectedTag, rootElement, id }), /* @__PURE__ */ React5.createElement("style", null, `
1819
1882
  .ProseMirror > * {
1820
1883
  height: 100%;
1821
1884
  }
1885
+ .${EDITOR_WRAPPER_SELECTOR} .ProseMirror > button[contenteditable="true"] {
1886
+ height: auto;
1887
+ cursor: text;
1888
+ }
1822
1889
  `), /* @__PURE__ */ React5.createElement(
1823
1890
  InlineEditor,
1824
1891
  {
@@ -1834,19 +1901,9 @@ var CanvasInlineEditor = ({
1834
1901
  onBlur,
1835
1902
  autofocus: true,
1836
1903
  expectedTag,
1837
- wrapperClassName: EDITOR_WRAPPER_SELECTOR,
1838
1904
  onSelectionEnd
1839
1905
  }
1840
- ), selectionOffsets && editor && /* @__PURE__ */ React5.createElement(
1841
- InlineEditingToolbarWrapper,
1842
- {
1843
- expectedTag,
1844
- editor,
1845
- rootElement,
1846
- id,
1847
- selectionOffsets
1848
- }
1849
- ));
1906
+ ), toolbarAnchor && editor && /* @__PURE__ */ React5.createElement(InlineEditingToolbar, { anchor: toolbarAnchor, editor, id }));
1850
1907
  };
1851
1908
  var InlineEditingOverlay = ({
1852
1909
  expectedTag,
@@ -1854,84 +1911,25 @@ var InlineEditingOverlay = ({
1854
1911
  id
1855
1912
  }) => {
1856
1913
  const inlineEditedElement = getInlineEditorElement(rootElement, expectedTag);
1857
- const [overlayRefElement, setOverlayElement] = useState4(inlineEditedElement);
1858
- useEffect7(() => {
1914
+ const [overlayRefElement, setOverlayElement] = useState5(inlineEditedElement);
1915
+ useEffect8(() => {
1859
1916
  setOverlayElement(getInlineEditorElement(rootElement, expectedTag));
1860
1917
  }, [expectedTag, rootElement]);
1861
1918
  return overlayRefElement ? /* @__PURE__ */ React5.createElement(OutlineOverlay, { element: overlayRefElement, id, isSelected: true }) : null;
1862
1919
  };
1863
- var InlineEditingToolbarWrapper = ({
1864
- expectedTag,
1865
- editor,
1866
- rootElement,
1867
- id,
1868
- selectionOffsets
1869
- }) => {
1870
- const [element, setElement] = useState4(null);
1871
- useEffect7(() => {
1872
- setElement(getInlineEditorElement(rootElement, expectedTag));
1873
- }, [expectedTag, rootElement]);
1874
- return element ? /* @__PURE__ */ React5.createElement(InlineEditingToolbar, { element, editor, id, selectionOffsets }) : null;
1875
- };
1876
- var InlineEditingToolbar = ({
1877
- element,
1878
- editor,
1879
- id,
1880
- selectionOffsets
1881
- }) => {
1882
- const { floating } = useFloatingOnElement({
1883
- element,
1884
- isSelected: true
1920
+ var InlineEditingToolbar = ({ anchor, editor, id }) => {
1921
+ const { refs, floatingStyles } = useFloating2({
1922
+ placement: "top",
1923
+ strategy: "fixed",
1924
+ transform: false,
1925
+ whileElementsMounted: autoUpdate2,
1926
+ middleware: [horizontalShifterMiddleware, flip()]
1885
1927
  });
1886
- const { getFloatingProps, getReferenceProps } = useInteractions2();
1887
- const style = getComputedStyle(floating.styles, selectionOffsets);
1888
- useBindReactPropsToElement(element, getReferenceProps);
1889
- return /* @__PURE__ */ React5.createElement(FloatingPortal2, { id: CANVAS_WRAPPER_ID }, /* @__PURE__ */ React5.createElement(
1890
- Box2,
1891
- {
1892
- ref: floating.setRef,
1893
- style: {
1894
- ...floating.styles,
1895
- pointerEvents: "none"
1896
- },
1897
- role: "presentation",
1898
- ...getFloatingProps({ style })
1899
- },
1900
- floating.styles.transform && /* @__PURE__ */ React5.createElement(
1901
- Box2,
1902
- {
1903
- sx: {
1904
- position: "relative",
1905
- transform: "translateY(-100%)",
1906
- height: "max-content"
1907
- }
1908
- },
1909
- /* @__PURE__ */ React5.createElement(
1910
- InlineEditorToolbar,
1911
- {
1912
- editor,
1913
- elementId: id,
1914
- sx: {
1915
- transform: "translateX(-50%)"
1916
- }
1917
- }
1918
- )
1919
- )
1920
- ));
1921
- };
1922
- var getInlineEditorElement = (elementWrapper, expectedTag) => {
1923
- return !expectedTag ? null : elementWrapper.querySelector(expectedTag);
1924
- };
1925
- var useOnClickOutsideIframe = (handleUnmount) => {
1926
- const asyncUnmountInlineEditor = React5.useCallback(() => queueMicrotask(handleUnmount), [handleUnmount]);
1927
- useEffect7(() => {
1928
- EDITOR_ELEMENTS_OUT_OF_IFRAME.forEach(
1929
- (selector) => document?.querySelector(selector)?.addEventListener("mousedown", asyncUnmountInlineEditor)
1930
- );
1931
- return () => EDITOR_ELEMENTS_OUT_OF_IFRAME.forEach(
1932
- (selector) => document?.querySelector(selector)?.removeEventListener("mousedown", asyncUnmountInlineEditor)
1933
- );
1934
- }, []);
1928
+ useLayoutEffect(() => {
1929
+ refs.setReference(anchor);
1930
+ return () => refs.setReference(null);
1931
+ }, [anchor, refs]);
1932
+ return /* @__PURE__ */ React5.createElement(FloatingPortal2, { id: CANVAS_WRAPPER_ID }, /* @__PURE__ */ React5.createElement(Box2, { ref: refs.setFloating, role: "presentation", style: { ...floatingStyles, pointerEvents: "none" } }, /* @__PURE__ */ React5.createElement(InlineEditorToolbar, { editor, elementId: id })));
1935
1933
  };
1936
1934
 
1937
1935
  // src/legacy/replacements/inline-editing/inline-editing-eligibility.ts
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@elementor/editor-canvas",
3
3
  "description": "Elementor Editor Canvas",
4
- "version": "3.35.5",
4
+ "version": "3.35.6",
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": "3.35.5",
41
- "@elementor/editor-controls": "3.35.5",
42
- "@elementor/editor-documents": "3.35.5",
43
- "@elementor/editor-elements": "3.35.5",
44
- "@elementor/editor-interactions": "3.35.5",
45
- "@elementor/editor-mcp": "3.35.5",
46
- "@elementor/editor-notifications": "3.35.5",
47
- "@elementor/editor-props": "3.35.5",
48
- "@elementor/editor-responsive": "3.35.5",
49
- "@elementor/editor-styles": "3.35.5",
50
- "@elementor/editor-styles-repository": "3.35.5",
51
- "@elementor/editor-ui": "3.35.5",
52
- "@elementor/editor-v1-adapters": "3.35.5",
53
- "@elementor/schema": "3.35.5",
54
- "@elementor/twing": "3.35.5",
40
+ "@elementor/editor": "3.35.6",
41
+ "@elementor/editor-controls": "3.35.6",
42
+ "@elementor/editor-documents": "3.35.6",
43
+ "@elementor/editor-elements": "3.35.6",
44
+ "@elementor/editor-interactions": "3.35.6",
45
+ "@elementor/editor-mcp": "3.35.6",
46
+ "@elementor/editor-notifications": "3.35.6",
47
+ "@elementor/editor-props": "3.35.6",
48
+ "@elementor/editor-responsive": "3.35.6",
49
+ "@elementor/editor-styles": "3.35.6",
50
+ "@elementor/editor-styles-repository": "3.35.6",
51
+ "@elementor/editor-ui": "3.35.6",
52
+ "@elementor/editor-v1-adapters": "3.35.6",
53
+ "@elementor/schema": "3.35.6",
54
+ "@elementor/twing": "3.35.6",
55
55
  "@elementor/ui": "1.36.17",
56
- "@elementor/utils": "3.35.5",
57
- "@elementor/wp-media": "3.35.5",
56
+ "@elementor/utils": "3.35.6",
57
+ "@elementor/wp-media": "3.35.6",
58
58
  "@floating-ui/react": "^0.27.5",
59
59
  "@wordpress/i18n": "^5.13.0"
60
60
  },
@@ -1,26 +1,19 @@
1
1
  import * as React from 'react';
2
- import { useEffect, useState } from 'react';
2
+ import { useEffect, useLayoutEffect, useState } from 'react';
3
3
  import { InlineEditor, InlineEditorToolbar } from '@elementor/editor-controls';
4
4
  import { Box, ThemeProvider } from '@elementor/ui';
5
- import { FloatingPortal, useInteractions } from '@floating-ui/react';
5
+ import { autoUpdate, flip, FloatingPortal, useFloating } from '@floating-ui/react';
6
6
 
7
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
8
  import {
11
- calcSelectionCenterOffsets,
12
9
  type Editor,
13
- type EditorView,
14
- getComputedStyle,
15
- type Offsets,
10
+ getInlineEditorElement,
11
+ horizontalShifterMiddleware as horizontalShifter,
12
+ removeToolbarAnchor,
13
+ useOnClickOutsideIframe,
14
+ useRenderToolbar,
16
15
  } from './inline-editing-utils';
17
16
 
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
17
  const EDITOR_WRAPPER_SELECTOR = 'inline-editor-wrapper';
25
18
 
26
19
  export const CanvasInlineEditor = ( {
@@ -30,7 +23,7 @@ export const CanvasInlineEditor = ( {
30
23
  rootElement,
31
24
  id,
32
25
  setValue,
33
- onBlur,
26
+ ...props
34
27
  }: {
35
28
  elementClasses: string;
36
29
  initialValue: string | null;
@@ -40,13 +33,13 @@ export const CanvasInlineEditor = ( {
40
33
  setValue: ( value: string | null ) => void;
41
34
  onBlur: () => void;
42
35
  } ) => {
43
- const [ selectionOffsets, setSelectionOffsets ] = useState< Offsets | null >( null );
44
36
  const [ editor, setEditor ] = useState< Editor | null >( null );
37
+ const { onSelectionEnd, anchor: toolbarAnchor } = useRenderToolbar( rootElement.ownerDocument, id );
45
38
 
46
- const onSelectionEnd = ( view: EditorView ) => {
47
- const hasSelection = ! view.state.selection.empty;
39
+ const onBlur = () => {
40
+ removeToolbarAnchor( rootElement.ownerDocument, id );
48
41
 
49
- setSelectionOffsets( hasSelection ? calcSelectionCenterOffsets( view ) : null );
42
+ props.onBlur();
50
43
  };
51
44
 
52
45
  useOnClickOutsideIframe( onBlur );
@@ -59,6 +52,10 @@ export const CanvasInlineEditor = ( {
59
52
  .ProseMirror > * {
60
53
  height: 100%;
61
54
  }
55
+ .${ EDITOR_WRAPPER_SELECTOR } .ProseMirror > button[contenteditable="true"] {
56
+ height: auto;
57
+ cursor: text;
58
+ }
62
59
  ` }
63
60
  </style>
64
61
  <InlineEditor
@@ -74,18 +71,9 @@ export const CanvasInlineEditor = ( {
74
71
  onBlur={ onBlur }
75
72
  autofocus
76
73
  expectedTag={ expectedTag }
77
- wrapperClassName={ EDITOR_WRAPPER_SELECTOR }
78
74
  onSelectionEnd={ onSelectionEnd }
79
75
  />
80
- { selectionOffsets && editor && (
81
- <InlineEditingToolbarWrapper
82
- expectedTag={ expectedTag }
83
- editor={ editor }
84
- rootElement={ rootElement }
85
- id={ id }
86
- selectionOffsets={ selectionOffsets }
87
- />
88
- ) }
76
+ { toolbarAnchor && editor && <InlineEditingToolbar anchor={ toolbarAnchor } editor={ editor } id={ id } /> }
89
77
  </ThemeProvider>
90
78
  );
91
79
  };
@@ -109,104 +97,26 @@ const InlineEditingOverlay = ( {
109
97
  return overlayRefElement ? <OutlineOverlay element={ overlayRefElement } id={ id } isSelected /> : null;
110
98
  };
111
99
 
112
- const InlineEditingToolbarWrapper = ( {
113
- expectedTag,
114
- editor,
115
- rootElement,
116
- id,
117
- selectionOffsets,
118
- }: {
119
- expectedTag: string | null;
120
- editor: Editor;
121
- rootElement: HTMLElement;
122
- id: string;
123
- selectionOffsets: Offsets;
124
- } ) => {
125
- const [ element, setElement ] = useState< HTMLElement | null >( null );
126
-
127
- useEffect( () => {
128
- setElement( getInlineEditorElement( rootElement, expectedTag ) );
129
- }, [ expectedTag, rootElement ] );
130
-
131
- return element ? (
132
- <InlineEditingToolbar element={ element } editor={ editor } id={ id } selectionOffsets={ selectionOffsets } />
133
- ) : null;
134
- };
135
-
136
- const InlineEditingToolbar = ( {
137
- element,
138
- editor,
139
- id,
140
- selectionOffsets,
141
- }: {
142
- element: HTMLElement;
143
- editor: Editor;
144
- id: string;
145
- selectionOffsets: Offsets;
146
- } ) => {
147
- const { floating } = useFloatingOnElement( {
148
- element,
149
- isSelected: true,
100
+ const InlineEditingToolbar = ( { anchor, editor, id }: { anchor: HTMLElement; editor: Editor; id: string } ) => {
101
+ const { refs, floatingStyles } = useFloating( {
102
+ placement: 'top',
103
+ strategy: 'fixed',
104
+ transform: false,
105
+ whileElementsMounted: autoUpdate,
106
+ middleware: [ horizontalShifter, flip() ],
150
107
  } );
151
- const { getFloatingProps, getReferenceProps } = useInteractions();
152
- const style = getComputedStyle( floating.styles, selectionOffsets );
153
108
 
154
- useBindReactPropsToElement( element, getReferenceProps );
109
+ useLayoutEffect( () => {
110
+ refs.setReference( anchor );
111
+
112
+ return () => refs.setReference( null );
113
+ }, [ anchor, refs ] );
155
114
 
156
115
  return (
157
116
  <FloatingPortal id={ CANVAS_WRAPPER_ID }>
158
- <Box
159
- ref={ floating.setRef }
160
- style={ {
161
- ...floating.styles,
162
- pointerEvents: 'none',
163
- } }
164
- role="presentation"
165
- { ...getFloatingProps( { style } ) }
166
- >
167
- { floating.styles.transform && (
168
- <Box
169
- sx={ {
170
- position: 'relative',
171
- transform: 'translateY(-100%)',
172
- height: 'max-content',
173
- } }
174
- >
175
- <InlineEditorToolbar
176
- editor={ editor }
177
- elementId={ id }
178
- sx={ {
179
- transform: 'translateX(-50%)',
180
- } }
181
- />
182
- </Box>
183
- ) }
117
+ <Box ref={ refs.setFloating } role="presentation" style={ { ...floatingStyles, pointerEvents: 'none' } }>
118
+ <InlineEditorToolbar editor={ editor } elementId={ id } />
184
119
  </Box>
185
120
  </FloatingPortal>
186
121
  );
187
122
  };
188
-
189
- const getInlineEditorElement = ( elementWrapper: HTMLElement, expectedTag: string | null ) => {
190
- return ! expectedTag ? null : ( elementWrapper.querySelector( expectedTag ) as HTMLDivElement );
191
- };
192
-
193
- // Elements out of iframe and canvas don't trigger "onClickAway" which unmounts the editor
194
- // since they are not part of the iframes owner document.
195
- // We need to manually add listeners to these elements to unmount the editor when they are clicked.
196
- const useOnClickOutsideIframe = ( handleUnmount: () => void ) => {
197
- const asyncUnmountInlineEditor = React.useCallback( () => queueMicrotask( handleUnmount ), [ handleUnmount ] );
198
-
199
- useEffect( () => {
200
- EDITOR_ELEMENTS_OUT_OF_IFRAME.forEach(
201
- ( selector ) =>
202
- document?.querySelector( selector )?.addEventListener( 'mousedown', asyncUnmountInlineEditor )
203
- );
204
-
205
- return () =>
206
- EDITOR_ELEMENTS_OUT_OF_IFRAME.forEach(
207
- ( selector ) =>
208
- document?.querySelector( selector )?.removeEventListener( 'mousedown', asyncUnmountInlineEditor )
209
- );
210
- // eslint-disable-next-line react-hooks/exhaustive-deps
211
- }, [] );
212
- };
@@ -1,9 +1,35 @@
1
- import { type CSSProperties } from 'react';
1
+ import { type CSSProperties, useCallback, useEffect, useState } from 'react';
2
2
  import { type InlineEditorToolbarProps } from '@elementor/editor-controls';
3
3
  import { type V1Element } from '@elementor/editor-elements';
4
+ import { type MiddlewareReturn, type MiddlewareState } from '@floating-ui/react';
4
5
 
5
6
  import { type LegacyWindow } from '../../types';
6
7
 
8
+ const TOP_BAR_SELECTOR = '#elementor-editor-wrapper-v2';
9
+ const NAVIGATOR_SELECTOR = '#elementor-navigator';
10
+ const EDITING_PANEL = '#elementor-panel';
11
+
12
+ const EDITOR_ELEMENTS_OUT_OF_IFRAME = [ TOP_BAR_SELECTOR, NAVIGATOR_SELECTOR, EDITING_PANEL ];
13
+
14
+ export const EDITOR_WRAPPER_SELECTOR = 'inline-editor-wrapper';
15
+
16
+ const TOOLBAR_ANCHOR_ID_PREFIX = 'inline-editing-toolbar-anchor';
17
+
18
+ const TOOLBAR_ANCHOR_STATIC_STYLES: CSSProperties = {
19
+ backgroundColor: 'transparent',
20
+ border: 'none',
21
+ outline: 'none',
22
+ boxShadow: 'none',
23
+ padding: '0',
24
+ margin: '0',
25
+ borderRadius: '0',
26
+ overflow: 'hidden',
27
+ opacity: '0',
28
+ pointerEvents: 'none',
29
+ position: 'absolute',
30
+ display: 'block',
31
+ };
32
+
7
33
  export type Editor = InlineEditorToolbarProps[ 'editor' ];
8
34
  export type EditorView = Editor[ 'view' ];
9
35
 
@@ -24,62 +50,134 @@ export const getWidgetType = ( container: V1Element | null ) => {
24
50
  return container?.model?.get( 'widgetType' ) ?? container?.model?.get( 'elType' ) ?? null;
25
51
  };
26
52
 
27
- export const calcSelectionCenterOffsets = ( view: EditorView ): Offsets | null => {
28
- const frameWindow = ( view.root as Document )?.defaultView;
53
+ export const getInlineEditorElement = ( elementWrapper: HTMLElement, expectedTag: string | null ) => {
54
+ return ! expectedTag ? null : ( elementWrapper.querySelector( expectedTag ) as HTMLDivElement );
55
+ };
56
+
57
+ // Elements out of iframe and canvas don't trigger "onClickAway" which unmounts the editor
58
+ // since they are not part of the iframes owner document.
59
+ // We need to manually add listeners to these elements to unmount the editor when they are clicked.
60
+ export const useOnClickOutsideIframe = ( handleUnmount: () => void ) => {
61
+ const asyncUnmountInlineEditor = useCallback( () => queueMicrotask( handleUnmount ), [ handleUnmount ] );
62
+
63
+ useEffect( () => {
64
+ EDITOR_ELEMENTS_OUT_OF_IFRAME.forEach(
65
+ ( selector ) =>
66
+ document?.querySelector( selector )?.addEventListener( 'mousedown', asyncUnmountInlineEditor )
67
+ );
68
+
69
+ return () =>
70
+ EDITOR_ELEMENTS_OUT_OF_IFRAME.forEach(
71
+ ( selector ) =>
72
+ document?.querySelector( selector )?.removeEventListener( 'mousedown', asyncUnmountInlineEditor )
73
+ );
74
+ // eslint-disable-next-line react-hooks/exhaustive-deps
75
+ }, [] );
76
+ };
77
+
78
+ export const useRenderToolbar = ( ownerDocument: Document, id: string ) => {
79
+ const [ anchor, setAnchor ] = useState< HTMLElement | null >( null );
80
+
81
+ const onSelectionEnd = ( view: EditorView ) => {
82
+ const hasSelection = ! view.state.selection.empty;
83
+
84
+ removeToolbarAnchor( ownerDocument, id );
85
+
86
+ if ( hasSelection ) {
87
+ setAnchor( createAnchorBasedOnSelection( ownerDocument, id ) );
88
+ } else {
89
+ setAnchor( null );
90
+ }
91
+ };
92
+
93
+ return { onSelectionEnd, anchor };
94
+ };
95
+
96
+ const createAnchorBasedOnSelection = ( ownerDocument: Document, id: string ): HTMLElement | null => {
97
+ const frameWindow = ownerDocument.defaultView;
29
98
  const selection = frameWindow?.getSelection();
30
- const editorContainer = view.dom;
31
99
 
32
- if ( ! selection || ! editorContainer ) {
100
+ if ( ! selection ) {
33
101
  return null;
34
102
  }
35
103
 
36
104
  const range = selection.getRangeAt( 0 );
37
105
  const selectionRect = range.getBoundingClientRect();
38
- const editorContainerRect = editorContainer.getBoundingClientRect();
106
+ const bodyRect = ownerDocument.body.getBoundingClientRect();
107
+ const toolbarAnchor = ownerDocument.createElement( 'span' );
39
108
 
40
- if ( ! selectionRect || ! editorContainerRect ) {
41
- return null;
42
- }
109
+ styleToolbarAnchor( toolbarAnchor, selectionRect, bodyRect );
110
+ toolbarAnchor.setAttribute( 'id', getToolbarAnchorId( id ) );
43
111
 
44
- const verticalOffset = selectionRect.top - editorContainerRect.top;
112
+ ownerDocument.body.appendChild( toolbarAnchor );
45
113
 
46
- const selectionCenter = selectionRect?.left + selectionRect?.width / 2;
47
- const horizontalOffset = selectionCenter - editorContainerRect.left;
114
+ return toolbarAnchor;
115
+ };
116
+
117
+ export const removeToolbarAnchor = ( ownerDocument: Document, id: string ) => {
118
+ const toolbarAnchor = getToolbarAnchor( ownerDocument, id );
48
119
 
49
- return { left: horizontalOffset, top: verticalOffset };
120
+ if ( toolbarAnchor ) {
121
+ ownerDocument.body.removeChild( toolbarAnchor );
122
+ }
50
123
  };
51
124
 
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
- };
125
+ const getToolbarAnchorId = ( id: string ) => `${ TOOLBAR_ANCHOR_ID_PREFIX }-${ id }`;
126
+
127
+ export const getToolbarAnchor = ( ownerDocument: Document, id: string ) =>
128
+ ownerDocument.getElementById( getToolbarAnchorId( id ) ) as HTMLElement | null;
129
+
130
+ const styleToolbarAnchor = ( anchor: HTMLElement, selectionRect: DOMRect, bodyRect: DOMRect ) => {
131
+ const { width, height } = selectionRect;
132
+
133
+ Object.assign( anchor.style, {
134
+ ...TOOLBAR_ANCHOR_STATIC_STYLES,
135
+ top: `${ selectionRect.top - bodyRect.top }px`,
136
+ left: `${ selectionRect.left - bodyRect.left }px`,
137
+ width: `${ width }px`,
138
+ height: `${ height }px`,
139
+ } );
65
140
  };
66
141
 
67
- const extractTransformValue = ( styles: CSSProperties ) => {
68
- const translateRegex = /translate\([^)]*\)\s?/g;
69
- const numericValuesRegex = /(-?\d+\.?\d*)/g;
142
+ export const horizontalShifterMiddleware: {
143
+ name: string;
144
+ fn: ( state: MiddlewareState ) => MiddlewareReturn;
145
+ } = {
146
+ name: 'horizontalShifter',
147
+ fn( state ) {
148
+ const {
149
+ x: left,
150
+ y: top,
151
+ elements: { reference: anchor, floating },
152
+ } = state;
70
153
 
71
- const translateValue = styles?.transform?.match( translateRegex )?.[ 0 ];
72
- const values = translateValue?.match( numericValuesRegex );
154
+ const newState: MiddlewareReturn = {
155
+ ...state,
156
+ x: left,
157
+ y: top,
158
+ };
73
159
 
74
- if ( ! translateValue || ! values ) {
75
- return null;
76
- }
160
+ const isLeftOverflown = left < 0;
77
161
 
78
- const [ numericX, numericY ] = values.map( Number );
162
+ if ( isLeftOverflown ) {
163
+ newState.x = 0;
79
164
 
80
- if ( ! numericX || ! numericY ) {
81
- return null;
82
- }
165
+ return newState;
166
+ }
167
+
168
+ const anchorRect = anchor.getBoundingClientRect();
169
+ const right = left + floating.offsetWidth;
170
+ const documentWidth = ( anchor as HTMLElement ).ownerDocument.body.offsetWidth;
171
+ const isRightOverflown = right > documentWidth && anchorRect.right < right;
172
+
173
+ if ( isRightOverflown ) {
174
+ const diff = right - documentWidth;
175
+
176
+ newState.x = left - diff;
177
+
178
+ return newState;
179
+ }
83
180
 
84
- return styles.transform;
181
+ return newState;
182
+ },
85
183
  };