@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.
- package/dist/index.d.mts +3 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +535 -393
- package/dist/index.mjs +516 -370
- package/package.json +20 -20
- package/src/__tests__/flex-transformer.test.ts +11 -11
- package/src/__tests__/prop-types.ts +11 -0
- package/src/init-settings-transformers.ts +4 -0
- package/src/init-style-transformers.ts +2 -2
- package/src/init.tsx +5 -4
- package/src/legacy/__tests__/create-templated-element-type.test.ts +50 -0
- package/src/legacy/__tests__/list-type.test.ts +89 -0
- package/src/legacy/create-nested-templated-element-type.ts +3 -1
- package/src/legacy/create-templated-element-type.ts +5 -2
- package/src/legacy/list-type.ts +63 -0
- package/src/legacy/replacements/inline-editing/__tests__/inline-editing-eligibility.test.ts +7 -7
- package/src/legacy/replacements/inline-editing/inline-editing-elements.tsx +22 -9
- package/src/legacy/replacements/inline-editing/inline-editing-eligibility.ts +12 -3
- package/src/legacy/tabs-model-extensions.ts +2 -5
- package/src/legacy/twig-rendering-utils.ts +11 -2
- package/src/legacy/types.ts +4 -0
- package/src/mcp/canvas-mcp.ts +2 -4
- package/src/mcp/mcp-description.ts +18 -30
- package/src/mcp/resources/__tests__/available-widgets-resource.test.ts +22 -13
- package/src/mcp/resources/__tests__/dynamic-tags-resource.test.ts +22 -30
- package/src/mcp/resources/__tests__/widgets-schema-resource.test.ts +11 -6
- package/src/mcp/resources/available-widgets-resource.ts +13 -10
- package/src/mcp/resources/dynamic-tags-resource.ts +6 -15
- package/src/mcp/resources/widgets-schema-resource.ts +8 -5
- package/src/mcp/tools/configure-element/__tests__/tool.test.ts +130 -0
- package/src/mcp/tools/configure-element/prompt.ts +3 -6
- package/src/mcp/tools/configure-element/schema.ts +6 -0
- package/src/mcp/tools/configure-element/tool.ts +11 -1
- package/src/mcp/tools/get-page-structure/tool.ts +73 -0
- package/src/mcp/utils/__tests__/do-update-element-property.test.ts +27 -1
- package/src/mcp/utils/do-update-element-property.ts +18 -6
- package/src/mcp/utils/get-mcp-error-message.ts +16 -0
- package/src/renderers/__tests__/compute-html-tag.test.ts +55 -0
- package/src/renderers/__tests__/create-dom-renderer.test.ts +60 -0
- package/src/renderers/__tests__/fixtures/html-tag-computer-cases.json +87 -0
- package/src/renderers/compute-html-tag.ts +73 -0
- package/src/renderers/create-dom-renderer.ts +13 -22
- package/src/transformers/settings/escaped-html-transformer.ts +6 -0
- package/src/transformers/shared/__tests__/icon-transformer.test.ts +199 -0
- package/src/transformers/shared/icon-transformer.ts +231 -0
- package/src/transformers/shared/process-svg-content.ts +26 -0
- package/src/transformers/shared/svg-src-transformer.ts +1 -26
- package/src/transformers/styles/flex-transformer.ts +11 -32
- package/src/utils/__tests__/sanitize-escaped-html.test.ts +105 -0
- package/src/utils/sanitize-escaped-html.ts +32 -0
- package/src/mcp/tools/build-composition/tool.ts +0 -133
- 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-
|
|
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-
|
|
41
|
-
"@elementor/editor-controls": "4.3.0-
|
|
42
|
-
"@elementor/editor-documents": "4.3.0-
|
|
43
|
-
"@elementor/editor-elements": "4.3.0-
|
|
44
|
-
"@elementor/editor-interactions": "4.3.0-
|
|
45
|
-
"@elementor/editor-mcp": "4.3.0-
|
|
46
|
-
"@elementor/editor-notifications": "4.3.0-
|
|
47
|
-
"@elementor/editor-props": "4.3.0-
|
|
48
|
-
"@elementor/editor-responsive": "4.3.0-
|
|
49
|
-
"@elementor/editor-styles": "4.3.0-
|
|
50
|
-
"@elementor/editor-styles-repository": "4.3.0-
|
|
51
|
-
"@elementor/editor-ui": "4.3.0-
|
|
52
|
-
"@elementor/editor-v1-adapters": "4.3.0-
|
|
53
|
-
"@elementor/events": "4.3.0-
|
|
54
|
-
"@elementor/http-client": "4.3.0-
|
|
55
|
-
"@elementor/schema": "4.3.0-
|
|
56
|
-
"@elementor/twing": "4.3.0-
|
|
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-
|
|
59
|
-
"@elementor/wp-media": "4.3.0-
|
|
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
|
|
242
|
-
it( 'should
|
|
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
|
|
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
|
|
58
|
-
- Configure
|
|
59
|
-
-
|
|
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
|
|
16
|
+
it( 'should allow inline editing for escaped-html prop values', () => {
|
|
17
17
|
expect(
|
|
18
18
|
isInlineEditingAllowed( {
|
|
19
|
-
rawValue: { $$type: 'html
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 {
|
|
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
|
|
44
|
+
return (
|
|
45
|
+
escapedHtmlPropTypeUtil.isValid( rawValue ) ||
|
|
46
|
+
htmlV3PropTypeUtil.isValid( rawValue ) ||
|
|
47
|
+
stringPropTypeUtil.isValid( rawValue )
|
|
48
|
+
);
|
|
40
49
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
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:
|
|
27
|
-
content: stringPropTypeUtil.create( `Tab ${ position }` ),
|
|
28
|
-
children: [],
|
|
29
|
-
} ),
|
|
26
|
+
paragraph: escapedHtmlPropTypeUtil.create( `Tab ${ position }` ),
|
|
30
27
|
},
|
|
31
28
|
};
|
|
32
29
|
|