@elementor/editor-canvas 4.3.0-998 → 4.4.0-1064

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 (50) hide show
  1. package/dist/index.d.mts +3 -1
  2. package/dist/index.d.ts +3 -1
  3. package/dist/index.js +452 -408
  4. package/dist/index.mjs +434 -386
  5. package/package.json +20 -20
  6. package/src/init-settings-transformers.ts +4 -0
  7. package/src/init-style-transformers.ts +2 -2
  8. package/src/init.tsx +5 -4
  9. package/src/legacy/__tests__/create-templated-element-type.test.ts +50 -0
  10. package/src/legacy/__tests__/list-type.test.ts +89 -0
  11. package/src/legacy/create-nested-templated-element-type.ts +3 -1
  12. package/src/legacy/create-templated-element-type.ts +5 -2
  13. package/src/legacy/list-type.ts +63 -0
  14. package/src/legacy/replacements/inline-editing/__tests__/inline-editing-eligibility.test.ts +7 -7
  15. package/src/legacy/replacements/inline-editing/inline-editing-elements.tsx +22 -9
  16. package/src/legacy/replacements/inline-editing/inline-editing-eligibility.ts +12 -3
  17. package/src/legacy/tabs-model-extensions.ts +2 -5
  18. package/src/legacy/twig-rendering-utils.ts +11 -2
  19. package/src/legacy/types.ts +4 -0
  20. package/src/mcp/canvas-mcp.ts +2 -6
  21. package/src/mcp/mcp-description.ts +18 -30
  22. package/src/mcp/resources/__tests__/available-widgets-resource.test.ts +22 -13
  23. package/src/mcp/resources/__tests__/dynamic-tags-resource.test.ts +22 -30
  24. package/src/mcp/resources/__tests__/widgets-schema-resource.test.ts +11 -6
  25. package/src/mcp/resources/available-widgets-resource.ts +13 -10
  26. package/src/mcp/resources/dynamic-tags-resource.ts +6 -15
  27. package/src/mcp/resources/widgets-schema-resource.ts +8 -5
  28. package/src/mcp/tools/configure-element/__tests__/tool.test.ts +130 -0
  29. package/src/mcp/tools/configure-element/prompt.ts +3 -6
  30. package/src/mcp/tools/configure-element/schema.ts +6 -0
  31. package/src/mcp/tools/configure-element/tool.ts +11 -1
  32. package/src/mcp/tools/get-page-structure/tool.ts +73 -0
  33. package/src/mcp/utils/__tests__/do-update-element-property.test.ts +27 -1
  34. package/src/mcp/utils/do-update-element-property.ts +18 -6
  35. package/src/mcp/utils/get-mcp-error-message.ts +16 -0
  36. package/src/renderers/__tests__/compute-html-tag.test.ts +55 -0
  37. package/src/renderers/__tests__/create-dom-renderer.test.ts +60 -0
  38. package/src/renderers/__tests__/fixtures/html-tag-computer-cases.json +87 -0
  39. package/src/renderers/compute-html-tag.ts +73 -0
  40. package/src/renderers/create-dom-renderer.ts +13 -22
  41. package/src/transformers/settings/escaped-html-transformer.ts +6 -0
  42. package/src/transformers/shared/__tests__/icon-transformer.test.ts +74 -0
  43. package/src/transformers/shared/icon-transformer.ts +122 -0
  44. package/src/transformers/shared/process-svg-content.ts +26 -0
  45. package/src/transformers/shared/svg-src-transformer.ts +1 -26
  46. package/src/utils/__tests__/sanitize-escaped-html.test.ts +105 -0
  47. package/src/utils/sanitize-escaped-html.ts +32 -0
  48. package/src/mcp/tools/build-composition/tool.ts +0 -133
  49. package/src/mcp/tools/create-element/tool.ts +0 -61
  50. 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-998",
4
+ "version": "4.4.0-1064",
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-998",
41
- "@elementor/editor-controls": "4.3.0-998",
42
- "@elementor/editor-documents": "4.3.0-998",
43
- "@elementor/editor-elements": "4.3.0-998",
44
- "@elementor/editor-interactions": "4.3.0-998",
45
- "@elementor/editor-mcp": "4.3.0-998",
46
- "@elementor/editor-notifications": "4.3.0-998",
47
- "@elementor/editor-props": "4.3.0-998",
48
- "@elementor/editor-responsive": "4.3.0-998",
49
- "@elementor/editor-styles": "4.3.0-998",
50
- "@elementor/editor-styles-repository": "4.3.0-998",
51
- "@elementor/editor-ui": "4.3.0-998",
52
- "@elementor/editor-v1-adapters": "4.3.0-998",
53
- "@elementor/events": "4.3.0-998",
54
- "@elementor/http-client": "4.3.0-998",
55
- "@elementor/schema": "4.3.0-998",
56
- "@elementor/twing": "4.3.0-998",
40
+ "@elementor/editor": "4.4.0-1064",
41
+ "@elementor/editor-controls": "4.4.0-1064",
42
+ "@elementor/editor-documents": "4.4.0-1064",
43
+ "@elementor/editor-elements": "4.4.0-1064",
44
+ "@elementor/editor-interactions": "4.4.0-1064",
45
+ "@elementor/editor-mcp": "4.4.0-1064",
46
+ "@elementor/editor-notifications": "4.4.0-1064",
47
+ "@elementor/editor-props": "4.4.0-1064",
48
+ "@elementor/editor-responsive": "4.4.0-1064",
49
+ "@elementor/editor-styles": "4.4.0-1064",
50
+ "@elementor/editor-styles-repository": "4.4.0-1064",
51
+ "@elementor/editor-ui": "4.4.0-1064",
52
+ "@elementor/editor-v1-adapters": "4.4.0-1064",
53
+ "@elementor/events": "4.4.0-1064",
54
+ "@elementor/http-client": "4.4.0-1064",
55
+ "@elementor/schema": "4.4.0-1064",
56
+ "@elementor/twing": "4.4.0-1064",
57
57
  "@elementor/ui": "1.37.5",
58
- "@elementor/utils": "4.3.0-998",
59
- "@elementor/wp-media": "4.3.0-998",
58
+ "@elementor/utils": "4.4.0-1064",
59
+ "@elementor/wp-media": "4.4.0-1064",
60
60
  "@floating-ui/react": "^0.27.5",
61
61
  "@wordpress/i18n": "^5.13.0",
62
62
  "dompurify": "^3.2.6"
@@ -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
 
@@ -7,7 +7,8 @@ import { type ElementView, type RenderContext } from './types';
7
7
 
8
8
  export type TwigElementConfig = Required<
9
9
  Pick< V1ElementConfig, 'twig_templates' | 'twig_main_template' | 'atomic_props_schema' | 'base_styles_dictionary' >
10
- >;
10
+ > &
11
+ Pick< V1ElementConfig, 'default_html_tag' | 'html_tag_follows_link' >;
11
12
 
12
13
  export type SetupTwigRendererOptions = {
13
14
  type: string;
@@ -19,6 +20,8 @@ export type SetupTwigRendererResult = {
19
20
  templateKey: string;
20
21
  baseStylesDictionary: Record< string, unknown >;
21
22
  resolveProps: PropsResolver;
23
+ defaultHtmlTag: string;
24
+ htmlTagFollowsLink: boolean;
22
25
  };
23
26
 
24
27
  export function setupTwigRenderer( { renderer, element }: SetupTwigRendererOptions ): SetupTwigRendererResult {
@@ -34,7 +37,13 @@ export function setupTwigRenderer( { renderer, element }: SetupTwigRendererOptio
34
37
  schema: element.atomic_props_schema,
35
38
  } );
36
39
 
37
- return { templateKey, baseStylesDictionary, resolveProps };
40
+ return {
41
+ templateKey,
42
+ baseStylesDictionary,
43
+ resolveProps,
44
+ defaultHtmlTag: element.default_html_tag ?? 'div',
45
+ htmlTagFollowsLink: element.html_tag_follows_link ?? true,
46
+ };
38
47
  }
39
48
 
40
49
  export interface TwigViewInterface extends Omit< ElementView, 'getResolverRenderContext' > {
@@ -174,6 +174,10 @@ export declare class ElementView {
174
174
  once: ( event: string, callback: () => void ) => void;
175
175
 
176
176
  getContainer(): V1Element;
177
+
178
+ initialize?( ...args: unknown[] ): void;
179
+
180
+ listenTo( target: unknown, event: string, callback: () => void ): void;
177
181
  }
178
182
 
179
183
  export declare class TemplatedElementView extends ElementView {
@@ -10,10 +10,8 @@ import { initEditorStateResource } from './resources/editor-state-resource';
10
10
  import { initGeneralContextResource } from './resources/general-context-resource';
11
11
  import { initSelectedElementResource } from './resources/selected-element-resource';
12
12
  import { initWidgetsSchemaResource } from './resources/widgets-schema-resource';
13
- import { initBuildCompositionTool } from './tools/build-composition/tool';
14
13
  import { initConfigureElementTool } from './tools/configure-element/tool';
15
- import { initCreateElementTool } from './tools/create-element/tool';
16
- import { initGetElementConfigTool } from './tools/get-element-config/tool';
14
+ import { initGetPageStructureTool } from './tools/get-page-structure/tool';
17
15
  import { getDynamicTagNamesByCategories } from './utils/resolve-dynamic-tag';
18
16
 
19
17
  export const initCanvasMcp = ( reg: MCPRegistryEntry ) => {
@@ -29,9 +27,7 @@ export const initCanvasMcp = ( reg: MCPRegistryEntry ) => {
29
27
  initEditorStateResource( reg );
30
28
  initGeneralContextResource( reg );
31
29
  initBestPracticesResource( reg );
32
- initGetElementConfigTool( reg );
33
30
  initConfigureElementTool( reg );
34
- initCreateElementTool( reg );
35
- initBuildCompositionTool( reg );
31
+ initGetPageStructureTool( reg );
36
32
  initBreakpointsResource( reg );
37
33
  };