@elementor/editor-canvas 3.35.0-403 → 3.35.0-405

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
@@ -951,7 +951,7 @@ function sortByStateType({ state: stateA }, { state: stateB }) {
951
951
  function createProviderSubscriber2({ provider, renderStyles, setStyleItems }) {
952
952
  return abortPreviousRuns(
953
953
  (abortController) => signalizedProcess(abortController.signal).then((_, signal) => {
954
- const styles = provider.actions.all().map((__5, index, items) => {
954
+ const styles = provider.actions.all().map((__6, index, items) => {
955
955
  const lastPosition = items.length - 1;
956
956
  const style = items[lastPosition - index];
957
957
  return {
@@ -1648,8 +1648,14 @@ var import_editor_elements3 = require("@elementor/editor-elements");
1648
1648
  var import_editor_props3 = require("@elementor/editor-props");
1649
1649
  var import_editor_v1_adapters8 = require("@elementor/editor-v1-adapters");
1650
1650
  var import_ui4 = require("@elementor/ui");
1651
+ var import_i18n = require("@wordpress/i18n");
1651
1652
 
1652
1653
  // src/legacy/replacements/base.ts
1654
+ var TRIGGER_TIMING = {
1655
+ before: "before",
1656
+ after: "after",
1657
+ never: "never"
1658
+ };
1653
1659
  var ReplacementBase = class {
1654
1660
  getSetting;
1655
1661
  setSetting;
@@ -1671,6 +1677,15 @@ var ReplacementBase = class {
1671
1677
  shouldRenderReplacement() {
1672
1678
  return true;
1673
1679
  }
1680
+ originalMethodsToTrigger() {
1681
+ return {
1682
+ _beforeRender: TRIGGER_TIMING.before,
1683
+ _afterRender: TRIGGER_TIMING.after,
1684
+ renderOnChange: TRIGGER_TIMING.never,
1685
+ onDestroy: TRIGGER_TIMING.never,
1686
+ render: TRIGGER_TIMING.never
1687
+ };
1688
+ }
1674
1689
  };
1675
1690
 
1676
1691
  // src/legacy/replacements/inline-editing/inline-editing-utils.ts
@@ -1693,6 +1708,12 @@ var getInitialPopoverPosition = () => {
1693
1708
 
1694
1709
  // src/legacy/replacements/inline-editing/inline-editing-elements.tsx
1695
1710
  var EXPERIMENT_KEY = "v4-inline-text-editing";
1711
+ var HISTORY_DEBOUNCE_WAIT = 800;
1712
+ var TOP_BAR_SELECTOR = "#elementor-editor-wrapper-v2";
1713
+ var NAVIGATOR_SELECTOR = "#elementor-navigator";
1714
+ var V4_EDITING_PANEL = "main.MuiBox-root";
1715
+ var V3_EDITING_PANEL = "#elementor-panel-content-wrapper";
1716
+ var BLUR_TRIGGERING_SELECTORS = [TOP_BAR_SELECTOR, NAVIGATOR_SELECTOR, V4_EDITING_PANEL, V3_EDITING_PANEL];
1696
1717
  var InlineEditingReplacement = class extends ReplacementBase {
1697
1718
  inlineEditorRoot = null;
1698
1719
  handlerAttached = false;
@@ -1708,11 +1729,11 @@ var InlineEditingReplacement = class extends ReplacementBase {
1708
1729
  shouldRenderReplacement() {
1709
1730
  return (0, import_editor_v1_adapters8.isExperimentActive)(EXPERIMENT_KEY) && !this.isValueDynamic();
1710
1731
  }
1711
- handleRenderInlineEditor = (event) => {
1712
- event.stopPropagation();
1713
- if (!this.isValueDynamic()) {
1714
- this.renderInlineEditor();
1732
+ handleRenderInlineEditor = () => {
1733
+ if (this.isEditingModeActive() || this.isValueDynamic()) {
1734
+ return;
1715
1735
  }
1736
+ this.renderInlineEditor();
1716
1737
  };
1717
1738
  renderOnChange() {
1718
1739
  if (this.isEditingModeActive()) {
@@ -1728,12 +1749,23 @@ var InlineEditingReplacement = class extends ReplacementBase {
1728
1749
  }
1729
1750
  _afterRender() {
1730
1751
  if (!this.isValueDynamic() && !this.handlerAttached) {
1731
- this.element.addEventListener("dblclick", this.handleRenderInlineEditor, { once: true });
1752
+ this.element.addEventListener("click", this.handleRenderInlineEditor);
1732
1753
  this.handlerAttached = true;
1733
1754
  }
1734
1755
  }
1756
+ originalMethodsToTrigger() {
1757
+ const before = this.isEditingModeActive() ? TRIGGER_TIMING.never : TRIGGER_TIMING.before;
1758
+ const after = this.isEditingModeActive() ? TRIGGER_TIMING.never : TRIGGER_TIMING.after;
1759
+ return {
1760
+ _beforeRender: before,
1761
+ _afterRender: after,
1762
+ renderOnChange: after,
1763
+ onDestroy: TRIGGER_TIMING.after,
1764
+ render: before
1765
+ };
1766
+ }
1735
1767
  resetInlineEditorRoot() {
1736
- this.element.removeEventListener("dblclick", this.handleRenderInlineEditor);
1768
+ this.element.removeEventListener("click", this.handleRenderInlineEditor);
1737
1769
  this.handlerAttached = false;
1738
1770
  this.inlineEditorRoot?.unmount?.();
1739
1771
  this.inlineEditorRoot = null;
@@ -1764,12 +1796,35 @@ var InlineEditingReplacement = class extends ReplacementBase {
1764
1796
  setContentValue(value) {
1765
1797
  const settingKey = this.getInlineEditablePropertyName();
1766
1798
  const valueToSave = value ? import_editor_props3.htmlPropTypeUtil.create(value) : null;
1799
+ (0, import_editor_v1_adapters8.undoable)(
1800
+ {
1801
+ do: () => {
1802
+ const prevValue = this.getContentValue();
1803
+ this.runCommand(settingKey, valueToSave);
1804
+ return prevValue;
1805
+ },
1806
+ undo: (prevValue) => {
1807
+ this.runCommand(settingKey, prevValue ?? null);
1808
+ }
1809
+ },
1810
+ {
1811
+ title: (0, import_editor_elements3.getElementLabel)(this.id),
1812
+ // translators: %s is the name of the property that was edited.
1813
+ subtitle: (0, import_i18n.__)("%s edited", "elementor").replace(
1814
+ "%s",
1815
+ this.getHtmlPropType()?.key ?? "Inline editing"
1816
+ ),
1817
+ debounce: { wait: HISTORY_DEBOUNCE_WAIT }
1818
+ }
1819
+ )();
1820
+ }
1821
+ runCommand(key, value) {
1767
1822
  (0, import_editor_v1_adapters8.__privateRunCommandSync)(
1768
1823
  "document/elements/set-settings",
1769
1824
  {
1770
1825
  container: (0, import_editor_elements3.getContainer)(this.id),
1771
1826
  settings: {
1772
- [settingKey]: valueToSave
1827
+ [key]: value
1773
1828
  }
1774
1829
  },
1775
1830
  { internal: true }
@@ -1793,13 +1848,13 @@ var InlineEditingReplacement = class extends ReplacementBase {
1793
1848
  return tagPropType;
1794
1849
  }
1795
1850
  renderInlineEditor() {
1851
+ if (this.isEditingModeActive()) {
1852
+ this.resetInlineEditorRoot();
1853
+ }
1796
1854
  const InlineEditorApp = this.InlineEditorApp;
1797
1855
  const wrapperClasses = "elementor";
1798
1856
  const elementClasses = this.element.children?.[0]?.classList.toString() ?? "";
1799
1857
  this.element.innerHTML = "";
1800
- if (this.inlineEditorRoot) {
1801
- this.resetInlineEditorRoot();
1802
- }
1803
1858
  this.inlineEditorRoot = (0, import_client.createRoot)(this.element);
1804
1859
  this.inlineEditorRoot.render(
1805
1860
  /* @__PURE__ */ React5.createElement(InlineEditorApp, { wrapperClasses, elementClasses })
@@ -1811,12 +1866,15 @@ var InlineEditingReplacement = class extends ReplacementBase {
1811
1866
  const wrapperRef = (0, import_react11.useRef)(null);
1812
1867
  const [isWrapperRendered, setIsWrapperRendered] = (0, import_react11.useState)(false);
1813
1868
  (0, import_react11.useEffect)(() => {
1814
- const panel = document?.querySelector("main.MuiBox-root");
1815
1869
  setIsWrapperRendered(!!wrapperRef.current);
1816
- panel?.addEventListener("click", asyncUnmountInlineEditor);
1817
- return () => panel?.removeEventListener("click", asyncUnmountInlineEditor);
1870
+ BLUR_TRIGGERING_SELECTORS.forEach(
1871
+ (selector) => document?.querySelector(selector)?.addEventListener("mousedown", asyncUnmountInlineEditor)
1872
+ );
1873
+ return () => BLUR_TRIGGERING_SELECTORS.forEach(
1874
+ (selector) => document?.querySelector(selector)?.removeEventListener("mousedown", asyncUnmountInlineEditor)
1875
+ );
1818
1876
  }, []);
1819
- const asyncUnmountInlineEditor = (0, import_react11.useCallback)(
1877
+ const asyncUnmountInlineEditor = React5.useCallback(
1820
1878
  () => queueMicrotask(this.unmountInlineEditor.bind(this)),
1821
1879
  []
1822
1880
  );
@@ -1878,9 +1936,7 @@ var createViewWithReplacements = (options) => {
1878
1936
  this.render();
1879
1937
  }
1880
1938
  renderOnChange() {
1881
- if (!this.#triggerMethod("renderOnChange")) {
1882
- TemplatedView.prototype.renderOnChange.apply(this);
1883
- }
1939
+ this.#triggerAltMethod("renderOnChange");
1884
1940
  }
1885
1941
  render() {
1886
1942
  const config = this.#config;
@@ -1889,33 +1945,32 @@ var createViewWithReplacements = (options) => {
1889
1945
  if (ReplacementClass && !this.#replacement) {
1890
1946
  this.#replacement = new ReplacementClass(config);
1891
1947
  }
1892
- if (!this.#triggerMethod("render")) {
1893
- TemplatedView.prototype.render.apply(this);
1894
- }
1948
+ this.#triggerAltMethod("render");
1895
1949
  }
1896
1950
  onDestroy() {
1897
- if (this.#triggerMethod("onDestroy")) {
1898
- this.#replacement = null;
1899
- }
1900
- TemplatedView.prototype.onDestroy.apply(this);
1951
+ this.#triggerAltMethod("onDestroy");
1901
1952
  }
1902
1953
  _afterRender() {
1903
- if (!this.#triggerMethod("_afterRender")) {
1904
- TemplatedView.prototype._afterRender.apply(this);
1905
- }
1954
+ this.#triggerAltMethod("_afterRender");
1906
1955
  }
1907
1956
  _beforeRender() {
1908
- if (!this.#triggerMethod("_beforeRender")) {
1909
- TemplatedView.prototype._beforeRender.apply(this);
1957
+ this.#triggerAltMethod("_beforeRender");
1958
+ }
1959
+ #triggerAltMethod(methodKey) {
1960
+ const baseMethod = TemplatedView.prototype[methodKey].bind(this);
1961
+ const shouldReplace = this.#replacement?.shouldRenderReplacement();
1962
+ const altMethod = shouldReplace && this.#replacement?.[methodKey]?.bind(this.#replacement);
1963
+ if (!altMethod || !shouldReplace) {
1964
+ return baseMethod();
1910
1965
  }
1911
- }
1912
- #triggerMethod(methodKey) {
1913
- const method = this.#replacement?.shouldRenderReplacement() && this.#replacement[methodKey]?.bind(this.#replacement);
1914
- if (!method || typeof method !== "function") {
1915
- return false;
1966
+ const renderTiming = this.#replacement?.originalMethodsToTrigger()[methodKey] ?? "never";
1967
+ if (renderTiming === "before") {
1968
+ baseMethod();
1969
+ }
1970
+ altMethod();
1971
+ if (renderTiming === "after") {
1972
+ baseMethod();
1916
1973
  }
1917
- method();
1918
- return true;
1919
1974
  }
1920
1975
  };
1921
1976
  };
@@ -3216,7 +3271,7 @@ An Example scenario of creating fully styled composition:
3216
3271
  var import_editor_elements9 = require("@elementor/editor-elements");
3217
3272
  var import_editor_notifications = require("@elementor/editor-notifications");
3218
3273
  var import_editor_v1_adapters11 = require("@elementor/editor-v1-adapters");
3219
- var import_i18n = require("@wordpress/i18n");
3274
+ var import_i18n2 = require("@wordpress/i18n");
3220
3275
  function initLinkInLinkPrevention() {
3221
3276
  (0, import_editor_v1_adapters11.blockCommand)({
3222
3277
  command: "document/elements/paste",
@@ -3250,7 +3305,7 @@ function blockLinkInLinkPaste(args) {
3250
3305
  const sourceElements = data.clipboard.elements;
3251
3306
  const notification = {
3252
3307
  type: "default",
3253
- message: (0, import_i18n.__)(
3308
+ message: (0, import_i18n2.__)(
3254
3309
  "To paste a link to this element, first remove the link from it's parent container.",
3255
3310
  "elementor"
3256
3311
  ),
@@ -3269,7 +3324,7 @@ function blockLinkInLinkMove(args) {
3269
3324
  const targetElement = target;
3270
3325
  const notification = {
3271
3326
  type: "default",
3272
- message: (0, import_i18n.__)("To drag a link to this element, first remove the link from it's parent container.", "elementor"),
3327
+ message: (0, import_i18n2.__)("To drag a link to this element, first remove the link from it's parent container.", "elementor"),
3273
3328
  id: "move-in-link-blocked",
3274
3329
  additionalActionProps: [learnMoreActionProps]
3275
3330
  };
@@ -3302,12 +3357,12 @@ var import_editor_v1_adapters13 = require("@elementor/editor-v1-adapters");
3302
3357
  var import_editor_elements11 = require("@elementor/editor-elements");
3303
3358
  var import_editor_styles_repository4 = require("@elementor/editor-styles-repository");
3304
3359
  var import_editor_v1_adapters12 = require("@elementor/editor-v1-adapters");
3305
- var import_i18n3 = require("@wordpress/i18n");
3360
+ var import_i18n4 = require("@wordpress/i18n");
3306
3361
 
3307
3362
  // src/style-commands/utils.ts
3308
3363
  var import_editor_elements10 = require("@elementor/editor-elements");
3309
3364
  var import_editor_props7 = require("@elementor/editor-props");
3310
- var import_i18n2 = require("@wordpress/i18n");
3365
+ var import_i18n3 = require("@wordpress/i18n");
3311
3366
  function hasAtomicWidgets(args) {
3312
3367
  const { containers = [args.container] } = args;
3313
3368
  return containers.some(isAtomicWidget);
@@ -3343,7 +3398,7 @@ function getClipboardElements(storageKey = "clipboard") {
3343
3398
  }
3344
3399
  }
3345
3400
  function getTitleForContainers(containers) {
3346
- return containers.length > 1 ? (0, import_i18n2.__)("Elements", "elementor") : (0, import_editor_elements10.getElementLabel)(containers[0].id);
3401
+ return containers.length > 1 ? (0, import_i18n3.__)("Elements", "elementor") : (0, import_editor_elements10.getElementLabel)(containers[0].id);
3347
3402
  }
3348
3403
 
3349
3404
  // src/style-commands/undoable-actions/paste-element-style.ts
@@ -3416,7 +3471,7 @@ var undoablePasteElementStyle = () => (0, import_editor_v1_adapters12.undoable)(
3416
3471
  },
3417
3472
  {
3418
3473
  title: ({ containers }) => getTitleForContainers(containers),
3419
- subtitle: (0, import_i18n3.__)("Style Pasted", "elementor")
3474
+ subtitle: (0, import_i18n4.__)("Style Pasted", "elementor")
3420
3475
  }
3421
3476
  );
3422
3477
 
@@ -3458,7 +3513,7 @@ var import_editor_v1_adapters15 = require("@elementor/editor-v1-adapters");
3458
3513
  var import_editor_elements12 = require("@elementor/editor-elements");
3459
3514
  var import_editor_styles_repository5 = require("@elementor/editor-styles-repository");
3460
3515
  var import_editor_v1_adapters14 = require("@elementor/editor-v1-adapters");
3461
- var import_i18n4 = require("@wordpress/i18n");
3516
+ var import_i18n5 = require("@wordpress/i18n");
3462
3517
  var undoableResetElementStyle = () => (0, import_editor_v1_adapters14.undoable)(
3463
3518
  {
3464
3519
  do: ({ containers }) => {
@@ -3496,7 +3551,7 @@ var undoableResetElementStyle = () => (0, import_editor_v1_adapters14.undoable)(
3496
3551
  },
3497
3552
  {
3498
3553
  title: ({ containers }) => getTitleForContainers(containers),
3499
- subtitle: (0, import_i18n4.__)("Style Reset", "elementor")
3554
+ subtitle: (0, import_i18n5.__)("Style Reset", "elementor")
3500
3555
  }
3501
3556
  );
3502
3557
 
package/dist/index.mjs CHANGED
@@ -909,7 +909,7 @@ function sortByStateType({ state: stateA }, { state: stateB }) {
909
909
  function createProviderSubscriber2({ provider, renderStyles, setStyleItems }) {
910
910
  return abortPreviousRuns(
911
911
  (abortController) => signalizedProcess(abortController.signal).then((_, signal) => {
912
- const styles = provider.actions.all().map((__5, index, items) => {
912
+ const styles = provider.actions.all().map((__6, index, items) => {
913
913
  const lastPosition = items.length - 1;
914
914
  const style = items[lastPosition - index];
915
915
  return {
@@ -1599,18 +1599,24 @@ function createTemplatedElementView({
1599
1599
 
1600
1600
  // src/legacy/replacements/inline-editing/inline-editing-elements.tsx
1601
1601
  import * as React5 from "react";
1602
- import { useCallback, useEffect as useEffect7, useRef as useRef2, useState as useState4 } from "react";
1602
+ import { useEffect as useEffect7, useRef as useRef2, useState as useState4 } from "react";
1603
1603
  import { createRoot } from "react-dom/client";
1604
1604
  import { InlineEditor } from "@elementor/editor-controls";
1605
- import { getContainer, getElementType } from "@elementor/editor-elements";
1605
+ import { getContainer, getElementLabel, getElementType } from "@elementor/editor-elements";
1606
1606
  import {
1607
1607
  htmlPropTypeUtil,
1608
1608
  stringPropTypeUtil
1609
1609
  } from "@elementor/editor-props";
1610
- import { __privateRunCommandSync as runCommandSync, isExperimentActive } from "@elementor/editor-v1-adapters";
1610
+ import { __privateRunCommandSync as runCommandSync, isExperimentActive, undoable } from "@elementor/editor-v1-adapters";
1611
1611
  import { Box as Box2, ThemeProvider } from "@elementor/ui";
1612
+ import { __ } from "@wordpress/i18n";
1612
1613
 
1613
1614
  // src/legacy/replacements/base.ts
1615
+ var TRIGGER_TIMING = {
1616
+ before: "before",
1617
+ after: "after",
1618
+ never: "never"
1619
+ };
1614
1620
  var ReplacementBase = class {
1615
1621
  getSetting;
1616
1622
  setSetting;
@@ -1632,6 +1638,15 @@ var ReplacementBase = class {
1632
1638
  shouldRenderReplacement() {
1633
1639
  return true;
1634
1640
  }
1641
+ originalMethodsToTrigger() {
1642
+ return {
1643
+ _beforeRender: TRIGGER_TIMING.before,
1644
+ _afterRender: TRIGGER_TIMING.after,
1645
+ renderOnChange: TRIGGER_TIMING.never,
1646
+ onDestroy: TRIGGER_TIMING.never,
1647
+ render: TRIGGER_TIMING.never
1648
+ };
1649
+ }
1635
1650
  };
1636
1651
 
1637
1652
  // src/legacy/replacements/inline-editing/inline-editing-utils.ts
@@ -1654,6 +1669,12 @@ var getInitialPopoverPosition = () => {
1654
1669
 
1655
1670
  // src/legacy/replacements/inline-editing/inline-editing-elements.tsx
1656
1671
  var EXPERIMENT_KEY = "v4-inline-text-editing";
1672
+ var HISTORY_DEBOUNCE_WAIT = 800;
1673
+ var TOP_BAR_SELECTOR = "#elementor-editor-wrapper-v2";
1674
+ var NAVIGATOR_SELECTOR = "#elementor-navigator";
1675
+ var V4_EDITING_PANEL = "main.MuiBox-root";
1676
+ var V3_EDITING_PANEL = "#elementor-panel-content-wrapper";
1677
+ var BLUR_TRIGGERING_SELECTORS = [TOP_BAR_SELECTOR, NAVIGATOR_SELECTOR, V4_EDITING_PANEL, V3_EDITING_PANEL];
1657
1678
  var InlineEditingReplacement = class extends ReplacementBase {
1658
1679
  inlineEditorRoot = null;
1659
1680
  handlerAttached = false;
@@ -1669,11 +1690,11 @@ var InlineEditingReplacement = class extends ReplacementBase {
1669
1690
  shouldRenderReplacement() {
1670
1691
  return isExperimentActive(EXPERIMENT_KEY) && !this.isValueDynamic();
1671
1692
  }
1672
- handleRenderInlineEditor = (event) => {
1673
- event.stopPropagation();
1674
- if (!this.isValueDynamic()) {
1675
- this.renderInlineEditor();
1693
+ handleRenderInlineEditor = () => {
1694
+ if (this.isEditingModeActive() || this.isValueDynamic()) {
1695
+ return;
1676
1696
  }
1697
+ this.renderInlineEditor();
1677
1698
  };
1678
1699
  renderOnChange() {
1679
1700
  if (this.isEditingModeActive()) {
@@ -1689,12 +1710,23 @@ var InlineEditingReplacement = class extends ReplacementBase {
1689
1710
  }
1690
1711
  _afterRender() {
1691
1712
  if (!this.isValueDynamic() && !this.handlerAttached) {
1692
- this.element.addEventListener("dblclick", this.handleRenderInlineEditor, { once: true });
1713
+ this.element.addEventListener("click", this.handleRenderInlineEditor);
1693
1714
  this.handlerAttached = true;
1694
1715
  }
1695
1716
  }
1717
+ originalMethodsToTrigger() {
1718
+ const before = this.isEditingModeActive() ? TRIGGER_TIMING.never : TRIGGER_TIMING.before;
1719
+ const after = this.isEditingModeActive() ? TRIGGER_TIMING.never : TRIGGER_TIMING.after;
1720
+ return {
1721
+ _beforeRender: before,
1722
+ _afterRender: after,
1723
+ renderOnChange: after,
1724
+ onDestroy: TRIGGER_TIMING.after,
1725
+ render: before
1726
+ };
1727
+ }
1696
1728
  resetInlineEditorRoot() {
1697
- this.element.removeEventListener("dblclick", this.handleRenderInlineEditor);
1729
+ this.element.removeEventListener("click", this.handleRenderInlineEditor);
1698
1730
  this.handlerAttached = false;
1699
1731
  this.inlineEditorRoot?.unmount?.();
1700
1732
  this.inlineEditorRoot = null;
@@ -1725,12 +1757,35 @@ var InlineEditingReplacement = class extends ReplacementBase {
1725
1757
  setContentValue(value) {
1726
1758
  const settingKey = this.getInlineEditablePropertyName();
1727
1759
  const valueToSave = value ? htmlPropTypeUtil.create(value) : null;
1760
+ undoable(
1761
+ {
1762
+ do: () => {
1763
+ const prevValue = this.getContentValue();
1764
+ this.runCommand(settingKey, valueToSave);
1765
+ return prevValue;
1766
+ },
1767
+ undo: (prevValue) => {
1768
+ this.runCommand(settingKey, prevValue ?? null);
1769
+ }
1770
+ },
1771
+ {
1772
+ title: getElementLabel(this.id),
1773
+ // translators: %s is the name of the property that was edited.
1774
+ subtitle: __("%s edited", "elementor").replace(
1775
+ "%s",
1776
+ this.getHtmlPropType()?.key ?? "Inline editing"
1777
+ ),
1778
+ debounce: { wait: HISTORY_DEBOUNCE_WAIT }
1779
+ }
1780
+ )();
1781
+ }
1782
+ runCommand(key, value) {
1728
1783
  runCommandSync(
1729
1784
  "document/elements/set-settings",
1730
1785
  {
1731
1786
  container: getContainer(this.id),
1732
1787
  settings: {
1733
- [settingKey]: valueToSave
1788
+ [key]: value
1734
1789
  }
1735
1790
  },
1736
1791
  { internal: true }
@@ -1754,13 +1809,13 @@ var InlineEditingReplacement = class extends ReplacementBase {
1754
1809
  return tagPropType;
1755
1810
  }
1756
1811
  renderInlineEditor() {
1812
+ if (this.isEditingModeActive()) {
1813
+ this.resetInlineEditorRoot();
1814
+ }
1757
1815
  const InlineEditorApp = this.InlineEditorApp;
1758
1816
  const wrapperClasses = "elementor";
1759
1817
  const elementClasses = this.element.children?.[0]?.classList.toString() ?? "";
1760
1818
  this.element.innerHTML = "";
1761
- if (this.inlineEditorRoot) {
1762
- this.resetInlineEditorRoot();
1763
- }
1764
1819
  this.inlineEditorRoot = createRoot(this.element);
1765
1820
  this.inlineEditorRoot.render(
1766
1821
  /* @__PURE__ */ React5.createElement(InlineEditorApp, { wrapperClasses, elementClasses })
@@ -1772,12 +1827,15 @@ var InlineEditingReplacement = class extends ReplacementBase {
1772
1827
  const wrapperRef = useRef2(null);
1773
1828
  const [isWrapperRendered, setIsWrapperRendered] = useState4(false);
1774
1829
  useEffect7(() => {
1775
- const panel = document?.querySelector("main.MuiBox-root");
1776
1830
  setIsWrapperRendered(!!wrapperRef.current);
1777
- panel?.addEventListener("click", asyncUnmountInlineEditor);
1778
- return () => panel?.removeEventListener("click", asyncUnmountInlineEditor);
1831
+ BLUR_TRIGGERING_SELECTORS.forEach(
1832
+ (selector) => document?.querySelector(selector)?.addEventListener("mousedown", asyncUnmountInlineEditor)
1833
+ );
1834
+ return () => BLUR_TRIGGERING_SELECTORS.forEach(
1835
+ (selector) => document?.querySelector(selector)?.removeEventListener("mousedown", asyncUnmountInlineEditor)
1836
+ );
1779
1837
  }, []);
1780
- const asyncUnmountInlineEditor = useCallback(
1838
+ const asyncUnmountInlineEditor = React5.useCallback(
1781
1839
  () => queueMicrotask(this.unmountInlineEditor.bind(this)),
1782
1840
  []
1783
1841
  );
@@ -1839,9 +1897,7 @@ var createViewWithReplacements = (options) => {
1839
1897
  this.render();
1840
1898
  }
1841
1899
  renderOnChange() {
1842
- if (!this.#triggerMethod("renderOnChange")) {
1843
- TemplatedView.prototype.renderOnChange.apply(this);
1844
- }
1900
+ this.#triggerAltMethod("renderOnChange");
1845
1901
  }
1846
1902
  render() {
1847
1903
  const config = this.#config;
@@ -1850,33 +1906,32 @@ var createViewWithReplacements = (options) => {
1850
1906
  if (ReplacementClass && !this.#replacement) {
1851
1907
  this.#replacement = new ReplacementClass(config);
1852
1908
  }
1853
- if (!this.#triggerMethod("render")) {
1854
- TemplatedView.prototype.render.apply(this);
1855
- }
1909
+ this.#triggerAltMethod("render");
1856
1910
  }
1857
1911
  onDestroy() {
1858
- if (this.#triggerMethod("onDestroy")) {
1859
- this.#replacement = null;
1860
- }
1861
- TemplatedView.prototype.onDestroy.apply(this);
1912
+ this.#triggerAltMethod("onDestroy");
1862
1913
  }
1863
1914
  _afterRender() {
1864
- if (!this.#triggerMethod("_afterRender")) {
1865
- TemplatedView.prototype._afterRender.apply(this);
1866
- }
1915
+ this.#triggerAltMethod("_afterRender");
1867
1916
  }
1868
1917
  _beforeRender() {
1869
- if (!this.#triggerMethod("_beforeRender")) {
1870
- TemplatedView.prototype._beforeRender.apply(this);
1918
+ this.#triggerAltMethod("_beforeRender");
1919
+ }
1920
+ #triggerAltMethod(methodKey) {
1921
+ const baseMethod = TemplatedView.prototype[methodKey].bind(this);
1922
+ const shouldReplace = this.#replacement?.shouldRenderReplacement();
1923
+ const altMethod = shouldReplace && this.#replacement?.[methodKey]?.bind(this.#replacement);
1924
+ if (!altMethod || !shouldReplace) {
1925
+ return baseMethod();
1871
1926
  }
1872
- }
1873
- #triggerMethod(methodKey) {
1874
- const method = this.#replacement?.shouldRenderReplacement() && this.#replacement[methodKey]?.bind(this.#replacement);
1875
- if (!method || typeof method !== "function") {
1876
- return false;
1927
+ const renderTiming = this.#replacement?.originalMethodsToTrigger()[methodKey] ?? "never";
1928
+ if (renderTiming === "before") {
1929
+ baseMethod();
1930
+ }
1931
+ altMethod();
1932
+ if (renderTiming === "after") {
1933
+ baseMethod();
1877
1934
  }
1878
- method();
1879
- return true;
1880
1935
  }
1881
1936
  };
1882
1937
  };
@@ -3193,7 +3248,7 @@ import {
3193
3248
  } from "@elementor/editor-elements";
3194
3249
  import { notify } from "@elementor/editor-notifications";
3195
3250
  import { blockCommand } from "@elementor/editor-v1-adapters";
3196
- import { __ } from "@wordpress/i18n";
3251
+ import { __ as __2 } from "@wordpress/i18n";
3197
3252
  function initLinkInLinkPrevention() {
3198
3253
  blockCommand({
3199
3254
  command: "document/elements/paste",
@@ -3227,7 +3282,7 @@ function blockLinkInLinkPaste(args) {
3227
3282
  const sourceElements = data.clipboard.elements;
3228
3283
  const notification = {
3229
3284
  type: "default",
3230
- message: __(
3285
+ message: __2(
3231
3286
  "To paste a link to this element, first remove the link from it's parent container.",
3232
3287
  "elementor"
3233
3288
  ),
@@ -3246,7 +3301,7 @@ function blockLinkInLinkMove(args) {
3246
3301
  const targetElement = target;
3247
3302
  const notification = {
3248
3303
  type: "default",
3249
- message: __("To drag a link to this element, first remove the link from it's parent container.", "elementor"),
3304
+ message: __2("To drag a link to this element, first remove the link from it's parent container.", "elementor"),
3250
3305
  id: "move-in-link-blocked",
3251
3306
  additionalActionProps: [learnMoreActionProps]
3252
3307
  };
@@ -3287,13 +3342,13 @@ import {
3287
3342
  updateElementStyle as updateElementStyle2
3288
3343
  } from "@elementor/editor-elements";
3289
3344
  import { ELEMENTS_STYLES_RESERVED_LABEL } from "@elementor/editor-styles-repository";
3290
- import { undoable } from "@elementor/editor-v1-adapters";
3291
- import { __ as __3 } from "@wordpress/i18n";
3345
+ import { undoable as undoable2 } from "@elementor/editor-v1-adapters";
3346
+ import { __ as __4 } from "@wordpress/i18n";
3292
3347
 
3293
3348
  // src/style-commands/utils.ts
3294
- import { getElementLabel, getWidgetsCache as getWidgetsCache7 } from "@elementor/editor-elements";
3349
+ import { getElementLabel as getElementLabel2, getWidgetsCache as getWidgetsCache7 } from "@elementor/editor-elements";
3295
3350
  import { CLASSES_PROP_KEY } from "@elementor/editor-props";
3296
- import { __ as __2 } from "@wordpress/i18n";
3351
+ import { __ as __3 } from "@wordpress/i18n";
3297
3352
  function hasAtomicWidgets(args) {
3298
3353
  const { containers = [args.container] } = args;
3299
3354
  return containers.some(isAtomicWidget);
@@ -3329,11 +3384,11 @@ function getClipboardElements(storageKey = "clipboard") {
3329
3384
  }
3330
3385
  }
3331
3386
  function getTitleForContainers(containers) {
3332
- return containers.length > 1 ? __2("Elements", "elementor") : getElementLabel(containers[0].id);
3387
+ return containers.length > 1 ? __3("Elements", "elementor") : getElementLabel2(containers[0].id);
3333
3388
  }
3334
3389
 
3335
3390
  // src/style-commands/undoable-actions/paste-element-style.ts
3336
- var undoablePasteElementStyle = () => undoable(
3391
+ var undoablePasteElementStyle = () => undoable2(
3337
3392
  {
3338
3393
  do: ({ containers, newStyle }) => {
3339
3394
  return containers.map((container) => {
@@ -3402,7 +3457,7 @@ var undoablePasteElementStyle = () => undoable(
3402
3457
  },
3403
3458
  {
3404
3459
  title: ({ containers }) => getTitleForContainers(containers),
3405
- subtitle: __3("Style Pasted", "elementor")
3460
+ subtitle: __4("Style Pasted", "elementor")
3406
3461
  }
3407
3462
  );
3408
3463
 
@@ -3447,9 +3502,9 @@ import {
3447
3502
  // src/style-commands/undoable-actions/reset-element-style.ts
3448
3503
  import { createElementStyle as createElementStyle3, deleteElementStyle as deleteElementStyle2, getElementStyles as getElementStyles4 } from "@elementor/editor-elements";
3449
3504
  import { ELEMENTS_STYLES_RESERVED_LABEL as ELEMENTS_STYLES_RESERVED_LABEL2 } from "@elementor/editor-styles-repository";
3450
- import { undoable as undoable2 } from "@elementor/editor-v1-adapters";
3451
- import { __ as __4 } from "@wordpress/i18n";
3452
- var undoableResetElementStyle = () => undoable2(
3505
+ import { undoable as undoable3 } from "@elementor/editor-v1-adapters";
3506
+ import { __ as __5 } from "@wordpress/i18n";
3507
+ var undoableResetElementStyle = () => undoable3(
3453
3508
  {
3454
3509
  do: ({ containers }) => {
3455
3510
  return containers.map((container) => {
@@ -3486,7 +3541,7 @@ var undoableResetElementStyle = () => undoable2(
3486
3541
  },
3487
3542
  {
3488
3543
  title: ({ containers }) => getTitleForContainers(containers),
3489
- subtitle: __4("Style Reset", "elementor")
3544
+ subtitle: __5("Style Reset", "elementor")
3490
3545
  }
3491
3546
  );
3492
3547
 
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.0-403",
4
+ "version": "3.35.0-405",
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.0-403",
41
- "@elementor/editor-controls": "3.35.0-403",
42
- "@elementor/editor-documents": "3.35.0-403",
43
- "@elementor/editor-elements": "3.35.0-403",
44
- "@elementor/editor-interactions": "3.35.0-403",
45
- "@elementor/editor-mcp": "3.35.0-403",
46
- "@elementor/editor-notifications": "3.35.0-403",
47
- "@elementor/editor-props": "3.35.0-403",
48
- "@elementor/editor-responsive": "3.35.0-403",
49
- "@elementor/editor-styles": "3.35.0-403",
50
- "@elementor/editor-styles-repository": "3.35.0-403",
51
- "@elementor/editor-ui": "3.35.0-403",
52
- "@elementor/editor-v1-adapters": "3.35.0-403",
53
- "@elementor/schema": "3.35.0-403",
54
- "@elementor/twing": "3.35.0-403",
40
+ "@elementor/editor": "3.35.0-405",
41
+ "@elementor/editor-controls": "3.35.0-405",
42
+ "@elementor/editor-documents": "3.35.0-405",
43
+ "@elementor/editor-elements": "3.35.0-405",
44
+ "@elementor/editor-interactions": "3.35.0-405",
45
+ "@elementor/editor-mcp": "3.35.0-405",
46
+ "@elementor/editor-notifications": "3.35.0-405",
47
+ "@elementor/editor-props": "3.35.0-405",
48
+ "@elementor/editor-responsive": "3.35.0-405",
49
+ "@elementor/editor-styles": "3.35.0-405",
50
+ "@elementor/editor-styles-repository": "3.35.0-405",
51
+ "@elementor/editor-ui": "3.35.0-405",
52
+ "@elementor/editor-v1-adapters": "3.35.0-405",
53
+ "@elementor/schema": "3.35.0-405",
54
+ "@elementor/twing": "3.35.0-405",
55
55
  "@elementor/ui": "1.36.17",
56
- "@elementor/utils": "3.35.0-403",
57
- "@elementor/wp-media": "3.35.0-403",
56
+ "@elementor/utils": "3.35.0-405",
57
+ "@elementor/wp-media": "3.35.0-405",
58
58
  "@floating-ui/react": "^0.27.5",
59
59
  "@wordpress/i18n": "^5.13.0"
60
60
  },
@@ -1,5 +1,14 @@
1
1
  import { type ReplacementSettings } from '../types';
2
2
 
3
+ export type TriggerMethod = 'render' | 'renderOnChange' | 'onDestroy' | '_beforeRender' | '_afterRender';
4
+ export type TriggerTiming = 'before' | 'after' | 'never';
5
+
6
+ export const TRIGGER_TIMING: Record< string, TriggerTiming > = {
7
+ before: 'before',
8
+ after: 'after',
9
+ never: 'never',
10
+ };
11
+
3
12
  export interface ReplacementBaseInterface {
4
13
  renderOnChange?: () => void;
5
14
  onDestroy?: () => void;
@@ -7,9 +16,10 @@ export interface ReplacementBaseInterface {
7
16
  _afterRender?: () => void;
8
17
  shouldRenderReplacement: () => boolean;
9
18
  render?: () => void;
19
+ originalMethodsToTrigger: () => Partial< Record< TriggerMethod, TriggerTiming > >;
10
20
  }
11
21
 
12
- export default class ReplacementBase implements ReplacementBaseInterface {
22
+ export class ReplacementBase implements ReplacementBaseInterface {
13
23
  protected getSetting: ReplacementSettings[ 'getSetting' ];
14
24
  protected setSetting: ReplacementSettings[ 'setSetting' ];
15
25
  protected element: ReplacementSettings[ 'element' ];
@@ -33,4 +43,14 @@ export default class ReplacementBase implements ReplacementBaseInterface {
33
43
  shouldRenderReplacement(): boolean {
34
44
  return true;
35
45
  }
46
+
47
+ originalMethodsToTrigger() {
48
+ return {
49
+ _beforeRender: TRIGGER_TIMING.before,
50
+ _afterRender: TRIGGER_TIMING.after,
51
+ renderOnChange: TRIGGER_TIMING.never,
52
+ onDestroy: TRIGGER_TIMING.never,
53
+ render: TRIGGER_TIMING.never,
54
+ };
55
+ }
36
56
  }
@@ -1,20 +1,22 @@
1
1
  import * as React from 'react';
2
- import { useCallback, useEffect, useRef, useState } from 'react';
2
+ import { useEffect, useRef, useState } from 'react';
3
3
  import { createRoot, type Root } from 'react-dom/client';
4
4
  import { InlineEditor } from '@elementor/editor-controls';
5
- import { getContainer, getElementType } from '@elementor/editor-elements';
5
+ import { getContainer, getElementLabel, getElementType } from '@elementor/editor-elements';
6
6
  import {
7
7
  htmlPropTypeUtil,
8
+ type HtmlPropValue,
8
9
  type PropType,
9
10
  stringPropTypeUtil,
10
11
  type StringPropValue,
11
12
  type TransformablePropValue,
12
13
  } from '@elementor/editor-props';
13
- import { __privateRunCommandSync as runCommandSync, isExperimentActive } from '@elementor/editor-v1-adapters';
14
+ import { __privateRunCommandSync as runCommandSync, isExperimentActive, undoable } from '@elementor/editor-v1-adapters';
14
15
  import { Box, ThemeProvider } from '@elementor/ui';
16
+ import { __ } from '@wordpress/i18n';
15
17
 
16
18
  import { OutlineOverlay } from '../../../components/outline-overlay';
17
- import ReplacementBase from '../base';
19
+ import { ReplacementBase, TRIGGER_TIMING } from '../base';
18
20
  import { getInitialPopoverPosition, INLINE_EDITING_PROPERTY_PER_TYPE } from './inline-editing-utils';
19
21
 
20
22
  const EXPERIMENT_KEY = 'v4-inline-text-editing';
@@ -25,6 +27,15 @@ type TagPropType = PropType< 'tag' > & {
25
27
  };
26
28
  };
27
29
 
30
+ const HISTORY_DEBOUNCE_WAIT = 800;
31
+
32
+ const TOP_BAR_SELECTOR = '#elementor-editor-wrapper-v2';
33
+ const NAVIGATOR_SELECTOR = '#elementor-navigator';
34
+ const V4_EDITING_PANEL = 'main.MuiBox-root';
35
+ const V3_EDITING_PANEL = '#elementor-panel-content-wrapper';
36
+
37
+ const BLUR_TRIGGERING_SELECTORS = [ TOP_BAR_SELECTOR, NAVIGATOR_SELECTOR, V4_EDITING_PANEL, V3_EDITING_PANEL ];
38
+
28
39
  export default class InlineEditingReplacement extends ReplacementBase {
29
40
  private inlineEditorRoot: Root | null = null;
30
41
  private handlerAttached = false;
@@ -45,12 +56,12 @@ export default class InlineEditingReplacement extends ReplacementBase {
45
56
  return isExperimentActive( EXPERIMENT_KEY ) && ! this.isValueDynamic();
46
57
  }
47
58
 
48
- handleRenderInlineEditor = ( event: Event ) => {
49
- event.stopPropagation();
50
-
51
- if ( ! this.isValueDynamic() ) {
52
- this.renderInlineEditor();
59
+ handleRenderInlineEditor = () => {
60
+ if ( this.isEditingModeActive() || this.isValueDynamic() ) {
61
+ return;
53
62
  }
63
+
64
+ this.renderInlineEditor();
54
65
  };
55
66
 
56
67
  renderOnChange() {
@@ -65,19 +76,32 @@ export default class InlineEditingReplacement extends ReplacementBase {
65
76
  this.resetInlineEditorRoot();
66
77
  }
67
78
 
68
- _beforeRender(): void {
79
+ _beforeRender() {
69
80
  this.resetInlineEditorRoot();
70
81
  }
71
82
 
72
83
  _afterRender() {
73
84
  if ( ! this.isValueDynamic() && ! this.handlerAttached ) {
74
- this.element.addEventListener( 'dblclick', this.handleRenderInlineEditor, { once: true } );
85
+ this.element.addEventListener( 'click', this.handleRenderInlineEditor );
75
86
  this.handlerAttached = true;
76
87
  }
77
88
  }
78
89
 
90
+ originalMethodsToTrigger() {
91
+ const before = this.isEditingModeActive() ? TRIGGER_TIMING.never : TRIGGER_TIMING.before;
92
+ const after = this.isEditingModeActive() ? TRIGGER_TIMING.never : TRIGGER_TIMING.after;
93
+
94
+ return {
95
+ _beforeRender: before,
96
+ _afterRender: after,
97
+ renderOnChange: after,
98
+ onDestroy: TRIGGER_TIMING.after,
99
+ render: before,
100
+ };
101
+ }
102
+
79
103
  resetInlineEditorRoot() {
80
- this.element.removeEventListener( 'dblclick', this.handleRenderInlineEditor );
104
+ this.element.removeEventListener( 'click', this.handleRenderInlineEditor );
81
105
  this.handlerAttached = false;
82
106
  this.inlineEditorRoot?.unmount?.();
83
107
  this.inlineEditorRoot = null;
@@ -124,12 +148,38 @@ export default class InlineEditingReplacement extends ReplacementBase {
124
148
  const settingKey = this.getInlineEditablePropertyName();
125
149
  const valueToSave = value ? htmlPropTypeUtil.create( value ) : null;
126
150
 
151
+ undoable(
152
+ {
153
+ do: () => {
154
+ const prevValue = this.getContentValue();
155
+
156
+ this.runCommand( settingKey, valueToSave );
157
+
158
+ return prevValue;
159
+ },
160
+ undo: ( prevValue ) => {
161
+ this.runCommand( settingKey, prevValue ?? null );
162
+ },
163
+ },
164
+ {
165
+ title: getElementLabel( this.id ),
166
+ // translators: %s is the name of the property that was edited.
167
+ subtitle: __( '%s edited', 'elementor' ).replace(
168
+ '%s',
169
+ this.getHtmlPropType()?.key ?? 'Inline editing'
170
+ ),
171
+ debounce: { wait: HISTORY_DEBOUNCE_WAIT },
172
+ }
173
+ )();
174
+ }
175
+
176
+ runCommand( key: string, value: HtmlPropValue | null ) {
127
177
  runCommandSync(
128
178
  'document/elements/set-settings',
129
179
  {
130
180
  container: getContainer( this.id ),
131
181
  settings: {
132
- [ settingKey ]: valueToSave,
182
+ [ key ]: value,
133
183
  },
134
184
  },
135
185
  { internal: true }
@@ -165,16 +215,16 @@ export default class InlineEditingReplacement extends ReplacementBase {
165
215
  }
166
216
 
167
217
  renderInlineEditor() {
218
+ if ( this.isEditingModeActive() ) {
219
+ this.resetInlineEditorRoot();
220
+ }
221
+
168
222
  const InlineEditorApp = this.InlineEditorApp;
169
223
  const wrapperClasses = 'elementor';
170
224
  const elementClasses = this.element.children?.[ 0 ]?.classList.toString() ?? '';
171
225
 
172
226
  this.element.innerHTML = '';
173
227
 
174
- if ( this.inlineEditorRoot ) {
175
- this.resetInlineEditorRoot();
176
- }
177
-
178
228
  this.inlineEditorRoot = createRoot( this.element );
179
229
  this.inlineEditorRoot.render(
180
230
  <InlineEditorApp wrapperClasses={ wrapperClasses } elementClasses={ elementClasses } />
@@ -188,16 +238,23 @@ export default class InlineEditingReplacement extends ReplacementBase {
188
238
  const [ isWrapperRendered, setIsWrapperRendered ] = useState( false );
189
239
 
190
240
  useEffect( () => {
191
- const panel = document?.querySelector( 'main.MuiBox-root' );
192
-
193
241
  setIsWrapperRendered( !! wrapperRef.current );
194
- panel?.addEventListener( 'click', asyncUnmountInlineEditor );
195
-
196
- return () => panel?.removeEventListener( 'click', asyncUnmountInlineEditor );
242
+ BLUR_TRIGGERING_SELECTORS.forEach(
243
+ ( selector ) =>
244
+ document?.querySelector( selector )?.addEventListener( 'mousedown', asyncUnmountInlineEditor )
245
+ );
246
+
247
+ return () =>
248
+ BLUR_TRIGGERING_SELECTORS.forEach(
249
+ ( selector ) =>
250
+ document
251
+ ?.querySelector( selector )
252
+ ?.removeEventListener( 'mousedown', asyncUnmountInlineEditor )
253
+ );
197
254
  // eslint-disable-next-line react-hooks/exhaustive-deps
198
255
  }, [] );
199
256
 
200
- const asyncUnmountInlineEditor = useCallback(
257
+ const asyncUnmountInlineEditor = React.useCallback(
201
258
  () => queueMicrotask( this.unmountInlineEditor.bind( this ) ),
202
259
  []
203
260
  );
@@ -1,8 +1,7 @@
1
1
  import type { CreateTemplatedElementTypeOptions } from '../create-templated-element-type';
2
2
  import { createTemplatedElementView } from '../create-templated-element-type';
3
3
  import type { ElementType, ElementView, LegacyWindow, ReplacementSettings } from '../types';
4
- import type ReplacementBase from './base';
5
- import { type ReplacementBaseInterface } from './base';
4
+ import { type ReplacementBase, type ReplacementBaseInterface, type TriggerMethod } from './base';
6
5
  import InlineEditingReplacement from './inline-editing/inline-editing-elements';
7
6
 
8
7
  type ReplacementConstructor = new ( settings: ReplacementSettings ) => ReplacementBase;
@@ -55,9 +54,7 @@ export const createViewWithReplacements = ( options: CreateTemplatedElementTypeO
55
54
  }
56
55
 
57
56
  renderOnChange(): void {
58
- if ( ! this.#triggerMethod( 'renderOnChange' ) ) {
59
- TemplatedView.prototype.renderOnChange.apply( this );
60
- }
57
+ this.#triggerAltMethod( 'renderOnChange' );
61
58
  }
62
59
 
63
60
  render() {
@@ -69,43 +66,41 @@ export const createViewWithReplacements = ( options: CreateTemplatedElementTypeO
69
66
  this.#replacement = new ReplacementClass( config );
70
67
  }
71
68
 
72
- if ( ! this.#triggerMethod( 'render' ) ) {
73
- TemplatedView.prototype.render.apply( this );
74
- }
69
+ this.#triggerAltMethod( 'render' );
75
70
  }
76
71
 
77
72
  onDestroy() {
78
- if ( this.#triggerMethod( 'onDestroy' ) ) {
79
- this.#replacement = null;
80
- }
81
-
82
- TemplatedView.prototype.onDestroy.apply( this );
73
+ this.#triggerAltMethod( 'onDestroy' );
83
74
  }
84
75
 
85
76
  _afterRender() {
86
- if ( ! this.#triggerMethod( '_afterRender' ) ) {
87
- TemplatedView.prototype._afterRender.apply( this );
88
- }
77
+ this.#triggerAltMethod( '_afterRender' );
89
78
  }
90
79
 
91
80
  _beforeRender(): void {
92
- if ( ! this.#triggerMethod( '_beforeRender' ) ) {
93
- TemplatedView.prototype._beforeRender.apply( this );
94
- }
81
+ this.#triggerAltMethod( '_beforeRender' );
95
82
  }
96
83
 
97
- #triggerMethod( methodKey: keyof ReplacementBaseInterface ) {
98
- const method =
99
- this.#replacement?.shouldRenderReplacement() &&
100
- this.#replacement[ methodKey ]?.bind( this.#replacement );
84
+ #triggerAltMethod( methodKey: keyof ReplacementBaseInterface ) {
85
+ const baseMethod = TemplatedView.prototype[ methodKey as TriggerMethod ].bind( this );
86
+ const shouldReplace = this.#replacement?.shouldRenderReplacement();
87
+ const altMethod = shouldReplace && this.#replacement?.[ methodKey ]?.bind( this.#replacement );
101
88
 
102
- if ( ! method || typeof method !== 'function' ) {
103
- return false;
89
+ if ( ! altMethod || ! shouldReplace ) {
90
+ return baseMethod();
104
91
  }
105
92
 
106
- method();
93
+ const renderTiming = this.#replacement?.originalMethodsToTrigger()[ methodKey as TriggerMethod ] ?? 'never';
94
+
95
+ if ( renderTiming === 'before' ) {
96
+ baseMethod();
97
+ }
107
98
 
108
- return true;
99
+ altMethod();
100
+
101
+ if ( renderTiming === 'after' ) {
102
+ baseMethod();
103
+ }
109
104
  }
110
105
  };
111
106
  };