@elementor/editor-canvas 4.3.0-1046 → 4.3.0-1048

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
@@ -2037,6 +2037,40 @@ var dateTimeTransformer = createTransformer((values) => {
2037
2037
  }).join(" ");
2038
2038
  });
2039
2039
 
2040
+ // src/utils/sanitize-escaped-html.ts
2041
+ var import_dompurify = __toESM(require("dompurify"));
2042
+ var ALLOWED_NON_OPERATIONAL_ATTRS = [
2043
+ "href",
2044
+ "target",
2045
+ "class",
2046
+ "id",
2047
+ "style",
2048
+ "title",
2049
+ "lang",
2050
+ "dir",
2051
+ "role"
2052
+ ];
2053
+ function getAllowedHtmlWrapperTags() {
2054
+ return window.elementorCommon?.config?.allowedHTMLWrapperTags ?? [];
2055
+ }
2056
+ function sanitizeEscapedHtml(value) {
2057
+ if (!value) {
2058
+ return "";
2059
+ }
2060
+ const allowedTags = [...getAllowedHtmlWrapperTags()];
2061
+ return import_dompurify.default.sanitize(value, {
2062
+ ALLOWED_TAGS: allowedTags,
2063
+ ALLOWED_ATTR: [...ALLOWED_NON_OPERATIONAL_ATTRS],
2064
+ ALLOW_DATA_ATTR: true,
2065
+ ALLOW_ARIA_ATTR: true
2066
+ });
2067
+ }
2068
+
2069
+ // src/transformers/settings/escaped-html-transformer.ts
2070
+ var escapedHtmlTransformer = createTransformer((value) => {
2071
+ return sanitizeEscapedHtml(value);
2072
+ });
2073
+
2040
2074
  // src/transformers/settings/html-v2-transformer.ts
2041
2075
  var htmlV2Transformer = createTransformer((value) => {
2042
2076
  return value?.content ?? "";
@@ -2114,11 +2148,11 @@ var plainTransformer = createTransformer((value) => {
2114
2148
  });
2115
2149
 
2116
2150
  // src/transformers/shared/svg-src-transformer.ts
2117
- var import_dompurify = __toESM(require("dompurify"));
2151
+ var import_dompurify2 = __toESM(require("dompurify"));
2118
2152
  var import_wp_media2 = require("@elementor/wp-media");
2119
2153
  var SVG_INLINE_STYLES = "width: 100%; height: 100%; overflow: unset;";
2120
2154
  function processSvgContent(svgText) {
2121
- const sanitized = import_dompurify.default.sanitize(svgText, {
2155
+ const sanitized = import_dompurify2.default.sanitize(svgText, {
2122
2156
  USE_PROFILES: { svg: true, svgFilters: true }
2123
2157
  });
2124
2158
  const parser = new DOMParser();
@@ -2189,7 +2223,7 @@ var videoSrcTransformer = createTransformer(async (value) => {
2189
2223
 
2190
2224
  // src/init-settings-transformers.ts
2191
2225
  function initSettingsTransformers() {
2192
- settingsTransformersRegistry.register("classes", createClassesTransformer()).register("link", linkTransformer).register("query", queryTransformer).register("image", imageTransformer).register("image-src", imageSrcTransformer).register("svg-src", svgSrcTransformer).register("video-src", videoSrcTransformer).register("attributes", attributesTransformer).register("date-time", dateTimeTransformer).register("html-v2", htmlV2Transformer).register("html-v3", htmlV3Transformer).register("date-range", dateRangeTransformer).register("time-range", timeRangeTransformer).registerFallback(plainTransformer);
2226
+ settingsTransformersRegistry.register("classes", createClassesTransformer()).register("link", linkTransformer).register("query", queryTransformer).register("image", imageTransformer).register("image-src", imageSrcTransformer).register("svg-src", svgSrcTransformer).register("video-src", videoSrcTransformer).register("attributes", attributesTransformer).register("date-time", dateTimeTransformer).register("html-v2", htmlV2Transformer).register("html-v3", htmlV3Transformer).register("escaped-html", escapedHtmlTransformer).register("date-range", dateRangeTransformer).register("time-range", timeRangeTransformer).registerFallback(plainTransformer);
2193
2227
  }
2194
2228
 
2195
2229
  // src/transformers/styles/background-color-overlay-transformer.ts
@@ -2578,11 +2612,11 @@ function createDomRenderer() {
2578
2612
  render: environment.render
2579
2613
  };
2580
2614
  }
2581
- function getAllowedHtmlWrapperTags() {
2615
+ function getAllowedHtmlWrapperTags2() {
2582
2616
  return window.elementorCommon?.config?.allowedHTMLWrapperTags ?? [];
2583
2617
  }
2584
2618
  function escapeHtmlTag(value) {
2585
- const allowedTags = getAllowedHtmlWrapperTags();
2619
+ const allowedTags = getAllowedHtmlWrapperTags2();
2586
2620
  const normalizedTag = value?.toLowerCase?.() ?? "";
2587
2621
  return allowedTags.includes(normalizedTag) ? value : "div";
2588
2622
  }
@@ -3516,7 +3550,7 @@ var import_editor_props3 = require("@elementor/editor-props");
3516
3550
  var hasKey = (propType) => {
3517
3551
  return "key" in propType;
3518
3552
  };
3519
- var TEXT_PROP_TYPE_KEYS = /* @__PURE__ */ new Set([import_editor_props3.htmlV3PropTypeUtil.key, import_editor_props3.stringPropTypeUtil.key]);
3553
+ var TEXT_PROP_TYPE_KEYS = /* @__PURE__ */ new Set([import_editor_props3.escapedHtmlPropTypeUtil.key, import_editor_props3.htmlV3PropTypeUtil.key, import_editor_props3.stringPropTypeUtil.key]);
3520
3554
  var isCoreTextPropTypeKey = (key) => {
3521
3555
  return TEXT_PROP_TYPE_KEYS.has(key);
3522
3556
  };
@@ -3536,7 +3570,7 @@ var isInlineEditingAllowed = ({ rawValue, propTypeFromSchema }) => {
3536
3570
  if (rawValue === null || rawValue === void 0) {
3537
3571
  return isAllowedBySchema(propTypeFromSchema);
3538
3572
  }
3539
- return import_editor_props3.htmlV3PropTypeUtil.isValid(rawValue) || import_editor_props3.stringPropTypeUtil.isValid(rawValue);
3573
+ return import_editor_props3.escapedHtmlPropTypeUtil.isValid(rawValue) || import_editor_props3.htmlV3PropTypeUtil.isValid(rawValue) || import_editor_props3.stringPropTypeUtil.isValid(rawValue);
3540
3574
  };
3541
3575
 
3542
3576
  // src/legacy/replacements/inline-editing/inline-editing-elements.tsx
@@ -3621,17 +3655,26 @@ var InlineEditingReplacement = class extends ReplacementBase {
3621
3655
  }
3622
3656
  getExtractedContentValue() {
3623
3657
  const propValue = this.getInlineEditablePropValue();
3658
+ if (import_editor_props4.escapedHtmlPropTypeUtil.isValid(propValue)) {
3659
+ return import_editor_props4.escapedHtmlPropTypeUtil.extract(propValue) ?? "";
3660
+ }
3624
3661
  const extracted = import_editor_props4.htmlV3PropTypeUtil.extract(propValue);
3625
3662
  return import_editor_props4.stringPropTypeUtil.extract(extracted?.content ?? null) ?? "";
3626
3663
  }
3664
+ createContentPropValue(value) {
3665
+ const content = value || "";
3666
+ const propTypeKey = this.getInlineEditablePropTypeKey();
3667
+ if (propTypeKey === import_editor_props4.htmlV3PropTypeUtil.key) {
3668
+ return import_editor_props4.htmlV3PropTypeUtil.create({
3669
+ content: import_editor_props4.stringPropTypeUtil.create(content),
3670
+ children: []
3671
+ });
3672
+ }
3673
+ return import_editor_props4.escapedHtmlPropTypeUtil.create(content);
3674
+ }
3627
3675
  setContentValue(value) {
3628
3676
  const settingKey = this.getInlineEditablePropertyName();
3629
- const html = value || "";
3630
- const parsed = (0, import_editor_props4.parseHtmlChildren)(html);
3631
- const valueToSave = import_editor_props4.htmlV3PropTypeUtil.create({
3632
- content: parsed.content ? import_editor_props4.stringPropTypeUtil.create(parsed.content) : null,
3633
- children: parsed.children
3634
- });
3677
+ const valueToSave = this.createContentPropValue(value);
3635
3678
  (0, import_editor_v1_adapters13.undoable)(
3636
3679
  {
3637
3680
  do: () => {
@@ -3660,7 +3703,7 @@ var InlineEditingReplacement = class extends ReplacementBase {
3660
3703
  return null;
3661
3704
  }
3662
3705
  if (propType.kind === "union") {
3663
- const textKeys = [import_editor_props4.htmlV3PropTypeUtil.key, import_editor_props4.stringPropTypeUtil.key];
3706
+ const textKeys = [import_editor_props4.escapedHtmlPropTypeUtil.key, import_editor_props4.htmlV3PropTypeUtil.key, import_editor_props4.stringPropTypeUtil.key];
3664
3707
  for (const key of textKeys) {
3665
3708
  if (propType.prop_types[key]) {
3666
3709
  return key;
@@ -3932,10 +3975,7 @@ var tabModelExtensions = {
3932
3975
  ...paragraphElement,
3933
3976
  settings: {
3934
3977
  ...paragraphElement.settings,
3935
- paragraph: import_editor_props5.htmlV3PropTypeUtil.create({
3936
- content: import_editor_props5.stringPropTypeUtil.create(`Tab ${position}`),
3937
- children: []
3938
- })
3978
+ paragraph: import_editor_props5.escapedHtmlPropTypeUtil.create(`Tab ${position}`)
3939
3979
  }
3940
3980
  };
3941
3981
  return [updatedParagraph, ...elements.slice(1)];
package/dist/index.mjs CHANGED
@@ -1989,6 +1989,40 @@ var dateTimeTransformer = createTransformer((values) => {
1989
1989
  }).join(" ");
1990
1990
  });
1991
1991
 
1992
+ // src/utils/sanitize-escaped-html.ts
1993
+ import DOMPurify from "dompurify";
1994
+ var ALLOWED_NON_OPERATIONAL_ATTRS = [
1995
+ "href",
1996
+ "target",
1997
+ "class",
1998
+ "id",
1999
+ "style",
2000
+ "title",
2001
+ "lang",
2002
+ "dir",
2003
+ "role"
2004
+ ];
2005
+ function getAllowedHtmlWrapperTags() {
2006
+ return window.elementorCommon?.config?.allowedHTMLWrapperTags ?? [];
2007
+ }
2008
+ function sanitizeEscapedHtml(value) {
2009
+ if (!value) {
2010
+ return "";
2011
+ }
2012
+ const allowedTags = [...getAllowedHtmlWrapperTags()];
2013
+ return DOMPurify.sanitize(value, {
2014
+ ALLOWED_TAGS: allowedTags,
2015
+ ALLOWED_ATTR: [...ALLOWED_NON_OPERATIONAL_ATTRS],
2016
+ ALLOW_DATA_ATTR: true,
2017
+ ALLOW_ARIA_ATTR: true
2018
+ });
2019
+ }
2020
+
2021
+ // src/transformers/settings/escaped-html-transformer.ts
2022
+ var escapedHtmlTransformer = createTransformer((value) => {
2023
+ return sanitizeEscapedHtml(value);
2024
+ });
2025
+
1992
2026
  // src/transformers/settings/html-v2-transformer.ts
1993
2027
  var htmlV2Transformer = createTransformer((value) => {
1994
2028
  return value?.content ?? "";
@@ -2066,11 +2100,11 @@ var plainTransformer = createTransformer((value) => {
2066
2100
  });
2067
2101
 
2068
2102
  // src/transformers/shared/svg-src-transformer.ts
2069
- import DOMPurify from "dompurify";
2103
+ import DOMPurify2 from "dompurify";
2070
2104
  import { getMediaAttachment as getMediaAttachment2 } from "@elementor/wp-media";
2071
2105
  var SVG_INLINE_STYLES = "width: 100%; height: 100%; overflow: unset;";
2072
2106
  function processSvgContent(svgText) {
2073
- const sanitized = DOMPurify.sanitize(svgText, {
2107
+ const sanitized = DOMPurify2.sanitize(svgText, {
2074
2108
  USE_PROFILES: { svg: true, svgFilters: true }
2075
2109
  });
2076
2110
  const parser = new DOMParser();
@@ -2141,7 +2175,7 @@ var videoSrcTransformer = createTransformer(async (value) => {
2141
2175
 
2142
2176
  // src/init-settings-transformers.ts
2143
2177
  function initSettingsTransformers() {
2144
- settingsTransformersRegistry.register("classes", createClassesTransformer()).register("link", linkTransformer).register("query", queryTransformer).register("image", imageTransformer).register("image-src", imageSrcTransformer).register("svg-src", svgSrcTransformer).register("video-src", videoSrcTransformer).register("attributes", attributesTransformer).register("date-time", dateTimeTransformer).register("html-v2", htmlV2Transformer).register("html-v3", htmlV3Transformer).register("date-range", dateRangeTransformer).register("time-range", timeRangeTransformer).registerFallback(plainTransformer);
2178
+ settingsTransformersRegistry.register("classes", createClassesTransformer()).register("link", linkTransformer).register("query", queryTransformer).register("image", imageTransformer).register("image-src", imageSrcTransformer).register("svg-src", svgSrcTransformer).register("video-src", videoSrcTransformer).register("attributes", attributesTransformer).register("date-time", dateTimeTransformer).register("html-v2", htmlV2Transformer).register("html-v3", htmlV3Transformer).register("escaped-html", escapedHtmlTransformer).register("date-range", dateRangeTransformer).register("time-range", timeRangeTransformer).registerFallback(plainTransformer);
2145
2179
  }
2146
2180
 
2147
2181
  // src/transformers/styles/background-color-overlay-transformer.ts
@@ -2530,11 +2564,11 @@ function createDomRenderer() {
2530
2564
  render: environment.render
2531
2565
  };
2532
2566
  }
2533
- function getAllowedHtmlWrapperTags() {
2567
+ function getAllowedHtmlWrapperTags2() {
2534
2568
  return window.elementorCommon?.config?.allowedHTMLWrapperTags ?? [];
2535
2569
  }
2536
2570
  function escapeHtmlTag(value) {
2537
- const allowedTags = getAllowedHtmlWrapperTags();
2571
+ const allowedTags = getAllowedHtmlWrapperTags2();
2538
2572
  const normalizedTag = value?.toLowerCase?.() ?? "";
2539
2573
  return allowedTags.includes(normalizedTag) ? value : "div";
2540
2574
  }
@@ -3193,8 +3227,8 @@ import { createRoot } from "react-dom/client";
3193
3227
  import * as React11 from "react";
3194
3228
  import { getContainer as getContainer2, getElementLabel, getElementType as getElementType2 } from "@elementor/editor-elements";
3195
3229
  import {
3230
+ escapedHtmlPropTypeUtil as escapedHtmlPropTypeUtil2,
3196
3231
  htmlV3PropTypeUtil as htmlV3PropTypeUtil2,
3197
- parseHtmlChildren,
3198
3232
  stringPropTypeUtil as stringPropTypeUtil2
3199
3233
  } from "@elementor/editor-props";
3200
3234
  import { __privateRunCommandSync as runCommandSync, getCurrentEditMode, undoable } from "@elementor/editor-v1-adapters";
@@ -3473,11 +3507,15 @@ var InlineEditingToolbar = ({ anchor, editor, id }) => {
3473
3507
  };
3474
3508
 
3475
3509
  // src/legacy/replacements/inline-editing/inline-editing-eligibility.ts
3476
- import { htmlV3PropTypeUtil, stringPropTypeUtil } from "@elementor/editor-props";
3510
+ import {
3511
+ escapedHtmlPropTypeUtil,
3512
+ htmlV3PropTypeUtil,
3513
+ stringPropTypeUtil
3514
+ } from "@elementor/editor-props";
3477
3515
  var hasKey = (propType) => {
3478
3516
  return "key" in propType;
3479
3517
  };
3480
- var TEXT_PROP_TYPE_KEYS = /* @__PURE__ */ new Set([htmlV3PropTypeUtil.key, stringPropTypeUtil.key]);
3518
+ var TEXT_PROP_TYPE_KEYS = /* @__PURE__ */ new Set([escapedHtmlPropTypeUtil.key, htmlV3PropTypeUtil.key, stringPropTypeUtil.key]);
3481
3519
  var isCoreTextPropTypeKey = (key) => {
3482
3520
  return TEXT_PROP_TYPE_KEYS.has(key);
3483
3521
  };
@@ -3497,7 +3535,7 @@ var isInlineEditingAllowed = ({ rawValue, propTypeFromSchema }) => {
3497
3535
  if (rawValue === null || rawValue === void 0) {
3498
3536
  return isAllowedBySchema(propTypeFromSchema);
3499
3537
  }
3500
- return htmlV3PropTypeUtil.isValid(rawValue) || stringPropTypeUtil.isValid(rawValue);
3538
+ return escapedHtmlPropTypeUtil.isValid(rawValue) || htmlV3PropTypeUtil.isValid(rawValue) || stringPropTypeUtil.isValid(rawValue);
3501
3539
  };
3502
3540
 
3503
3541
  // src/legacy/replacements/inline-editing/inline-editing-elements.tsx
@@ -3582,17 +3620,26 @@ var InlineEditingReplacement = class extends ReplacementBase {
3582
3620
  }
3583
3621
  getExtractedContentValue() {
3584
3622
  const propValue = this.getInlineEditablePropValue();
3623
+ if (escapedHtmlPropTypeUtil2.isValid(propValue)) {
3624
+ return escapedHtmlPropTypeUtil2.extract(propValue) ?? "";
3625
+ }
3585
3626
  const extracted = htmlV3PropTypeUtil2.extract(propValue);
3586
3627
  return stringPropTypeUtil2.extract(extracted?.content ?? null) ?? "";
3587
3628
  }
3629
+ createContentPropValue(value) {
3630
+ const content = value || "";
3631
+ const propTypeKey = this.getInlineEditablePropTypeKey();
3632
+ if (propTypeKey === htmlV3PropTypeUtil2.key) {
3633
+ return htmlV3PropTypeUtil2.create({
3634
+ content: stringPropTypeUtil2.create(content),
3635
+ children: []
3636
+ });
3637
+ }
3638
+ return escapedHtmlPropTypeUtil2.create(content);
3639
+ }
3588
3640
  setContentValue(value) {
3589
3641
  const settingKey = this.getInlineEditablePropertyName();
3590
- const html = value || "";
3591
- const parsed = parseHtmlChildren(html);
3592
- const valueToSave = htmlV3PropTypeUtil2.create({
3593
- content: parsed.content ? stringPropTypeUtil2.create(parsed.content) : null,
3594
- children: parsed.children
3595
- });
3642
+ const valueToSave = this.createContentPropValue(value);
3596
3643
  undoable(
3597
3644
  {
3598
3645
  do: () => {
@@ -3621,7 +3668,7 @@ var InlineEditingReplacement = class extends ReplacementBase {
3621
3668
  return null;
3622
3669
  }
3623
3670
  if (propType.kind === "union") {
3624
- const textKeys = [htmlV3PropTypeUtil2.key, stringPropTypeUtil2.key];
3671
+ const textKeys = [escapedHtmlPropTypeUtil2.key, htmlV3PropTypeUtil2.key, stringPropTypeUtil2.key];
3625
3672
  for (const key of textKeys) {
3626
3673
  if (propType.prop_types[key]) {
3627
3674
  return key;
@@ -3877,7 +3924,7 @@ function createNestedTemplatedType(type, renderer, element) {
3877
3924
  }
3878
3925
 
3879
3926
  // src/legacy/tabs-model-extensions.ts
3880
- import { htmlV3PropTypeUtil as htmlV3PropTypeUtil3, stringPropTypeUtil as stringPropTypeUtil3 } from "@elementor/editor-props";
3927
+ import { escapedHtmlPropTypeUtil as escapedHtmlPropTypeUtil3 } from "@elementor/editor-props";
3881
3928
  var tabModelExtensions = {
3882
3929
  modifyDefaultChildren(elements) {
3883
3930
  if (!Array.isArray(elements) || elements.length === 0) {
@@ -3893,10 +3940,7 @@ var tabModelExtensions = {
3893
3940
  ...paragraphElement,
3894
3941
  settings: {
3895
3942
  ...paragraphElement.settings,
3896
- paragraph: htmlV3PropTypeUtil3.create({
3897
- content: stringPropTypeUtil3.create(`Tab ${position}`),
3898
- children: []
3899
- })
3943
+ paragraph: escapedHtmlPropTypeUtil3.create(`Tab ${position}`)
3900
3944
  }
3901
3945
  };
3902
3946
  return [updatedParagraph, ...elements.slice(1)];
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.3.0-1046",
4
+ "version": "4.3.0-1048",
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.3.0-1046",
41
- "@elementor/editor-controls": "4.3.0-1046",
42
- "@elementor/editor-documents": "4.3.0-1046",
43
- "@elementor/editor-elements": "4.3.0-1046",
44
- "@elementor/editor-interactions": "4.3.0-1046",
45
- "@elementor/editor-mcp": "4.3.0-1046",
46
- "@elementor/editor-notifications": "4.3.0-1046",
47
- "@elementor/editor-props": "4.3.0-1046",
48
- "@elementor/editor-responsive": "4.3.0-1046",
49
- "@elementor/editor-styles": "4.3.0-1046",
50
- "@elementor/editor-styles-repository": "4.3.0-1046",
51
- "@elementor/editor-ui": "4.3.0-1046",
52
- "@elementor/editor-v1-adapters": "4.3.0-1046",
53
- "@elementor/events": "4.3.0-1046",
54
- "@elementor/http-client": "4.3.0-1046",
55
- "@elementor/schema": "4.3.0-1046",
56
- "@elementor/twing": "4.3.0-1046",
40
+ "@elementor/editor": "4.3.0-1048",
41
+ "@elementor/editor-controls": "4.3.0-1048",
42
+ "@elementor/editor-documents": "4.3.0-1048",
43
+ "@elementor/editor-elements": "4.3.0-1048",
44
+ "@elementor/editor-interactions": "4.3.0-1048",
45
+ "@elementor/editor-mcp": "4.3.0-1048",
46
+ "@elementor/editor-notifications": "4.3.0-1048",
47
+ "@elementor/editor-props": "4.3.0-1048",
48
+ "@elementor/editor-responsive": "4.3.0-1048",
49
+ "@elementor/editor-styles": "4.3.0-1048",
50
+ "@elementor/editor-styles-repository": "4.3.0-1048",
51
+ "@elementor/editor-ui": "4.3.0-1048",
52
+ "@elementor/editor-v1-adapters": "4.3.0-1048",
53
+ "@elementor/events": "4.3.0-1048",
54
+ "@elementor/http-client": "4.3.0-1048",
55
+ "@elementor/schema": "4.3.0-1048",
56
+ "@elementor/twing": "4.3.0-1048",
57
57
  "@elementor/ui": "1.37.5",
58
- "@elementor/utils": "4.3.0-1046",
59
- "@elementor/wp-media": "4.3.0-1046",
58
+ "@elementor/utils": "4.3.0-1048",
59
+ "@elementor/wp-media": "4.3.0-1048",
60
60
  "@floating-ui/react": "^0.27.5",
61
61
  "@wordpress/i18n": "^5.13.0",
62
62
  "dompurify": "^3.2.6"
@@ -3,6 +3,7 @@ import { attributesTransformer } from './transformers/settings/attributes-transf
3
3
  import { createClassesTransformer } from './transformers/settings/classes-transformer';
4
4
  import { dateRangeTransformer } from './transformers/settings/date-range-transformer';
5
5
  import { dateTimeTransformer } from './transformers/settings/date-time-transformer';
6
+ import { escapedHtmlTransformer } from './transformers/settings/escaped-html-transformer';
6
7
  import { htmlV2Transformer } from './transformers/settings/html-v2-transformer';
7
8
  import { htmlV3Transformer } from './transformers/settings/html-v3-transformer';
8
9
  import { linkTransformer } from './transformers/settings/link-transformer';
@@ -27,6 +28,7 @@ export function initSettingsTransformers() {
27
28
  .register( 'date-time', dateTimeTransformer )
28
29
  .register( 'html-v2', htmlV2Transformer )
29
30
  .register( 'html-v3', htmlV3Transformer )
31
+ .register( 'escaped-html', escapedHtmlTransformer )
30
32
  .register( 'date-range', dateRangeTransformer )
31
33
  .register( 'time-range', timeRangeTransformer )
32
34
  .registerFallback( plainTransformer );
@@ -13,10 +13,10 @@ const createUnionPropType = ( keys: string[] ): PropType =>
13
13
  } ) as PropType;
14
14
 
15
15
  describe( 'isInlineEditingAllowed', () => {
16
- it( 'should allow inline editing for html-v3 prop values', () => {
16
+ it( 'should allow inline editing for escaped-html prop values', () => {
17
17
  expect(
18
18
  isInlineEditingAllowed( {
19
- rawValue: { $$type: 'html-v3', value: { content: { $$type: 'string', value: 'Hello' }, children: [] } },
19
+ rawValue: { $$type: 'escaped-html', value: 'Hello' },
20
20
  propTypeFromSchema: null,
21
21
  } )
22
22
  ).toBe( true );
@@ -40,11 +40,11 @@ describe( 'isInlineEditingAllowed', () => {
40
40
  ).toBe( false );
41
41
  } );
42
42
 
43
- it( 'should allow when value is unset but schema key is html-v3', () => {
43
+ it( 'should allow when value is unset but schema key is escaped-html', () => {
44
44
  expect(
45
45
  isInlineEditingAllowed( {
46
46
  rawValue: undefined,
47
- propTypeFromSchema: createPlainPropType( 'html-v3' ),
47
+ propTypeFromSchema: createPlainPropType( 'escaped-html' ),
48
48
  } )
49
49
  ).toBe( true );
50
50
  } );
@@ -67,16 +67,16 @@ describe( 'isInlineEditingAllowed', () => {
67
67
  ).toBe( false );
68
68
  } );
69
69
 
70
- it( 'should allow when value is unset and union schema includes html-v3', () => {
70
+ it( 'should allow when value is unset and union schema includes escaped-html', () => {
71
71
  expect(
72
72
  isInlineEditingAllowed( {
73
73
  rawValue: undefined,
74
- propTypeFromSchema: createUnionPropType( [ 'dynamic', 'html-v3' ] ),
74
+ propTypeFromSchema: createUnionPropType( [ 'dynamic', 'escaped-html' ] ),
75
75
  } )
76
76
  ).toBe( true );
77
77
  } );
78
78
 
79
- it( 'should disallow when value is unset and union schema does not include html-v3/string', () => {
79
+ it( 'should disallow when value is unset and union schema does not include escaped-html/html-v3/string', () => {
80
80
  expect(
81
81
  isInlineEditingAllowed( {
82
82
  rawValue: undefined,
@@ -1,8 +1,8 @@
1
1
  import * as React from 'react';
2
2
  import { getContainer, getElementLabel, getElementType } from '@elementor/editor-elements';
3
3
  import {
4
+ escapedHtmlPropTypeUtil,
4
5
  htmlV3PropTypeUtil,
5
- parseHtmlChildren,
6
6
  type PropType,
7
7
  type PropValue,
8
8
  stringPropTypeUtil,
@@ -126,20 +126,33 @@ export default class InlineEditingReplacement extends ReplacementBase {
126
126
 
127
127
  getExtractedContentValue() {
128
128
  const propValue = this.getInlineEditablePropValue();
129
+
130
+ if ( escapedHtmlPropTypeUtil.isValid( propValue ) ) {
131
+ return escapedHtmlPropTypeUtil.extract( propValue ) ?? '';
132
+ }
133
+
129
134
  const extracted = htmlV3PropTypeUtil.extract( propValue );
130
135
 
131
136
  return stringPropTypeUtil.extract( extracted?.content ?? null ) ?? '';
132
137
  }
133
138
 
139
+ createContentPropValue( value: string | null ): PropValue {
140
+ const content = value || '';
141
+ const propTypeKey = this.getInlineEditablePropTypeKey();
142
+
143
+ if ( propTypeKey === htmlV3PropTypeUtil.key ) {
144
+ return htmlV3PropTypeUtil.create( {
145
+ content: stringPropTypeUtil.create( content ),
146
+ children: [],
147
+ } );
148
+ }
149
+
150
+ return escapedHtmlPropTypeUtil.create( content );
151
+ }
152
+
134
153
  setContentValue( value: string | null ) {
135
154
  const settingKey = this.getInlineEditablePropertyName();
136
- const html = value || '';
137
- const parsed = parseHtmlChildren( html );
138
-
139
- const valueToSave = htmlV3PropTypeUtil.create( {
140
- content: parsed.content ? stringPropTypeUtil.create( parsed.content ) : null,
141
- children: parsed.children,
142
- } );
155
+ const valueToSave = this.createContentPropValue( value );
143
156
 
144
157
  undoable(
145
158
  {
@@ -174,7 +187,7 @@ export default class InlineEditingReplacement extends ReplacementBase {
174
187
  }
175
188
 
176
189
  if ( propType.kind === 'union' ) {
177
- const textKeys = [ htmlV3PropTypeUtil.key, stringPropTypeUtil.key ];
190
+ const textKeys = [ escapedHtmlPropTypeUtil.key, htmlV3PropTypeUtil.key, stringPropTypeUtil.key ];
178
191
 
179
192
  for ( const key of textKeys ) {
180
193
  if ( propType.prop_types[ key ] ) {
@@ -1,4 +1,9 @@
1
- import { htmlV3PropTypeUtil, type PropType, stringPropTypeUtil } from '@elementor/editor-props';
1
+ import {
2
+ escapedHtmlPropTypeUtil,
3
+ htmlV3PropTypeUtil,
4
+ type PropType,
5
+ stringPropTypeUtil,
6
+ } from '@elementor/editor-props';
2
7
 
3
8
  type InlineEditingEligibilityArgs = {
4
9
  rawValue: unknown;
@@ -9,7 +14,7 @@ const hasKey = ( propType: PropType ): propType is PropType & { key: unknown } =
9
14
  return 'key' in propType;
10
15
  };
11
16
 
12
- const TEXT_PROP_TYPE_KEYS = new Set( [ htmlV3PropTypeUtil.key, stringPropTypeUtil.key ] );
17
+ const TEXT_PROP_TYPE_KEYS = new Set( [ escapedHtmlPropTypeUtil.key, htmlV3PropTypeUtil.key, stringPropTypeUtil.key ] );
13
18
 
14
19
  const isCoreTextPropTypeKey = ( key: unknown ): boolean => {
15
20
  return ( TEXT_PROP_TYPE_KEYS as Set< unknown > ).has( key );
@@ -36,5 +41,9 @@ export const isInlineEditingAllowed = ( { rawValue, propTypeFromSchema }: Inline
36
41
  return isAllowedBySchema( propTypeFromSchema );
37
42
  }
38
43
 
39
- return htmlV3PropTypeUtil.isValid( rawValue ) || stringPropTypeUtil.isValid( rawValue );
44
+ return (
45
+ escapedHtmlPropTypeUtil.isValid( rawValue ) ||
46
+ htmlV3PropTypeUtil.isValid( rawValue ) ||
47
+ stringPropTypeUtil.isValid( rawValue )
48
+ );
40
49
  };
@@ -1,4 +1,4 @@
1
- import { htmlV3PropTypeUtil, stringPropTypeUtil } from '@elementor/editor-props';
1
+ import { escapedHtmlPropTypeUtil } from '@elementor/editor-props';
2
2
 
3
3
  import { type ModelExtensions } from './create-nested-templated-element-type';
4
4
  import { registerModelExtensions } from './init-legacy-views';
@@ -23,10 +23,7 @@ const tabModelExtensions: ModelExtensions = {
23
23
  ...paragraphElement,
24
24
  settings: {
25
25
  ...paragraphElement.settings,
26
- paragraph: htmlV3PropTypeUtil.create( {
27
- content: stringPropTypeUtil.create( `Tab ${ position }` ),
28
- children: [],
29
- } ),
26
+ paragraph: escapedHtmlPropTypeUtil.create( `Tab ${ position }` ),
30
27
  },
31
28
  };
32
29
 
@@ -0,0 +1,6 @@
1
+ import { sanitizeEscapedHtml } from '../../utils/sanitize-escaped-html';
2
+ import { createTransformer } from '../create-transformer';
3
+
4
+ export const escapedHtmlTransformer = createTransformer( ( value: string | null ) => {
5
+ return sanitizeEscapedHtml( value );
6
+ } );
@@ -0,0 +1,105 @@
1
+ import { sanitizeEscapedHtml } from '../sanitize-escaped-html';
2
+
3
+ const TEST_ALLOWED_HTML_WRAPPER_TAGS = [ 'a', 'div', 'span', 'strong' ];
4
+
5
+ describe( 'sanitizeEscapedHtml', () => {
6
+ beforeEach( () => {
7
+ window.elementorCommon = {
8
+ config: {
9
+ allowedHTMLWrapperTags: TEST_ALLOWED_HTML_WRAPPER_TAGS,
10
+ },
11
+ };
12
+ } );
13
+
14
+ afterEach( () => {
15
+ delete window.elementorCommon;
16
+ } );
17
+
18
+ it( 'returns an empty string for nullish values', () => {
19
+ // Arrange & Act & Assert.
20
+ expect( sanitizeEscapedHtml( null ) ).toBe( '' );
21
+ expect( sanitizeEscapedHtml( '' ) ).toBe( '' );
22
+ } );
23
+
24
+ it( 'keeps allowed tags and strips disallowed tags', () => {
25
+ // Arrange.
26
+ const value = 'Hello <script>alert(1)</script><strong>world</strong>';
27
+
28
+ // Act.
29
+ const result = sanitizeEscapedHtml( value );
30
+
31
+ // Assert.
32
+ expect( result ).toBe( 'Hello <strong>world</strong>' );
33
+ } );
34
+
35
+ it( 'fails closed when the localized allowlist is missing', () => {
36
+ // Arrange.
37
+ delete window.elementorCommon;
38
+ const value = '<strong>world</strong>';
39
+
40
+ // Act.
41
+ const result = sanitizeEscapedHtml( value );
42
+
43
+ // Assert.
44
+ expect( result ).toBe( 'world' );
45
+ } );
46
+
47
+ it( 'strips javascript href from links', () => {
48
+ // Arrange.
49
+ const value = '<a href="javascript:alert(1)">click</a>';
50
+
51
+ // Act.
52
+ const result = sanitizeEscapedHtml( value );
53
+
54
+ // Assert.
55
+ expect( result ).not.toContain( 'javascript:' );
56
+ expect( result ).toContain( 'click' );
57
+ } );
58
+
59
+ it( 'strips data href from links', () => {
60
+ // Arrange.
61
+ const value = '<a href="data:text/html,<script>alert(1)</script>">click</a>';
62
+
63
+ // Act.
64
+ const result = sanitizeEscapedHtml( value );
65
+
66
+ // Assert.
67
+ expect( result ).not.toMatch( /<a\s+href=/i );
68
+ expect( result ).toContain( 'click' );
69
+ } );
70
+
71
+ it( 'preserves non-operational attributes', () => {
72
+ // Arrange.
73
+ const value =
74
+ '<span id="e-1" class="foo bar" style="color: red;" title="t" lang="en" dir="ltr" role="text" data-x="1" aria-label="label">world</span>';
75
+
76
+ // Act.
77
+ const result = sanitizeEscapedHtml( value );
78
+
79
+ // Assert.
80
+ expect( result ).toContain( 'id="e-1"' );
81
+ expect( result ).toContain( 'class="foo bar"' );
82
+ expect( result ).toContain( 'style="color: red;"' );
83
+ expect( result ).toContain( 'title="t"' );
84
+ expect( result ).toContain( 'lang="en"' );
85
+ expect( result ).toContain( 'dir="ltr"' );
86
+ expect( result ).toContain( 'role="text"' );
87
+ expect( result ).toContain( 'data-x="1"' );
88
+ expect( result ).toContain( 'aria-label="label"' );
89
+ expect( result ).toContain( 'world' );
90
+ } );
91
+
92
+ it( 'strips functional event-handler attributes', () => {
93
+ // Arrange.
94
+ const value = '<span id="e-1" onclick="evil()" onmouseover="evil()">world</span>';
95
+
96
+ // Act.
97
+ const result = sanitizeEscapedHtml( value );
98
+
99
+ // Assert.
100
+ expect( result ).toContain( 'id="e-1"' );
101
+ expect( result ).not.toContain( 'onclick' );
102
+ expect( result ).not.toContain( 'onmouseover' );
103
+ expect( result ).toContain( 'world' );
104
+ } );
105
+ } );
@@ -0,0 +1,32 @@
1
+ import DOMPurify from 'dompurify';
2
+
3
+ const ALLOWED_NON_OPERATIONAL_ATTRS = [
4
+ 'href',
5
+ 'target',
6
+ 'class',
7
+ 'id',
8
+ 'style',
9
+ 'title',
10
+ 'lang',
11
+ 'dir',
12
+ 'role',
13
+ ] as const;
14
+
15
+ function getAllowedHtmlWrapperTags(): readonly string[] {
16
+ return window.elementorCommon?.config?.allowedHTMLWrapperTags ?? [];
17
+ }
18
+
19
+ export function sanitizeEscapedHtml( value: string | null ): string {
20
+ if ( ! value ) {
21
+ return '';
22
+ }
23
+
24
+ const allowedTags = [ ...getAllowedHtmlWrapperTags() ];
25
+
26
+ return DOMPurify.sanitize( value, {
27
+ ALLOWED_TAGS: allowedTags,
28
+ ALLOWED_ATTR: [ ...ALLOWED_NON_OPERATIONAL_ATTRS ],
29
+ ALLOW_DATA_ATTR: true,
30
+ ALLOW_ARIA_ATTR: true,
31
+ } );
32
+ }