@elementor/editor-canvas 4.4.0-1144 → 4.4.0-1146

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
@@ -1799,6 +1799,7 @@ var import_editor_elements4 = require("@elementor/editor-elements");
1799
1799
  var FORM_ELEMENT_TYPE = "e-form";
1800
1800
  var FORM_HIDDEN_FIELD_ELEMENT_TYPE = "e-form-hidden";
1801
1801
  var FORM_HONEYPOT_ELEMENT_TYPE = "e-form-honeypot";
1802
+ var FORM_RECAPTCHA_SCORE_ELEMENT_TYPE = "e-form-recaptcha-score";
1802
1803
  var FORM_FIELD_ELEMENT_TYPES = /* @__PURE__ */ new Set([
1803
1804
  "e-form-input",
1804
1805
  "e-form-textarea",
@@ -1811,7 +1812,8 @@ var FORM_FIELD_ELEMENT_TYPES = /* @__PURE__ */ new Set([
1811
1812
  "e-form-date-picker",
1812
1813
  "e-form-time-picker",
1813
1814
  FORM_HIDDEN_FIELD_ELEMENT_TYPE,
1814
- FORM_HONEYPOT_ELEMENT_TYPE
1815
+ FORM_HONEYPOT_ELEMENT_TYPE,
1816
+ FORM_RECAPTCHA_SCORE_ELEMENT_TYPE
1815
1817
  ]);
1816
1818
  function getArgsElementType(args) {
1817
1819
  return args.model?.widgetType || args.model?.elType;
@@ -1947,112 +1949,128 @@ function handleBlockedFormField() {
1947
1949
  (0, import_editor_notifications.notify)(FORM_FIELDS_OUTSIDE_ALERT);
1948
1950
  }
1949
1951
 
1950
- // src/form-structure/hidden-field-hint.ts
1952
+ // src/form-structure/form-atom-added-hints.ts
1951
1953
  var import_editor_notifications2 = require("@elementor/editor-notifications");
1952
1954
  var import_editor_v1_adapters12 = require("@elementor/editor-v1-adapters");
1953
1955
  var import_i18n2 = require("@wordpress/i18n");
1954
1956
  var MOVE_COMMAND = "document/elements/move";
1957
+ var FORM_ATOM_HINTS = {
1958
+ [FORM_HONEYPOT_ELEMENT_TYPE]: {
1959
+ idPrefix: "honeypot-added",
1960
+ getMessage: () => (0, import_i18n2.__)("Honeypot added - invisible on your form. Reorder it in the Structure panel.", "elementor")
1961
+ },
1962
+ [FORM_RECAPTCHA_SCORE_ELEMENT_TYPE]: {
1963
+ idPrefix: "recaptcha-score-added",
1964
+ getMessage: () => (0, import_i18n2.__)("Score-based reCAPTCHA added - You can adjust its settings from the Structure panel.", "elementor")
1965
+ }
1966
+ };
1955
1967
  var notificationCounter = 0;
1956
- function createHiddenFieldNotification() {
1957
- return {
1958
- type: "default",
1959
- message: (0, import_i18n2.__)("Hidden field added - invisible on your form. Manage it in the Structure panel.", "elementor"),
1960
- id: `hidden-field-added-${++notificationCounter}`
1961
- };
1962
- }
1963
- function initHiddenFieldHint() {
1968
+ function initFormAtomAddedHints() {
1964
1969
  (0, import_editor_v1_adapters12.registerDataHook)("after", "document/elements/create", (args, _result, options) => {
1965
1970
  if (options?.commandsCurrentTrace?.includes(MOVE_COMMAND)) {
1966
1971
  return;
1967
1972
  }
1968
- if (getArgsElementType(args) === FORM_HIDDEN_FIELD_ELEMENT_TYPE) {
1969
- (0, import_editor_notifications2.notify)(createHiddenFieldNotification());
1973
+ const elementType = getArgsElementType(args);
1974
+ const hint = elementType ? FORM_ATOM_HINTS[elementType] : void 0;
1975
+ if (!hint) {
1976
+ return;
1970
1977
  }
1978
+ (0, import_editor_notifications2.notify)({
1979
+ type: "default",
1980
+ message: hint.getMessage(),
1981
+ id: `${hint.idPrefix}-${++notificationCounter}`
1982
+ });
1971
1983
  });
1972
1984
  }
1973
1985
 
1974
- // src/form-structure/honeypot-hint.ts
1986
+ // src/form-structure/hidden-field-hint.ts
1975
1987
  var import_editor_notifications3 = require("@elementor/editor-notifications");
1976
1988
  var import_editor_v1_adapters13 = require("@elementor/editor-v1-adapters");
1977
1989
  var import_i18n3 = require("@wordpress/i18n");
1978
1990
  var MOVE_COMMAND2 = "document/elements/move";
1979
1991
  var notificationCounter2 = 0;
1980
- function createHoneypotNotification() {
1992
+ function createHiddenFieldNotification() {
1981
1993
  return {
1982
1994
  type: "default",
1983
- message: (0, import_i18n3.__)("Honeypot added - invisible on your form. Reorder it in the Structure panel.", "elementor"),
1984
- id: `honeypot-added-${++notificationCounter2}`
1995
+ message: (0, import_i18n3.__)("Hidden field added - invisible on your form. Manage it in the Structure panel.", "elementor"),
1996
+ id: `hidden-field-added-${++notificationCounter2}`
1985
1997
  };
1986
1998
  }
1987
- function initHoneypotHint() {
1999
+ function initHiddenFieldHint() {
1988
2000
  (0, import_editor_v1_adapters13.registerDataHook)("after", "document/elements/create", (args, _result, options) => {
1989
2001
  if (options?.commandsCurrentTrace?.includes(MOVE_COMMAND2)) {
1990
2002
  return;
1991
2003
  }
1992
- if (getArgsElementType(args) === FORM_HONEYPOT_ELEMENT_TYPE) {
1993
- (0, import_editor_notifications3.notify)(createHoneypotNotification());
2004
+ if (getArgsElementType(args) === FORM_HIDDEN_FIELD_ELEMENT_TYPE) {
2005
+ (0, import_editor_notifications3.notify)(createHiddenFieldNotification());
1994
2006
  }
1995
2007
  });
1996
2008
  }
1997
2009
 
1998
- // src/form-structure/prevent-duplicate-honeypot-commands.ts
2010
+ // src/form-structure/prevent-duplicate-form-atoms-commands.ts
1999
2011
  var import_editor_elements5 = require("@elementor/editor-elements");
2000
2012
  var import_editor_notifications4 = require("@elementor/editor-notifications");
2001
2013
  var import_editor_v1_adapters14 = require("@elementor/editor-v1-adapters");
2002
2014
  var import_i18n4 = require("@wordpress/i18n");
2003
2015
  var MOVE_COMMAND3 = "document/elements/move";
2004
- var DUPLICATE_HONEYPOT_ALERT = {
2005
- type: "default",
2006
- message: (0, import_i18n4.__)("Only one Honeypot can be added per form.", "elementor"),
2007
- id: "duplicate-honeypot-blocked"
2016
+ var SINGLE_INSTANCE_FORM_ATOM_ALERTS = {
2017
+ [FORM_HONEYPOT_ELEMENT_TYPE]: {
2018
+ type: "default",
2019
+ message: (0, import_i18n4.__)("Only one Honeypot can be added per form.", "elementor"),
2020
+ id: "duplicate-honeypot-blocked"
2021
+ },
2022
+ [FORM_RECAPTCHA_SCORE_ELEMENT_TYPE]: {
2023
+ type: "default",
2024
+ message: (0, import_i18n4.__)("Only one Score-based reCAPTCHA can be added per form.", "elementor"),
2025
+ id: "duplicate-recaptcha-score-blocked"
2026
+ }
2008
2027
  };
2009
- function initDuplicateHoneypotPrevention() {
2028
+ var SINGLE_INSTANCE_FORM_ATOM_TYPES = Object.keys(SINGLE_INSTANCE_FORM_ATOM_ALERTS);
2029
+ function initDuplicateFormAtomsPrevention() {
2010
2030
  (0, import_editor_v1_adapters14.registerDataHook)("dependency", "document/elements/create", (args, options) => {
2011
- return !blockHoneypotCreate(args, options);
2031
+ return !blockDuplicateCreate(args, options);
2012
2032
  });
2013
2033
  (0, import_editor_v1_adapters14.blockCommand)({
2014
2034
  command: "document/elements/move",
2015
- condition: blockHoneypotMove
2035
+ condition: blockDuplicateMove
2016
2036
  });
2017
2037
  }
2018
- function blockHoneypotCreate(args, options) {
2038
+ function blockDuplicateCreate(args, options) {
2019
2039
  if (options?.commandsCurrentTrace?.includes(MOVE_COMMAND3)) {
2020
2040
  return false;
2021
2041
  }
2022
- if (!hasModelElementType(args.model, FORM_HONEYPOT_ELEMENT_TYPE)) {
2023
- return false;
2024
- }
2025
2042
  const containers = args.containers ?? [args.container];
2026
- if (containers.some((container) => formHasHoneypot(getFormContainer(container)))) {
2027
- handleBlockedHoneypot();
2028
- return true;
2043
+ const blockedType = SINGLE_INSTANCE_FORM_ATOM_TYPES.find(
2044
+ (type) => hasModelElementType(args.model, type) && containers.some((container) => formHasElementType(getFormContainer(container), type))
2045
+ );
2046
+ if (!blockedType) {
2047
+ return false;
2029
2048
  }
2030
- return false;
2049
+ (0, import_editor_notifications4.notify)(SINGLE_INSTANCE_FORM_ATOM_ALERTS[blockedType]);
2050
+ return true;
2031
2051
  }
2032
- function blockHoneypotMove(args) {
2052
+ function blockDuplicateMove(args) {
2033
2053
  const { containers = [args.container], target } = args;
2034
2054
  const movedContainers = containers.filter((container) => !!container);
2035
- if (!movedContainers.some((container) => hasElementType(container, FORM_HONEYPOT_ELEMENT_TYPE))) {
2036
- return false;
2037
- }
2038
2055
  const movedElements = new Set(movedContainers.flatMap((container) => (0, import_editor_elements5.getAllDescendants)(container)));
2039
- if (formHasHoneypot(getFormContainer(target), movedElements)) {
2040
- handleBlockedHoneypot();
2041
- return true;
2056
+ const targetForm = getFormContainer(target);
2057
+ const blockedType = SINGLE_INSTANCE_FORM_ATOM_TYPES.find(
2058
+ (type) => movedContainers.some((container) => hasElementType(container, type)) && formHasElementType(targetForm, type, movedElements)
2059
+ );
2060
+ if (!blockedType) {
2061
+ return false;
2042
2062
  }
2043
- return false;
2063
+ (0, import_editor_notifications4.notify)(SINGLE_INSTANCE_FORM_ATOM_ALERTS[blockedType]);
2064
+ return true;
2044
2065
  }
2045
- function formHasHoneypot(form, excluded = /* @__PURE__ */ new Set()) {
2066
+ function formHasElementType(form, type, excluded = /* @__PURE__ */ new Set()) {
2046
2067
  if (!form) {
2047
2068
  return false;
2048
2069
  }
2049
2070
  return (0, import_editor_elements5.getAllDescendants)(form).some(
2050
- (element) => !excluded.has(element) && getElementType(element) === FORM_HONEYPOT_ELEMENT_TYPE
2071
+ (element) => !excluded.has(element) && getElementType(element) === type
2051
2072
  );
2052
2073
  }
2053
- function handleBlockedHoneypot() {
2054
- (0, import_editor_notifications4.notify)(DUPLICATE_HONEYPOT_ALERT);
2055
- }
2056
2074
 
2057
2075
  // src/form-structure/prevent-form-nesting-commands.ts
2058
2076
  var import_editor_notifications5 = require("@elementor/editor-notifications");
@@ -2312,7 +2330,6 @@ var svgSrcTransformer = createTransformer(async (value, { signal }) => {
2312
2330
  var EMPTY_ICON_RESULT = { html: null, url: null };
2313
2331
  var ICON_SVG_SIZE = "100%";
2314
2332
  var ICON_SVG_OVERFLOW = "visible";
2315
- var FONT_AWESOME_LIBRARY_PREFIX = "fa-";
2316
2333
  var EICONS_LIBRARY = "eicons";
2317
2334
  var DEFAULT_SVG_RELATIVE_PATH = "images/default-svg.svg";
2318
2335
  var iconTransformer = createTransformer(async (value, { signal }) => {
@@ -2325,7 +2342,7 @@ var iconTransformer = createTransformer(async (value, { signal }) => {
2325
2342
  if (fontAwesomeHtml) {
2326
2343
  return { html: fontAwesomeHtml, url: null };
2327
2344
  }
2328
- if (library.startsWith(FONT_AWESOME_LIBRARY_PREFIX) || EICONS_LIBRARY === library) {
2345
+ if (EICONS_LIBRARY === library) {
2329
2346
  return EMPTY_ICON_RESULT;
2330
2347
  }
2331
2348
  const providerHtml = await resolveProviderSvg(library, iconValue, signal);
@@ -6087,8 +6104,8 @@ function init() {
6087
6104
  initFormNestingPrevention();
6088
6105
  initFormAncestorEnforcement();
6089
6106
  initHiddenFieldHint();
6090
- initDuplicateHoneypotPrevention();
6091
- initHoneypotHint();
6107
+ initDuplicateFormAtomsPrevention();
6108
+ initFormAtomAddedHints();
6092
6109
  initViewReplacements();
6093
6110
  initLegacyViews();
6094
6111
  initSettingsTransformers();
package/dist/index.mjs CHANGED
@@ -1751,6 +1751,7 @@ import { getAllDescendants } from "@elementor/editor-elements";
1751
1751
  var FORM_ELEMENT_TYPE = "e-form";
1752
1752
  var FORM_HIDDEN_FIELD_ELEMENT_TYPE = "e-form-hidden";
1753
1753
  var FORM_HONEYPOT_ELEMENT_TYPE = "e-form-honeypot";
1754
+ var FORM_RECAPTCHA_SCORE_ELEMENT_TYPE = "e-form-recaptcha-score";
1754
1755
  var FORM_FIELD_ELEMENT_TYPES = /* @__PURE__ */ new Set([
1755
1756
  "e-form-input",
1756
1757
  "e-form-textarea",
@@ -1763,7 +1764,8 @@ var FORM_FIELD_ELEMENT_TYPES = /* @__PURE__ */ new Set([
1763
1764
  "e-form-date-picker",
1764
1765
  "e-form-time-picker",
1765
1766
  FORM_HIDDEN_FIELD_ELEMENT_TYPE,
1766
- FORM_HONEYPOT_ELEMENT_TYPE
1767
+ FORM_HONEYPOT_ELEMENT_TYPE,
1768
+ FORM_RECAPTCHA_SCORE_ELEMENT_TYPE
1767
1769
  ]);
1768
1770
  function getArgsElementType(args) {
1769
1771
  return args.model?.widgetType || args.model?.elType;
@@ -1899,112 +1901,128 @@ function handleBlockedFormField() {
1899
1901
  notify(FORM_FIELDS_OUTSIDE_ALERT);
1900
1902
  }
1901
1903
 
1902
- // src/form-structure/hidden-field-hint.ts
1904
+ // src/form-structure/form-atom-added-hints.ts
1903
1905
  import { notify as notify2 } from "@elementor/editor-notifications";
1904
1906
  import { registerDataHook as registerDataHook3 } from "@elementor/editor-v1-adapters";
1905
1907
  import { __ as __2 } from "@wordpress/i18n";
1906
1908
  var MOVE_COMMAND = "document/elements/move";
1909
+ var FORM_ATOM_HINTS = {
1910
+ [FORM_HONEYPOT_ELEMENT_TYPE]: {
1911
+ idPrefix: "honeypot-added",
1912
+ getMessage: () => __2("Honeypot added - invisible on your form. Reorder it in the Structure panel.", "elementor")
1913
+ },
1914
+ [FORM_RECAPTCHA_SCORE_ELEMENT_TYPE]: {
1915
+ idPrefix: "recaptcha-score-added",
1916
+ getMessage: () => __2("Score-based reCAPTCHA added - You can adjust its settings from the Structure panel.", "elementor")
1917
+ }
1918
+ };
1907
1919
  var notificationCounter = 0;
1908
- function createHiddenFieldNotification() {
1909
- return {
1910
- type: "default",
1911
- message: __2("Hidden field added - invisible on your form. Manage it in the Structure panel.", "elementor"),
1912
- id: `hidden-field-added-${++notificationCounter}`
1913
- };
1914
- }
1915
- function initHiddenFieldHint() {
1920
+ function initFormAtomAddedHints() {
1916
1921
  registerDataHook3("after", "document/elements/create", (args, _result, options) => {
1917
1922
  if (options?.commandsCurrentTrace?.includes(MOVE_COMMAND)) {
1918
1923
  return;
1919
1924
  }
1920
- if (getArgsElementType(args) === FORM_HIDDEN_FIELD_ELEMENT_TYPE) {
1921
- notify2(createHiddenFieldNotification());
1925
+ const elementType = getArgsElementType(args);
1926
+ const hint = elementType ? FORM_ATOM_HINTS[elementType] : void 0;
1927
+ if (!hint) {
1928
+ return;
1922
1929
  }
1930
+ notify2({
1931
+ type: "default",
1932
+ message: hint.getMessage(),
1933
+ id: `${hint.idPrefix}-${++notificationCounter}`
1934
+ });
1923
1935
  });
1924
1936
  }
1925
1937
 
1926
- // src/form-structure/honeypot-hint.ts
1938
+ // src/form-structure/hidden-field-hint.ts
1927
1939
  import { notify as notify3 } from "@elementor/editor-notifications";
1928
1940
  import { registerDataHook as registerDataHook4 } from "@elementor/editor-v1-adapters";
1929
1941
  import { __ as __3 } from "@wordpress/i18n";
1930
1942
  var MOVE_COMMAND2 = "document/elements/move";
1931
1943
  var notificationCounter2 = 0;
1932
- function createHoneypotNotification() {
1944
+ function createHiddenFieldNotification() {
1933
1945
  return {
1934
1946
  type: "default",
1935
- message: __3("Honeypot added - invisible on your form. Reorder it in the Structure panel.", "elementor"),
1936
- id: `honeypot-added-${++notificationCounter2}`
1947
+ message: __3("Hidden field added - invisible on your form. Manage it in the Structure panel.", "elementor"),
1948
+ id: `hidden-field-added-${++notificationCounter2}`
1937
1949
  };
1938
1950
  }
1939
- function initHoneypotHint() {
1951
+ function initHiddenFieldHint() {
1940
1952
  registerDataHook4("after", "document/elements/create", (args, _result, options) => {
1941
1953
  if (options?.commandsCurrentTrace?.includes(MOVE_COMMAND2)) {
1942
1954
  return;
1943
1955
  }
1944
- if (getArgsElementType(args) === FORM_HONEYPOT_ELEMENT_TYPE) {
1945
- notify3(createHoneypotNotification());
1956
+ if (getArgsElementType(args) === FORM_HIDDEN_FIELD_ELEMENT_TYPE) {
1957
+ notify3(createHiddenFieldNotification());
1946
1958
  }
1947
1959
  });
1948
1960
  }
1949
1961
 
1950
- // src/form-structure/prevent-duplicate-honeypot-commands.ts
1962
+ // src/form-structure/prevent-duplicate-form-atoms-commands.ts
1951
1963
  import { getAllDescendants as getAllDescendants2 } from "@elementor/editor-elements";
1952
1964
  import { notify as notify4 } from "@elementor/editor-notifications";
1953
1965
  import { blockCommand as blockCommand2, registerDataHook as registerDataHook5 } from "@elementor/editor-v1-adapters";
1954
1966
  import { __ as __4 } from "@wordpress/i18n";
1955
1967
  var MOVE_COMMAND3 = "document/elements/move";
1956
- var DUPLICATE_HONEYPOT_ALERT = {
1957
- type: "default",
1958
- message: __4("Only one Honeypot can be added per form.", "elementor"),
1959
- id: "duplicate-honeypot-blocked"
1968
+ var SINGLE_INSTANCE_FORM_ATOM_ALERTS = {
1969
+ [FORM_HONEYPOT_ELEMENT_TYPE]: {
1970
+ type: "default",
1971
+ message: __4("Only one Honeypot can be added per form.", "elementor"),
1972
+ id: "duplicate-honeypot-blocked"
1973
+ },
1974
+ [FORM_RECAPTCHA_SCORE_ELEMENT_TYPE]: {
1975
+ type: "default",
1976
+ message: __4("Only one Score-based reCAPTCHA can be added per form.", "elementor"),
1977
+ id: "duplicate-recaptcha-score-blocked"
1978
+ }
1960
1979
  };
1961
- function initDuplicateHoneypotPrevention() {
1980
+ var SINGLE_INSTANCE_FORM_ATOM_TYPES = Object.keys(SINGLE_INSTANCE_FORM_ATOM_ALERTS);
1981
+ function initDuplicateFormAtomsPrevention() {
1962
1982
  registerDataHook5("dependency", "document/elements/create", (args, options) => {
1963
- return !blockHoneypotCreate(args, options);
1983
+ return !blockDuplicateCreate(args, options);
1964
1984
  });
1965
1985
  blockCommand2({
1966
1986
  command: "document/elements/move",
1967
- condition: blockHoneypotMove
1987
+ condition: blockDuplicateMove
1968
1988
  });
1969
1989
  }
1970
- function blockHoneypotCreate(args, options) {
1990
+ function blockDuplicateCreate(args, options) {
1971
1991
  if (options?.commandsCurrentTrace?.includes(MOVE_COMMAND3)) {
1972
1992
  return false;
1973
1993
  }
1974
- if (!hasModelElementType(args.model, FORM_HONEYPOT_ELEMENT_TYPE)) {
1975
- return false;
1976
- }
1977
1994
  const containers = args.containers ?? [args.container];
1978
- if (containers.some((container) => formHasHoneypot(getFormContainer(container)))) {
1979
- handleBlockedHoneypot();
1980
- return true;
1995
+ const blockedType = SINGLE_INSTANCE_FORM_ATOM_TYPES.find(
1996
+ (type) => hasModelElementType(args.model, type) && containers.some((container) => formHasElementType(getFormContainer(container), type))
1997
+ );
1998
+ if (!blockedType) {
1999
+ return false;
1981
2000
  }
1982
- return false;
2001
+ notify4(SINGLE_INSTANCE_FORM_ATOM_ALERTS[blockedType]);
2002
+ return true;
1983
2003
  }
1984
- function blockHoneypotMove(args) {
2004
+ function blockDuplicateMove(args) {
1985
2005
  const { containers = [args.container], target } = args;
1986
2006
  const movedContainers = containers.filter((container) => !!container);
1987
- if (!movedContainers.some((container) => hasElementType(container, FORM_HONEYPOT_ELEMENT_TYPE))) {
1988
- return false;
1989
- }
1990
2007
  const movedElements = new Set(movedContainers.flatMap((container) => getAllDescendants2(container)));
1991
- if (formHasHoneypot(getFormContainer(target), movedElements)) {
1992
- handleBlockedHoneypot();
1993
- return true;
2008
+ const targetForm = getFormContainer(target);
2009
+ const blockedType = SINGLE_INSTANCE_FORM_ATOM_TYPES.find(
2010
+ (type) => movedContainers.some((container) => hasElementType(container, type)) && formHasElementType(targetForm, type, movedElements)
2011
+ );
2012
+ if (!blockedType) {
2013
+ return false;
1994
2014
  }
1995
- return false;
2015
+ notify4(SINGLE_INSTANCE_FORM_ATOM_ALERTS[blockedType]);
2016
+ return true;
1996
2017
  }
1997
- function formHasHoneypot(form, excluded = /* @__PURE__ */ new Set()) {
2018
+ function formHasElementType(form, type, excluded = /* @__PURE__ */ new Set()) {
1998
2019
  if (!form) {
1999
2020
  return false;
2000
2021
  }
2001
2022
  return getAllDescendants2(form).some(
2002
- (element) => !excluded.has(element) && getElementType(element) === FORM_HONEYPOT_ELEMENT_TYPE
2023
+ (element) => !excluded.has(element) && getElementType(element) === type
2003
2024
  );
2004
2025
  }
2005
- function handleBlockedHoneypot() {
2006
- notify4(DUPLICATE_HONEYPOT_ALERT);
2007
- }
2008
2026
 
2009
2027
  // src/form-structure/prevent-form-nesting-commands.ts
2010
2028
  import { notify as notify5 } from "@elementor/editor-notifications";
@@ -2268,7 +2286,6 @@ var svgSrcTransformer = createTransformer(async (value, { signal }) => {
2268
2286
  var EMPTY_ICON_RESULT = { html: null, url: null };
2269
2287
  var ICON_SVG_SIZE = "100%";
2270
2288
  var ICON_SVG_OVERFLOW = "visible";
2271
- var FONT_AWESOME_LIBRARY_PREFIX = "fa-";
2272
2289
  var EICONS_LIBRARY = "eicons";
2273
2290
  var DEFAULT_SVG_RELATIVE_PATH = "images/default-svg.svg";
2274
2291
  var iconTransformer = createTransformer(async (value, { signal }) => {
@@ -2281,7 +2298,7 @@ var iconTransformer = createTransformer(async (value, { signal }) => {
2281
2298
  if (fontAwesomeHtml) {
2282
2299
  return { html: fontAwesomeHtml, url: null };
2283
2300
  }
2284
- if (library.startsWith(FONT_AWESOME_LIBRARY_PREFIX) || EICONS_LIBRARY === library) {
2301
+ if (EICONS_LIBRARY === library) {
2285
2302
  return EMPTY_ICON_RESULT;
2286
2303
  }
2287
2304
  const providerHtml = await resolveProviderSvg(library, iconValue, signal);
@@ -6085,8 +6102,8 @@ function init() {
6085
6102
  initFormNestingPrevention();
6086
6103
  initFormAncestorEnforcement();
6087
6104
  initHiddenFieldHint();
6088
- initDuplicateHoneypotPrevention();
6089
- initHoneypotHint();
6105
+ initDuplicateFormAtomsPrevention();
6106
+ initFormAtomAddedHints();
6090
6107
  initViewReplacements();
6091
6108
  initLegacyViews();
6092
6109
  initSettingsTransformers();
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.4.0-1144",
4
+ "version": "4.4.0-1146",
5
5
  "private": false,
6
6
  "author": "Elementor Team",
7
7
  "homepage": "https://elementor.com/",
@@ -37,26 +37,26 @@
37
37
  "react-dom": "^18.3.1"
38
38
  },
39
39
  "dependencies": {
40
- "@elementor/editor": "4.4.0-1144",
41
- "@elementor/editor-controls": "4.4.0-1144",
42
- "@elementor/editor-documents": "4.4.0-1144",
43
- "@elementor/editor-elements": "4.4.0-1144",
44
- "@elementor/editor-interactions": "4.4.0-1144",
45
- "@elementor/editor-mcp": "4.4.0-1144",
46
- "@elementor/editor-notifications": "4.4.0-1144",
47
- "@elementor/editor-props": "4.4.0-1144",
48
- "@elementor/editor-responsive": "4.4.0-1144",
49
- "@elementor/editor-styles": "4.4.0-1144",
50
- "@elementor/editor-styles-repository": "4.4.0-1144",
51
- "@elementor/editor-ui": "4.4.0-1144",
52
- "@elementor/editor-v1-adapters": "4.4.0-1144",
53
- "@elementor/events": "4.4.0-1144",
54
- "@elementor/http-client": "4.4.0-1144",
55
- "@elementor/schema": "4.4.0-1144",
56
- "@elementor/twing": "4.4.0-1144",
40
+ "@elementor/editor": "4.4.0-1146",
41
+ "@elementor/editor-controls": "4.4.0-1146",
42
+ "@elementor/editor-documents": "4.4.0-1146",
43
+ "@elementor/editor-elements": "4.4.0-1146",
44
+ "@elementor/editor-interactions": "4.4.0-1146",
45
+ "@elementor/editor-mcp": "4.4.0-1146",
46
+ "@elementor/editor-notifications": "4.4.0-1146",
47
+ "@elementor/editor-props": "4.4.0-1146",
48
+ "@elementor/editor-responsive": "4.4.0-1146",
49
+ "@elementor/editor-styles": "4.4.0-1146",
50
+ "@elementor/editor-styles-repository": "4.4.0-1146",
51
+ "@elementor/editor-ui": "4.4.0-1146",
52
+ "@elementor/editor-v1-adapters": "4.4.0-1146",
53
+ "@elementor/events": "4.4.0-1146",
54
+ "@elementor/http-client": "4.4.0-1146",
55
+ "@elementor/schema": "4.4.0-1146",
56
+ "@elementor/twing": "4.4.0-1146",
57
57
  "@elementor/ui": "1.41.4",
58
- "@elementor/utils": "4.4.0-1144",
59
- "@elementor/wp-media": "4.4.0-1144",
58
+ "@elementor/utils": "4.4.0-1146",
59
+ "@elementor/wp-media": "4.4.0-1146",
60
60
  "@floating-ui/react": "^0.27.5",
61
61
  "@wordpress/i18n": "^5.13.0",
62
62
  "dompurify": "^3.2.6"
@@ -1,7 +1,7 @@
1
1
  import { createHooksRegistry, setupHooksRegistry } from 'test-utils';
2
2
  import { notify } from '@elementor/editor-notifications';
3
3
 
4
- import { initHoneypotHint } from '../honeypot-hint';
4
+ import { initFormAtomAddedHints } from '../form-atom-added-hints';
5
5
 
6
6
  jest.mock( '@elementor/editor-notifications', () => ( {
7
7
  notify: jest.fn(),
@@ -10,14 +10,45 @@ jest.mock( '@elementor/editor-notifications', () => ( {
10
10
  const CREATE_COMMAND = 'document/elements/create';
11
11
  const MOVE_COMMAND = 'document/elements/move';
12
12
  const HONEYPOT_TYPE = 'e-form-honeypot';
13
+ const RECAPTCHA_SCORE_TYPE = 'e-form-recaptcha-score';
13
14
 
14
- describe( 'honeypot hint', () => {
15
+ describe( 'form atom added hints', () => {
15
16
  const hooksRegistry = createHooksRegistry();
16
17
 
17
18
  beforeEach( () => {
18
19
  jest.mocked( notify ).mockClear();
19
20
  setupHooksRegistry( hooksRegistry );
20
- initHoneypotHint();
21
+ initFormAtomAddedHints();
22
+ } );
23
+
24
+ it( 'should notify when a reCAPTCHA Score is added', () => {
25
+ // Arrange
26
+ const hook = hooksRegistry.getByCommand( CREATE_COMMAND );
27
+
28
+ // Act
29
+ hook?.apply( { model: { widgetType: RECAPTCHA_SCORE_TYPE } } );
30
+
31
+ // Assert
32
+ expect( notify ).toHaveBeenCalledWith(
33
+ expect.objectContaining( {
34
+ message: 'Score-based reCAPTCHA added - You can adjust its settings from the Structure panel.',
35
+ } )
36
+ );
37
+ } );
38
+
39
+ it( 'should notify again when a reCAPTCHA Score is removed and added once more', () => {
40
+ // Arrange
41
+ const hook = hooksRegistry.getByCommand( CREATE_COMMAND );
42
+
43
+ // Act
44
+ hook?.apply( { model: { widgetType: RECAPTCHA_SCORE_TYPE } } );
45
+ hook?.apply( { model: { widgetType: RECAPTCHA_SCORE_TYPE } } );
46
+
47
+ // Assert
48
+ expect( notify ).toHaveBeenCalledTimes( 2 );
49
+ expect( jest.mocked( notify ).mock.calls[ 0 ][ 0 ].id ).not.toBe(
50
+ jest.mocked( notify ).mock.calls[ 1 ][ 0 ].id
51
+ );
21
52
  } );
22
53
 
23
54
  it( 'should notify when a honeypot is added', () => {
@@ -2,7 +2,7 @@ import { createHooksRegistry, setupHooksRegistry } from 'test-utils';
2
2
  import { type V1Element } from '@elementor/editor-elements';
3
3
  import { notify } from '@elementor/editor-notifications';
4
4
 
5
- import { initDuplicateHoneypotPrevention } from '../prevent-duplicate-honeypot-commands';
5
+ import { initDuplicateFormAtomsPrevention } from '../prevent-duplicate-form-atoms-commands';
6
6
 
7
7
  jest.mock( '@elementor/editor-notifications', () => ( {
8
8
  notify: jest.fn(),
@@ -11,6 +11,7 @@ jest.mock( '@elementor/editor-notifications', () => ( {
11
11
  const CREATE_COMMAND = 'document/elements/create';
12
12
  const MOVE_COMMAND = 'document/elements/move';
13
13
  const HONEYPOT_TYPE = 'e-form-honeypot';
14
+ const RECAPTCHA_SCORE_TYPE = 'e-form-recaptcha-score';
14
15
 
15
16
  function createElement( type: string, children: V1Element[] = [] ): V1Element {
16
17
  const element = {
@@ -26,7 +27,7 @@ function createElement( type: string, children: V1Element[] = [] ): V1Element {
26
27
  return element;
27
28
  }
28
29
 
29
- describe( 'prevent duplicate honeypot', () => {
30
+ describe( 'prevent duplicate form atoms', () => {
30
31
  const hooksRegistry = createHooksRegistry();
31
32
 
32
33
  const isAllowed = ( command: string, args: unknown ) => hooksRegistry.getByCommand( command )?.apply( args );
@@ -34,7 +35,7 @@ describe( 'prevent duplicate honeypot', () => {
34
35
  beforeEach( () => {
35
36
  jest.mocked( notify ).mockClear();
36
37
  setupHooksRegistry( hooksRegistry );
37
- initDuplicateHoneypotPrevention();
38
+ initDuplicateFormAtomsPrevention();
38
39
  } );
39
40
 
40
41
  it( 'should allow adding the first honeypot to a form', () => {
@@ -130,4 +131,68 @@ describe( 'prevent duplicate honeypot', () => {
130
131
  expect( result ).toBe( false );
131
132
  expect( notify ).toHaveBeenCalledTimes( 1 );
132
133
  } );
134
+
135
+ it( 'should block a second reCAPTCHA Score in the same form and show its toast', () => {
136
+ // Arrange
137
+ const flexbox = createElement( 'e-flexbox' );
138
+ createElement( 'e-form', [ createElement( RECAPTCHA_SCORE_TYPE ), flexbox ] );
139
+
140
+ // Act
141
+ const result = isAllowed( CREATE_COMMAND, {
142
+ container: flexbox,
143
+ model: { widgetType: RECAPTCHA_SCORE_TYPE },
144
+ } );
145
+
146
+ // Assert
147
+ expect( result ).toBe( false );
148
+ expect( notify ).toHaveBeenCalledWith(
149
+ expect.objectContaining( { message: 'Only one Score-based reCAPTCHA can be added per form.' } )
150
+ );
151
+ } );
152
+
153
+ it( 'should allow a reCAPTCHA Score in a form that already has a honeypot', () => {
154
+ // Arrange
155
+ const form = createElement( 'e-form', [ createElement( HONEYPOT_TYPE ) ] );
156
+
157
+ // Act
158
+ const result = isAllowed( CREATE_COMMAND, { container: form, model: { widgetType: RECAPTCHA_SCORE_TYPE } } );
159
+
160
+ // Assert
161
+ expect( result ).toBe( true );
162
+ expect( notify ).not.toHaveBeenCalled();
163
+ } );
164
+
165
+ it( 'should allow a reCAPTCHA Score in a second form on the same page', () => {
166
+ // Arrange
167
+ const secondForm = createElement( 'e-form' );
168
+ createElement( 'document', [
169
+ createElement( 'e-form', [ createElement( RECAPTCHA_SCORE_TYPE ) ] ),
170
+ secondForm,
171
+ ] );
172
+
173
+ // Act
174
+ const result = isAllowed( CREATE_COMMAND, {
175
+ container: secondForm,
176
+ model: { widgetType: RECAPTCHA_SCORE_TYPE },
177
+ } );
178
+
179
+ // Assert
180
+ expect( result ).toBe( true );
181
+ } );
182
+
183
+ it( 'should block moving a reCAPTCHA Score into another form that already has one', () => {
184
+ // Arrange
185
+ const recaptcha = createElement( RECAPTCHA_SCORE_TYPE );
186
+ const targetForm = createElement( 'e-form', [ createElement( RECAPTCHA_SCORE_TYPE ) ] );
187
+ createElement( 'document', [ createElement( 'e-form', [ recaptcha ] ), targetForm ] );
188
+
189
+ // Act
190
+ const result = isAllowed( MOVE_COMMAND, { containers: [ recaptcha ], target: targetForm } );
191
+
192
+ // Assert
193
+ expect( result ).toBe( false );
194
+ expect( notify ).toHaveBeenCalledWith(
195
+ expect.objectContaining( { message: 'Only one Score-based reCAPTCHA can be added per form.' } )
196
+ );
197
+ } );
133
198
  } );
@@ -0,0 +1,53 @@
1
+ import { notify } from '@elementor/editor-notifications';
2
+ import { type HookOptions, registerDataHook } from '@elementor/editor-v1-adapters';
3
+ import { __ } from '@wordpress/i18n';
4
+
5
+ import {
6
+ type CreateArgs,
7
+ FORM_HONEYPOT_ELEMENT_TYPE,
8
+ FORM_RECAPTCHA_SCORE_ELEMENT_TYPE,
9
+ getArgsElementType,
10
+ } from './utils';
11
+
12
+ const MOVE_COMMAND = 'document/elements/move';
13
+
14
+ type FormAtomHint = {
15
+ idPrefix: string;
16
+ getMessage: () => string;
17
+ };
18
+
19
+ const FORM_ATOM_HINTS: Record< string, FormAtomHint > = {
20
+ [ FORM_HONEYPOT_ELEMENT_TYPE ]: {
21
+ idPrefix: 'honeypot-added',
22
+ getMessage: () =>
23
+ __( 'Honeypot added - invisible on your form. Reorder it in the Structure panel.', 'elementor' ),
24
+ },
25
+ [ FORM_RECAPTCHA_SCORE_ELEMENT_TYPE ]: {
26
+ idPrefix: 'recaptcha-score-added',
27
+ getMessage: () =>
28
+ __( 'Score-based reCAPTCHA added - You can adjust its settings from the Structure panel.', 'elementor' ),
29
+ },
30
+ };
31
+
32
+ let notificationCounter = 0;
33
+
34
+ export function initFormAtomAddedHints() {
35
+ registerDataHook( 'after', 'document/elements/create', ( args: CreateArgs, _result, options?: HookOptions ) => {
36
+ if ( options?.commandsCurrentTrace?.includes( MOVE_COMMAND ) ) {
37
+ return;
38
+ }
39
+
40
+ const elementType = getArgsElementType( args );
41
+ const hint = elementType ? FORM_ATOM_HINTS[ elementType ] : undefined;
42
+
43
+ if ( ! hint ) {
44
+ return;
45
+ }
46
+
47
+ notify( {
48
+ type: 'default',
49
+ message: hint.getMessage(),
50
+ id: `${ hint.idPrefix }-${ ++notificationCounter }`,
51
+ } );
52
+ } );
53
+ }
@@ -0,0 +1,97 @@
1
+ import { getAllDescendants, type V1Element } from '@elementor/editor-elements';
2
+ import { type NotificationData, notify } from '@elementor/editor-notifications';
3
+ import { blockCommand, type HookOptions, registerDataHook } from '@elementor/editor-v1-adapters';
4
+ import { __ } from '@wordpress/i18n';
5
+
6
+ import {
7
+ type CreateArgs,
8
+ FORM_HONEYPOT_ELEMENT_TYPE,
9
+ FORM_RECAPTCHA_SCORE_ELEMENT_TYPE,
10
+ getElementType,
11
+ getFormContainer,
12
+ hasElementType,
13
+ hasModelElementType,
14
+ type MoveArgs,
15
+ } from './utils';
16
+
17
+ const MOVE_COMMAND = 'document/elements/move';
18
+
19
+ const SINGLE_INSTANCE_FORM_ATOM_ALERTS: Record< string, NotificationData > = {
20
+ [ FORM_HONEYPOT_ELEMENT_TYPE ]: {
21
+ type: 'default',
22
+ message: __( 'Only one Honeypot can be added per form.', 'elementor' ),
23
+ id: 'duplicate-honeypot-blocked',
24
+ },
25
+ [ FORM_RECAPTCHA_SCORE_ELEMENT_TYPE ]: {
26
+ type: 'default',
27
+ message: __( 'Only one Score-based reCAPTCHA can be added per form.', 'elementor' ),
28
+ id: 'duplicate-recaptcha-score-blocked',
29
+ },
30
+ };
31
+
32
+ const SINGLE_INSTANCE_FORM_ATOM_TYPES = Object.keys( SINGLE_INSTANCE_FORM_ATOM_ALERTS );
33
+
34
+ export function initDuplicateFormAtomsPrevention() {
35
+ registerDataHook( 'dependency', 'document/elements/create', ( args: CreateArgs, options?: HookOptions ) => {
36
+ return ! blockDuplicateCreate( args, options );
37
+ } );
38
+
39
+ blockCommand( {
40
+ command: 'document/elements/move',
41
+ condition: blockDuplicateMove,
42
+ } );
43
+ }
44
+
45
+ function blockDuplicateCreate( args: CreateArgs, options?: HookOptions ): boolean {
46
+ // Move re-creates the moved element at the target, so it is handled by the move condition.
47
+ if ( options?.commandsCurrentTrace?.includes( MOVE_COMMAND ) ) {
48
+ return false;
49
+ }
50
+
51
+ const containers = args.containers ?? [ args.container ];
52
+
53
+ const blockedType = SINGLE_INSTANCE_FORM_ATOM_TYPES.find(
54
+ ( type ) =>
55
+ hasModelElementType( args.model, type ) &&
56
+ containers.some( ( container ) => formHasElementType( getFormContainer( container ), type ) )
57
+ );
58
+
59
+ if ( ! blockedType ) {
60
+ return false;
61
+ }
62
+
63
+ notify( SINGLE_INSTANCE_FORM_ATOM_ALERTS[ blockedType ] );
64
+
65
+ return true;
66
+ }
67
+
68
+ function blockDuplicateMove( args: MoveArgs ): boolean {
69
+ const { containers = [ args.container ], target } = args;
70
+ const movedContainers = containers.filter( ( container ): container is V1Element => !! container );
71
+ const movedElements = new Set( movedContainers.flatMap( ( container ) => getAllDescendants( container ) ) );
72
+ const targetForm = getFormContainer( target );
73
+
74
+ const blockedType = SINGLE_INSTANCE_FORM_ATOM_TYPES.find(
75
+ ( type ) =>
76
+ movedContainers.some( ( container ) => hasElementType( container, type ) ) &&
77
+ formHasElementType( targetForm, type, movedElements )
78
+ );
79
+
80
+ if ( ! blockedType ) {
81
+ return false;
82
+ }
83
+
84
+ notify( SINGLE_INSTANCE_FORM_ATOM_ALERTS[ blockedType ] );
85
+
86
+ return true;
87
+ }
88
+
89
+ function formHasElementType( form: V1Element | undefined, type: string, excluded: Set< V1Element > = new Set() ) {
90
+ if ( ! form ) {
91
+ return false;
92
+ }
93
+
94
+ return getAllDescendants( form ).some(
95
+ ( element ) => ! excluded.has( element ) && getElementType( element ) === type
96
+ );
97
+ }
@@ -38,6 +38,7 @@ export type StorageContent = {
38
38
  export const FORM_ELEMENT_TYPE = 'e-form';
39
39
  export const FORM_HIDDEN_FIELD_ELEMENT_TYPE = 'e-form-hidden';
40
40
  export const FORM_HONEYPOT_ELEMENT_TYPE = 'e-form-honeypot';
41
+ export const FORM_RECAPTCHA_SCORE_ELEMENT_TYPE = 'e-form-recaptcha-score';
41
42
  export const FORM_FIELD_ELEMENT_TYPES = new Set( [
42
43
  'e-form-input',
43
44
  'e-form-textarea',
@@ -51,6 +52,7 @@ export const FORM_FIELD_ELEMENT_TYPES = new Set( [
51
52
  'e-form-time-picker',
52
53
  FORM_HIDDEN_FIELD_ELEMENT_TYPE,
53
54
  FORM_HONEYPOT_ELEMENT_TYPE,
55
+ FORM_RECAPTCHA_SCORE_ELEMENT_TYPE,
54
56
  ] );
55
57
 
56
58
  export function getArgsElementType( args: CreateArgs ): string | undefined {
package/src/init.tsx CHANGED
@@ -6,9 +6,9 @@ import { ElementsOverlays } from './components/elements-overlays';
6
6
  import { InteractionsRenderer } from './components/interactions-renderer';
7
7
  import { StyleRenderer } from './components/style-renderer';
8
8
  import { initFormAncestorEnforcement } from './form-structure/enforce-form-ancestor-commands';
9
+ import { initFormAtomAddedHints } from './form-structure/form-atom-added-hints';
9
10
  import { initHiddenFieldHint } from './form-structure/hidden-field-hint';
10
- import { initHoneypotHint } from './form-structure/honeypot-hint';
11
- import { initDuplicateHoneypotPrevention } from './form-structure/prevent-duplicate-honeypot-commands';
11
+ import { initDuplicateFormAtomsPrevention } from './form-structure/prevent-duplicate-form-atoms-commands';
12
12
  import { initFormNestingPrevention } from './form-structure/prevent-form-nesting-commands';
13
13
  import { initSettingsTransformers } from './init-settings-transformers';
14
14
  import { initStyleTransformers } from './init-style-transformers';
@@ -29,8 +29,8 @@ export function init() {
29
29
  initFormNestingPrevention();
30
30
  initFormAncestorEnforcement();
31
31
  initHiddenFieldHint();
32
- initDuplicateHoneypotPrevention();
33
- initHoneypotHint();
32
+ initDuplicateFormAtomsPrevention();
33
+ initFormAtomAddedHints();
34
34
 
35
35
  initViewReplacements();
36
36
 
@@ -133,7 +133,7 @@ describe( 'iconTransformer', () => {
133
133
  expect( html ).toContain( 'M10 10' );
134
134
  } );
135
135
 
136
- it( 'returns null html when the icon cannot be resolved', async () => {
136
+ it( 'falls back to the default svg when the icon cannot be resolved', async () => {
137
137
  // Arrange.
138
138
  mockFetch( { icons: {} } );
139
139
 
@@ -141,7 +141,7 @@ describe( 'iconTransformer', () => {
141
141
  const result = await resolveSavedIcon( 'fas fa-missing', 'fa-solid' );
142
142
 
143
143
  // Assert.
144
- expect( result ).toEqual( { html: null, url: null } );
144
+ expect( result ).toEqual( { html: null, url: 'https://example.com/assets/images/default-svg.svg' } );
145
145
  } );
146
146
 
147
147
  it( 'fetches json from the localized font awesome 7 base url', async () => {
@@ -180,8 +180,70 @@ describe( 'iconTransformer', () => {
180
180
  const result = await resolveSavedIcon( 'fas fa-star', 'fa-../../wp-config' );
181
181
 
182
182
  // Assert.
183
- expect( global.fetch ).not.toHaveBeenCalled();
184
- expect( result ).toEqual( { html: null, url: null } );
183
+ expect( ( global.fetch as jest.Mock ).mock.calls.map( ( [ url ] ) => String( url ) ) ).toEqual( [
184
+ 'https://example.com/assets/images/default-svg.svg',
185
+ ] );
186
+ expect( result ).toEqual(
187
+ expect.objectContaining( { url: 'https://example.com/assets/images/default-svg.svg' } )
188
+ );
189
+ } );
190
+
191
+ it( 'asks providers for a font awesome icon missing from the free data', async () => {
192
+ // Arrange.
193
+ mockFetch( { icons: {} } );
194
+ const resolveSvg = jest
195
+ .fn()
196
+ .mockResolvedValue(
197
+ '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10 10"><path d="M2 2h6v6H2z"></path></svg>'
198
+ );
199
+ registerIconLibraryProvider( {
200
+ id: 'test-provider',
201
+ useIcons: () => ( { data: [], isLoading: false } ),
202
+ resolveSvg,
203
+ } );
204
+
205
+ // Act.
206
+ const result = ( await resolveSavedIcon( 'fal fa-star', 'fa-light' ) ) as { html: string; url: string | null };
207
+
208
+ unregisterIconLibraryProvider( 'test-provider' );
209
+
210
+ // Assert.
211
+ expect( resolveSvg ).toHaveBeenCalledWith( 'fa-light', 'fal fa-star', undefined );
212
+ expect( result.html ).toContain( 'M2 2h6v6H2z' );
213
+ expect( result.url ).toBeNull();
214
+ } );
215
+
216
+ it( 'falls back to the default svg for a font awesome icon when no provider resolves it', async () => {
217
+ // Arrange.
218
+ registerIconLibraryProvider( {
219
+ id: 'test-provider',
220
+ useIcons: () => ( { data: [], isLoading: false } ),
221
+ resolveSvg: async () => null,
222
+ } );
223
+ const defaultSvg =
224
+ '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 50"><path d="M24.9999 4.31543"></path></svg>';
225
+ global.fetch = jest.fn( ( url: string ) =>
226
+ Promise.resolve(
227
+ String( url ).endsWith( '.svg' )
228
+ ? {
229
+ ok: true,
230
+ headers: new Headers( { 'content-type': 'image/svg+xml' } ),
231
+ text: () => Promise.resolve( defaultSvg ),
232
+ }
233
+ : { ok: true, json: () => Promise.resolve( { icons: {} } ) }
234
+ )
235
+ ) as never;
236
+
237
+ // Act.
238
+ const result = await resolveSavedIcon( 'fa-light fa-star', 'fa-light' );
239
+
240
+ unregisterIconLibraryProvider( 'test-provider' );
241
+
242
+ // Assert.
243
+ expect( result ).toEqual( {
244
+ html: expect.stringContaining( 'M24.9999 4.31543' ),
245
+ url: 'https://example.com/assets/images/default-svg.svg',
246
+ } );
185
247
  } );
186
248
 
187
249
  it( 'falls back to the default svg when no provider resolves a non-native library', async () => {
@@ -13,7 +13,6 @@ import { svgSrcTransformer } from './svg-src-transformer';
13
13
  const EMPTY_ICON_RESULT = { html: null, url: null };
14
14
  const ICON_SVG_SIZE = '100%';
15
15
  const ICON_SVG_OVERFLOW = 'visible';
16
- const FONT_AWESOME_LIBRARY_PREFIX = 'fa-';
17
16
  const EICONS_LIBRARY = 'eicons';
18
17
  const DEFAULT_SVG_RELATIVE_PATH = 'images/default-svg.svg';
19
18
 
@@ -36,7 +35,7 @@ export const iconTransformer = createTransformer( async ( value: IconValue, { si
36
35
  return { html: fontAwesomeHtml, url: null };
37
36
  }
38
37
 
39
- if ( library.startsWith( FONT_AWESOME_LIBRARY_PREFIX ) || EICONS_LIBRARY === library ) {
38
+ if ( EICONS_LIBRARY === library ) {
40
39
  return EMPTY_ICON_RESULT;
41
40
  }
42
41
 
@@ -1,29 +0,0 @@
1
- import { type NotificationData, notify } from '@elementor/editor-notifications';
2
- import { type HookOptions, registerDataHook } from '@elementor/editor-v1-adapters';
3
- import { __ } from '@wordpress/i18n';
4
-
5
- import { type CreateArgs, FORM_HONEYPOT_ELEMENT_TYPE, getArgsElementType } from './utils';
6
-
7
- const MOVE_COMMAND = 'document/elements/move';
8
-
9
- let notificationCounter = 0;
10
-
11
- function createHoneypotNotification(): NotificationData {
12
- return {
13
- type: 'default',
14
- message: __( 'Honeypot added - invisible on your form. Reorder it in the Structure panel.', 'elementor' ),
15
- id: `honeypot-added-${ ++notificationCounter }`,
16
- };
17
- }
18
-
19
- export function initHoneypotHint() {
20
- registerDataHook( 'after', 'document/elements/create', ( args: CreateArgs, _result, options?: HookOptions ) => {
21
- if ( options?.commandsCurrentTrace?.includes( MOVE_COMMAND ) ) {
22
- return;
23
- }
24
-
25
- if ( getArgsElementType( args ) === FORM_HONEYPOT_ELEMENT_TYPE ) {
26
- notify( createHoneypotNotification() );
27
- }
28
- } );
29
- }
@@ -1,87 +0,0 @@
1
- import { getAllDescendants, type V1Element } from '@elementor/editor-elements';
2
- import { type NotificationData, notify } from '@elementor/editor-notifications';
3
- import { blockCommand, type HookOptions, registerDataHook } from '@elementor/editor-v1-adapters';
4
- import { __ } from '@wordpress/i18n';
5
-
6
- import {
7
- type CreateArgs,
8
- FORM_HONEYPOT_ELEMENT_TYPE,
9
- getElementType,
10
- getFormContainer,
11
- hasElementType,
12
- hasModelElementType,
13
- type MoveArgs,
14
- } from './utils';
15
-
16
- const MOVE_COMMAND = 'document/elements/move';
17
-
18
- const DUPLICATE_HONEYPOT_ALERT: NotificationData = {
19
- type: 'default',
20
- message: __( 'Only one Honeypot can be added per form.', 'elementor' ),
21
- id: 'duplicate-honeypot-blocked',
22
- };
23
-
24
- export function initDuplicateHoneypotPrevention() {
25
- registerDataHook( 'dependency', 'document/elements/create', ( args: CreateArgs, options?: HookOptions ) => {
26
- return ! blockHoneypotCreate( args, options );
27
- } );
28
-
29
- blockCommand( {
30
- command: 'document/elements/move',
31
- condition: blockHoneypotMove,
32
- } );
33
- }
34
-
35
- function blockHoneypotCreate( args: CreateArgs, options?: HookOptions ): boolean {
36
- // Move re-creates the moved element at the target, so it is handled by the move condition.
37
- if ( options?.commandsCurrentTrace?.includes( MOVE_COMMAND ) ) {
38
- return false;
39
- }
40
-
41
- if ( ! hasModelElementType( args.model, FORM_HONEYPOT_ELEMENT_TYPE ) ) {
42
- return false;
43
- }
44
-
45
- const containers = args.containers ?? [ args.container ];
46
-
47
- if ( containers.some( ( container ) => formHasHoneypot( getFormContainer( container ) ) ) ) {
48
- handleBlockedHoneypot();
49
-
50
- return true;
51
- }
52
-
53
- return false;
54
- }
55
-
56
- function blockHoneypotMove( args: MoveArgs ): boolean {
57
- const { containers = [ args.container ], target } = args;
58
- const movedContainers = containers.filter( ( container ): container is V1Element => !! container );
59
-
60
- if ( ! movedContainers.some( ( container ) => hasElementType( container, FORM_HONEYPOT_ELEMENT_TYPE ) ) ) {
61
- return false;
62
- }
63
-
64
- const movedElements = new Set( movedContainers.flatMap( ( container ) => getAllDescendants( container ) ) );
65
-
66
- if ( formHasHoneypot( getFormContainer( target ), movedElements ) ) {
67
- handleBlockedHoneypot();
68
-
69
- return true;
70
- }
71
-
72
- return false;
73
- }
74
-
75
- function formHasHoneypot( form?: V1Element, excluded: Set< V1Element > = new Set() ): boolean {
76
- if ( ! form ) {
77
- return false;
78
- }
79
-
80
- return getAllDescendants( form ).some(
81
- ( element ) => ! excluded.has( element ) && getElementType( element ) === FORM_HONEYPOT_ELEMENT_TYPE
82
- );
83
- }
84
-
85
- function handleBlockedHoneypot(): void {
86
- notify( DUPLICATE_HONEYPOT_ALERT );
87
- }