@elementor/editor-canvas 4.3.0-999 → 4.3.0-beta2

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.
Files changed (52) hide show
  1. package/dist/index.d.mts +3 -1
  2. package/dist/index.d.ts +3 -1
  3. package/dist/index.js +535 -393
  4. package/dist/index.mjs +516 -370
  5. package/package.json +20 -20
  6. package/src/__tests__/flex-transformer.test.ts +11 -11
  7. package/src/__tests__/prop-types.ts +11 -0
  8. package/src/init-settings-transformers.ts +4 -0
  9. package/src/init-style-transformers.ts +2 -2
  10. package/src/init.tsx +5 -4
  11. package/src/legacy/__tests__/create-templated-element-type.test.ts +50 -0
  12. package/src/legacy/__tests__/list-type.test.ts +89 -0
  13. package/src/legacy/create-nested-templated-element-type.ts +3 -1
  14. package/src/legacy/create-templated-element-type.ts +5 -2
  15. package/src/legacy/list-type.ts +63 -0
  16. package/src/legacy/replacements/inline-editing/__tests__/inline-editing-eligibility.test.ts +7 -7
  17. package/src/legacy/replacements/inline-editing/inline-editing-elements.tsx +22 -9
  18. package/src/legacy/replacements/inline-editing/inline-editing-eligibility.ts +12 -3
  19. package/src/legacy/tabs-model-extensions.ts +2 -5
  20. package/src/legacy/twig-rendering-utils.ts +11 -2
  21. package/src/legacy/types.ts +4 -0
  22. package/src/mcp/canvas-mcp.ts +2 -4
  23. package/src/mcp/mcp-description.ts +18 -30
  24. package/src/mcp/resources/__tests__/available-widgets-resource.test.ts +22 -13
  25. package/src/mcp/resources/__tests__/dynamic-tags-resource.test.ts +22 -30
  26. package/src/mcp/resources/__tests__/widgets-schema-resource.test.ts +11 -6
  27. package/src/mcp/resources/available-widgets-resource.ts +13 -10
  28. package/src/mcp/resources/dynamic-tags-resource.ts +6 -15
  29. package/src/mcp/resources/widgets-schema-resource.ts +8 -5
  30. package/src/mcp/tools/configure-element/__tests__/tool.test.ts +130 -0
  31. package/src/mcp/tools/configure-element/prompt.ts +3 -6
  32. package/src/mcp/tools/configure-element/schema.ts +6 -0
  33. package/src/mcp/tools/configure-element/tool.ts +11 -1
  34. package/src/mcp/tools/get-page-structure/tool.ts +73 -0
  35. package/src/mcp/utils/__tests__/do-update-element-property.test.ts +27 -1
  36. package/src/mcp/utils/do-update-element-property.ts +18 -6
  37. package/src/mcp/utils/get-mcp-error-message.ts +16 -0
  38. package/src/renderers/__tests__/compute-html-tag.test.ts +55 -0
  39. package/src/renderers/__tests__/create-dom-renderer.test.ts +60 -0
  40. package/src/renderers/__tests__/fixtures/html-tag-computer-cases.json +87 -0
  41. package/src/renderers/compute-html-tag.ts +73 -0
  42. package/src/renderers/create-dom-renderer.ts +13 -22
  43. package/src/transformers/settings/escaped-html-transformer.ts +6 -0
  44. package/src/transformers/shared/__tests__/icon-transformer.test.ts +199 -0
  45. package/src/transformers/shared/icon-transformer.ts +231 -0
  46. package/src/transformers/shared/process-svg-content.ts +26 -0
  47. package/src/transformers/shared/svg-src-transformer.ts +1 -26
  48. package/src/transformers/styles/flex-transformer.ts +11 -32
  49. package/src/utils/__tests__/sanitize-escaped-html.test.ts +105 -0
  50. package/src/utils/sanitize-escaped-html.ts +32 -0
  51. package/src/mcp/tools/build-composition/tool.ts +0 -133
  52. package/src/mcp/tools/get-element-config/tool.ts +0 -114
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-999",
4
+ "version": "4.3.0-beta2",
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-999",
41
- "@elementor/editor-controls": "4.3.0-999",
42
- "@elementor/editor-documents": "4.3.0-999",
43
- "@elementor/editor-elements": "4.3.0-999",
44
- "@elementor/editor-interactions": "4.3.0-999",
45
- "@elementor/editor-mcp": "4.3.0-999",
46
- "@elementor/editor-notifications": "4.3.0-999",
47
- "@elementor/editor-props": "4.3.0-999",
48
- "@elementor/editor-responsive": "4.3.0-999",
49
- "@elementor/editor-styles": "4.3.0-999",
50
- "@elementor/editor-styles-repository": "4.3.0-999",
51
- "@elementor/editor-ui": "4.3.0-999",
52
- "@elementor/editor-v1-adapters": "4.3.0-999",
53
- "@elementor/events": "4.3.0-999",
54
- "@elementor/http-client": "4.3.0-999",
55
- "@elementor/schema": "4.3.0-999",
56
- "@elementor/twing": "4.3.0-999",
40
+ "@elementor/editor": "4.3.0-beta2",
41
+ "@elementor/editor-controls": "4.3.0-beta2",
42
+ "@elementor/editor-documents": "4.3.0-beta2",
43
+ "@elementor/editor-elements": "4.3.0-beta2",
44
+ "@elementor/editor-interactions": "4.3.0-beta2",
45
+ "@elementor/editor-mcp": "4.3.0-beta2",
46
+ "@elementor/editor-notifications": "4.3.0-beta2",
47
+ "@elementor/editor-props": "4.3.0-beta2",
48
+ "@elementor/editor-responsive": "4.3.0-beta2",
49
+ "@elementor/editor-styles": "4.3.0-beta2",
50
+ "@elementor/editor-styles-repository": "4.3.0-beta2",
51
+ "@elementor/editor-ui": "4.3.0-beta2",
52
+ "@elementor/editor-v1-adapters": "4.3.0-beta2",
53
+ "@elementor/events": "4.3.0-beta2",
54
+ "@elementor/http-client": "4.3.0-beta2",
55
+ "@elementor/schema": "4.3.0-beta2",
56
+ "@elementor/twing": "4.3.0-beta2",
57
57
  "@elementor/ui": "1.37.5",
58
- "@elementor/utils": "4.3.0-999",
59
- "@elementor/wp-media": "4.3.0-999",
58
+ "@elementor/utils": "4.3.0-beta2",
59
+ "@elementor/wp-media": "4.3.0-beta2",
60
60
  "@floating-ui/react": "^0.27.5",
61
61
  "@wordpress/i18n": "^5.13.0",
62
62
  "dompurify": "^3.2.6"
@@ -68,7 +68,7 @@ describe( 'flexTransformer', () => {
68
68
  } );
69
69
 
70
70
  describe( 'when only two values are provided', () => {
71
- it( 'should return "grow shrink" when grow and shrink are set', () => {
71
+ it( 'should return "grow shrink auto" when grow and shrink are set', () => {
72
72
  // Arrange.
73
73
  const value: Flex = {
74
74
  flexGrow: 1,
@@ -80,7 +80,7 @@ describe( 'flexTransformer', () => {
80
80
  const result = flexTransformer( value, { key: 'flex' } );
81
81
 
82
82
  // Assert.
83
- expect( result ).toBe( '1 2' );
83
+ expect( result ).toBe( '1 2 auto' );
84
84
  } );
85
85
 
86
86
  it( 'should return "grow 1 basis" when grow and basis are set', () => {
@@ -115,7 +115,7 @@ describe( 'flexTransformer', () => {
115
115
  } );
116
116
 
117
117
  describe( 'when only one value is provided', () => {
118
- it( 'should return "grow" when only grow is set', () => {
118
+ it( 'should return "grow 1 auto" when only grow is set', () => {
119
119
  // Arrange.
120
120
  const value: Flex = {
121
121
  flexGrow: 1,
@@ -127,10 +127,10 @@ describe( 'flexTransformer', () => {
127
127
  const result = flexTransformer( value, { key: 'flex' } );
128
128
 
129
129
  // Assert.
130
- expect( result ).toBe( '1' );
130
+ expect( result ).toBe( '1 1 auto' );
131
131
  } );
132
132
 
133
- it( 'should return "0 shrink" when only shrink is set', () => {
133
+ it( 'should return "0 shrink auto" when only shrink is set', () => {
134
134
  // Arrange.
135
135
  const value: Flex = {
136
136
  flexGrow: null,
@@ -142,7 +142,7 @@ describe( 'flexTransformer', () => {
142
142
  const result = flexTransformer( value, { key: 'flex' } );
143
143
 
144
144
  // Assert.
145
- expect( result ).toBe( '0 2' );
145
+ expect( result ).toBe( '0 2 auto' );
146
146
  } );
147
147
 
148
148
  it( 'should return "0 1 basis" when only basis is set', () => {
@@ -238,8 +238,8 @@ describe( 'flexTransformer', () => {
238
238
  } );
239
239
  } );
240
240
 
241
- describe( 'CSS shorthand behavior', () => {
242
- it( 'should follow CSS flex shorthand rules for single value', () => {
241
+ describe( 'CSS initial values for omitted longhands', () => {
242
+ it( 'should default shrink and basis when only grow is set', () => {
243
243
  // Arrange.
244
244
  const value: Flex = {
245
245
  flexGrow: 2,
@@ -251,10 +251,10 @@ describe( 'flexTransformer', () => {
251
251
  const result = flexTransformer( value, { key: 'flex' } );
252
252
 
253
253
  // Assert.
254
- expect( result ).toBe( '2' );
254
+ expect( result ).toBe( '2 1 auto' );
255
255
  } );
256
256
 
257
- it( 'should follow CSS flex shorthand rules for two values', () => {
257
+ it( 'should default basis when grow and shrink are set', () => {
258
258
  // Arrange.
259
259
  const value: Flex = {
260
260
  flexGrow: 1,
@@ -266,7 +266,7 @@ describe( 'flexTransformer', () => {
266
266
  const result = flexTransformer( value, { key: 'flex' } );
267
267
 
268
268
  // Assert.
269
- expect( result ).toBe( '1 2' );
269
+ expect( result ).toBe( '1 2 auto' );
270
270
  } );
271
271
  } );
272
272
  } );
@@ -36,6 +36,17 @@ export function linkPropType() {
36
36
  } );
37
37
  }
38
38
 
39
+ export function iconPropType() {
40
+ return createMockPropType( {
41
+ kind: 'object',
42
+ key: 'icon',
43
+ shape: {
44
+ value: stringPropType(),
45
+ library: stringPropType(),
46
+ },
47
+ } );
48
+ }
49
+
39
50
  export function imagePropType() {
40
51
  return createMockPropType( {
41
52
  kind: 'object',
@@ -3,11 +3,13 @@ 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';
9
10
  import { queryTransformer } from './transformers/settings/query-transformer';
10
11
  import { timeRangeTransformer } from './transformers/settings/time-range-transformer';
12
+ import { iconTransformer } from './transformers/shared/icon-transformer';
11
13
  import { imageSrcTransformer } from './transformers/shared/image-src-transformer';
12
14
  import { imageTransformer } from './transformers/shared/image-transformer';
13
15
  import { plainTransformer } from './transformers/shared/plain-transformer';
@@ -22,11 +24,13 @@ export function initSettingsTransformers() {
22
24
  .register( 'image', imageTransformer )
23
25
  .register( 'image-src', imageSrcTransformer )
24
26
  .register( 'svg-src', svgSrcTransformer )
27
+ .register( 'icon', iconTransformer )
25
28
  .register( 'video-src', videoSrcTransformer )
26
29
  .register( 'attributes', attributesTransformer )
27
30
  .register( 'date-time', dateTimeTransformer )
28
31
  .register( 'html-v2', htmlV2Transformer )
29
32
  .register( 'html-v3', htmlV3Transformer )
33
+ .register( 'escaped-html', escapedHtmlTransformer )
30
34
  .register( 'date-range', dateRangeTransformer )
31
35
  .register( 'time-range', timeRangeTransformer )
32
36
  .registerFallback( plainTransformer );
@@ -80,14 +80,14 @@ export function initStyleTransformers() {
80
80
  )
81
81
  .register( 'flex', flexTransformer )
82
82
  .register(
83
- 'border-width',
83
+ 'border-width-v2',
84
84
  createMultiPropsTransformer(
85
85
  [ 'block-start', 'block-end', 'inline-start', 'inline-end' ],
86
86
  ( { key } ) => `border-${ key }-width`
87
87
  )
88
88
  )
89
89
  .register(
90
- 'border-radius',
90
+ 'border-radius-v2',
91
91
  createMultiPropsTransformer(
92
92
  [ 'start-start', 'start-end', 'end-start', 'end-end' ],
93
93
  ( { key } ) => `border-${ key }-radius`
package/src/init.tsx CHANGED
@@ -10,6 +10,7 @@ import { initFormNestingPrevention } from './form-structure/prevent-form-nesting
10
10
  import { initSettingsTransformers } from './init-settings-transformers';
11
11
  import { initStyleTransformers } from './init-style-transformers';
12
12
  import { initLegacyViews } from './legacy/init-legacy-views';
13
+ import { initListType } from './legacy/list-type';
13
14
  import { initViewReplacements } from './legacy/replacements/manager';
14
15
  import { initTabsModelExtensions } from './legacy/tabs-model-extensions';
15
16
  import { initCanvasMcp } from './mcp/canvas-mcp';
@@ -54,14 +55,14 @@ export function init() {
54
55
  initCanvasMcp(
55
56
  getMCPByDomain( 'canvas', {
56
57
  instructions: `Everything related to V4 ( Atomic ) canvas.
57
- # Canvas workflow for new compositions
58
- - Configure elements settings and styles
59
- - Build compositions/sections out of V4 atomic elements using context aware designs using the website resources
60
- - Get and retrieve element configuration values
58
+ # Canvas workflow
59
+ - Configure element settings and styles with configure-element
60
+ - Get page structure and element configuration values
61
61
  `,
62
62
  docs: mcpDescription,
63
63
  } )
64
64
  );
65
65
 
66
66
  initTabsModelExtensions();
67
+ initListType();
67
68
  }
@@ -5,6 +5,7 @@ import {
5
5
  createTemplatedElementType,
6
6
  createTemplatedElementView,
7
7
  } from '../create-templated-element-type';
8
+ import { type ElementView } from '../types';
8
9
 
9
10
  const MOCK_ELEMENT_TYPE = 'test-element';
10
11
  const MOCK_HTML = '<div>Element</div>';
@@ -19,6 +20,8 @@ const createMockElementConfig = () => ( {
19
20
  twig_main_template: 'main',
20
21
  atomic_props_schema: {},
21
22
  base_styles_dictionary: {},
23
+ default_html_tag: 'div',
24
+ html_tag_follows_link: true,
22
25
  } );
23
26
 
24
27
  describe( 'createTemplatedElementType', () => {
@@ -116,6 +119,53 @@ describe( 'createTemplatedElementView', () => {
116
119
  expect( utils.register ).toHaveBeenCalledWith( 'template2', '<div>Template 2</div>' );
117
120
  } );
118
121
  } );
122
+
123
+ describe( 'render context', () => {
124
+ it( 'should inject computed tag into the twig render context', async () => {
125
+ // Arrange
126
+ const utils = createMockRenderer();
127
+ const ViewClass = createTemplatedElementView( {
128
+ type: MOCK_ELEMENT_TYPE,
129
+ renderer: utils,
130
+ element: {
131
+ ...createMockElementConfig(),
132
+ twig_templates: {
133
+ main: '<{{ tag | e("html_tag") }}></{{ tag | e("html_tag") }}>',
134
+ },
135
+ },
136
+ } );
137
+
138
+ const view = new ViewClass() as unknown as ElementView & {
139
+ _abortController: AbortController;
140
+ };
141
+ view.model = {
142
+ get: ( key: string ) => {
143
+ if ( key === 'id' ) {
144
+ return 'test-id';
145
+ }
146
+
147
+ if ( key === 'settings' ) {
148
+ return { toJSON: () => ( {} ) };
149
+ }
150
+
151
+ return undefined;
152
+ },
153
+ } as unknown as ElementView[ 'model' ];
154
+ view.isRendered = false;
155
+ view._abortController = new AbortController();
156
+ view.triggerMethod = jest.fn();
157
+ view.bindUIElements = jest.fn();
158
+ view.$el = { html: jest.fn() } as unknown as ElementView[ '$el' ];
159
+ view._ensureViewIsIntact = jest.fn();
160
+ view.resetChildViewContainer = jest.fn();
161
+
162
+ // Act
163
+ await view._renderTemplate();
164
+
165
+ // Assert
166
+ expect( utils.render ).toHaveBeenCalledWith( 'main', expect.objectContaining( { tag: 'div' } ) );
167
+ } );
168
+ } );
119
169
  } );
120
170
 
121
171
  describe( 'canBeTemplated', () => {
@@ -0,0 +1,89 @@
1
+ import {
2
+ createNestedTemplatedElementType,
3
+ createNestedTemplatedElementView,
4
+ } from '../create-nested-templated-element-type';
5
+ import { elementsLegacyTypes } from '../init-legacy-views';
6
+ import { initListType } from '../list-type';
7
+ import type { ElementType, NestedTemplatedElementViewClass } from '../types';
8
+
9
+ jest.mock( '../create-nested-templated-element-type', () => ( {
10
+ createNestedTemplatedElementType: jest.fn(),
11
+ createNestedTemplatedElementView: jest.fn(),
12
+ } ) );
13
+
14
+ const LIST_TYPE = 'e-list';
15
+ const resetListType = () => Reflect.deleteProperty( elementsLegacyTypes, LIST_TYPE );
16
+
17
+ const createMockBaseType = (): typeof ElementType => {
18
+ const MockBaseType = function MockBaseType() {};
19
+
20
+ MockBaseType.prototype.getType = () => LIST_TYPE;
21
+ MockBaseType.prototype.getView = () => function MockBaseView() {};
22
+
23
+ return MockBaseType as unknown as typeof ElementType;
24
+ };
25
+
26
+ const createMockBaseView = ( generatedViewIdRef: { current: number } ): NestedTemplatedElementViewClass => {
27
+ const MockBaseView = Object.assign( function BaseViewMock() {}, {
28
+ extend() {
29
+ return Object.assign( function ExtendedViewMock() {}, {
30
+ viewId: ++generatedViewIdRef.current,
31
+ } );
32
+ },
33
+ } );
34
+
35
+ return MockBaseView as unknown as NestedTemplatedElementViewClass;
36
+ };
37
+
38
+ const createMockOptions = () => ( {
39
+ type: LIST_TYPE,
40
+ renderer: {
41
+ register: jest.fn(),
42
+ render: jest.fn(),
43
+ },
44
+ element: {
45
+ twig_templates: {},
46
+ twig_main_template: 'main',
47
+ atomic_props_schema: {},
48
+ base_styles_dictionary: {},
49
+ default_html_tag: 'ul',
50
+ html_tag_follows_link: false,
51
+ support_nesting: true,
52
+ },
53
+ } );
54
+
55
+ describe( 'initListType', () => {
56
+ beforeEach( () => {
57
+ const generatedViewIdRef = { current: 0 };
58
+
59
+ jest.clearAllMocks();
60
+ resetListType();
61
+
62
+ jest.mocked( createNestedTemplatedElementType ).mockImplementation( () => createMockBaseType() );
63
+
64
+ jest.mocked( createNestedTemplatedElementView ).mockImplementation( () =>
65
+ createMockBaseView( generatedViewIdRef )
66
+ );
67
+ } );
68
+
69
+ afterEach( () => {
70
+ resetListType();
71
+ } );
72
+
73
+ it( 'should reuse the same view class for multiple list type instances', () => {
74
+ // Arrange
75
+ initListType();
76
+ const createListType = elementsLegacyTypes[ LIST_TYPE ];
77
+ const ElementType = createListType( createMockOptions() );
78
+
79
+ // Act
80
+ const typeInstance1 = new ElementType();
81
+ const typeInstance2 = new ElementType();
82
+ const viewClass1 = typeInstance1.getView();
83
+ const viewClass2 = typeInstance2.getView();
84
+
85
+ // Assert
86
+ expect( viewClass1 ).toBe( viewClass2 );
87
+ expect( createNestedTemplatedElementView ).toHaveBeenCalledTimes( 1 );
88
+ } );
89
+ } );
@@ -1,5 +1,6 @@
1
1
  import { ELEMENT_STYLE_CHANGE_EVENT, type V1ElementModelProps } from '@elementor/editor-elements';
2
2
 
3
+ import { computeHtmlTag } from '../renderers/compute-html-tag';
3
4
  import { type DomRenderer } from '../renderers/create-dom-renderer';
4
5
  import { signalizedProcess } from '../utils/signalized-process';
5
6
  import { createPendingElement } from './create-pending-element';
@@ -93,7 +94,7 @@ export function createNestedTemplatedElementView( {
93
94
  }: CreateNestedTemplatedElementViewOptions ): NestedTemplatedElementViewClass {
94
95
  const legacyWindow = window as unknown as LegacyWindow;
95
96
 
96
- const { templateKey, baseStylesDictionary, resolveProps } = setupTwigRenderer( {
97
+ const { templateKey, baseStylesDictionary, resolveProps, defaultHtmlTag, htmlTagFollowsLink } = setupTwigRenderer( {
97
98
  type,
98
99
  renderer,
99
100
  element,
@@ -198,6 +199,7 @@ export function createNestedTemplatedElementView( {
198
199
  interaction_id: this.getInteractionId(),
199
200
  type,
200
201
  settings: resolvedSettings,
202
+ tag: computeHtmlTag( resolvedSettings, defaultHtmlTag, { followLink: htmlTagFollowsLink } ),
201
203
  base_styles: baseStylesDictionary,
202
204
  editor_attributes: buildEditorAttributes( model ),
203
205
  editor_classes: buildEditorClasses( model ),
@@ -1,5 +1,6 @@
1
1
  import { type V1ElementConfig } from '@elementor/editor-elements';
2
2
 
3
+ import { computeHtmlTag } from '../renderers/compute-html-tag';
3
4
  import { type DomRenderer } from '../renderers/create-dom-renderer';
4
5
  import { signalizedProcess } from '../utils/signalized-process';
5
6
  import { createElementViewClassDeclaration } from './create-element-type';
@@ -27,7 +28,8 @@ export type CreateTemplatedElementTypeOptions = {
27
28
 
28
29
  export type TemplatedElementConfig = Required<
29
30
  Pick< V1ElementConfig, 'twig_templates' | 'twig_main_template' | 'atomic_props_schema' | 'base_styles_dictionary' >
30
- >;
31
+ > &
32
+ Pick< V1ElementConfig, 'default_html_tag' | 'html_tag_follows_link' >;
31
33
 
32
34
  export function createTemplatedElementType( {
33
35
  type,
@@ -69,7 +71,7 @@ export function createTemplatedElementView( {
69
71
  }: CreateTemplatedElementTypeOptions ): typeof TemplatedElementView {
70
72
  const BaseView = createElementViewClassDeclaration();
71
73
 
72
- const { templateKey, baseStylesDictionary, resolveProps } = setupTwigRenderer( {
74
+ const { templateKey, baseStylesDictionary, resolveProps, defaultHtmlTag, htmlTagFollowsLink } = setupTwigRenderer( {
73
75
  type,
74
76
  renderer,
75
77
  element,
@@ -165,6 +167,7 @@ export function createTemplatedElementView( {
165
167
  interaction_id: this.getInteractionId(),
166
168
  type,
167
169
  settings,
170
+ tag: computeHtmlTag( settings, defaultHtmlTag, { followLink: htmlTagFollowsLink } ),
168
171
  base_styles: baseStylesDictionary,
169
172
  ...( this.getResolverRenderContext?.() ?? {} ),
170
173
  };
@@ -0,0 +1,63 @@
1
+ import {
2
+ createNestedTemplatedElementType,
3
+ type CreateNestedTemplatedElementTypeOptions,
4
+ createNestedTemplatedElementView,
5
+ } from './create-nested-templated-element-type';
6
+ import { registerElementType } from './init-legacy-views';
7
+ import type { ElementType, RenderContext } from './types';
8
+
9
+ const LIST_TYPE = 'e-list';
10
+
11
+ /**
12
+ * Initialize the e-list element type with custom render context.
13
+ */
14
+ export function initListType() {
15
+ registerElementType( LIST_TYPE, ( options ) =>
16
+ createListType( options as CreateNestedTemplatedElementTypeOptions )
17
+ );
18
+ }
19
+
20
+ function createListType( options: CreateNestedTemplatedElementTypeOptions ): typeof ElementType {
21
+ const BaseType = createNestedTemplatedElementType( options );
22
+ let ListView: ReturnType< typeof createListView > | null = null;
23
+
24
+ return class extends BaseType {
25
+ getView() {
26
+ if ( ! ListView ) {
27
+ ListView = createListView( options );
28
+ }
29
+
30
+ return ListView;
31
+ }
32
+ };
33
+ }
34
+
35
+ function createListView( options: CreateNestedTemplatedElementTypeOptions ) {
36
+ const BaseView = createNestedTemplatedElementView( options );
37
+
38
+ return BaseView.extend( {
39
+ getRenderContext(): RenderContext | undefined {
40
+ const parentContext = this._parent?.getRenderContext?.();
41
+ const settings = this.model.get( 'settings' );
42
+ const showMarkersProp = settings?.get?.( 'show_markers' ) as unknown;
43
+ const showMarkers = ( showMarkersProp as { value?: boolean } )?.value ?? showMarkersProp ?? true;
44
+
45
+ return {
46
+ ...parentContext,
47
+ show_markers: showMarkers,
48
+ };
49
+ },
50
+
51
+ getResolverRenderContext(): RenderContext | undefined {
52
+ const parentContext = this._parent?.getResolverRenderContext?.();
53
+ const settings = this.model.get( 'settings' );
54
+ const showMarkersProp = settings?.get?.( 'show_markers' ) as unknown;
55
+ const showMarkers = ( showMarkersProp as { value?: boolean } )?.value ?? showMarkersProp ?? true;
56
+
57
+ return {
58
+ ...parentContext,
59
+ show_markers: showMarkers,
60
+ };
61
+ },
62
+ } );
63
+ }
@@ -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