@elementor/editor-canvas 0.13.1 → 0.14.0
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/.turbo/turbo-build.log +10 -10
- package/CHANGELOG.md +17 -0
- package/dist/index.d.mts +9 -8
- package/dist/index.d.ts +9 -8
- package/dist/index.js +38 -28
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +46 -31
- package/dist/index.mjs.map +1 -1
- package/package.json +7 -6
- package/src/__tests__/__mocks__/styles-schema.ts +3 -3
- package/src/__tests__/settings-props-resolver.test.ts +1 -1
- package/src/__tests__/styles-prop-resolver.test.ts +7 -7
- package/src/components/__tests__/elements-overlays.test.tsx +39 -34
- package/src/components/element-overlay.tsx +3 -2
- package/src/components/elements-overlays.tsx +26 -9
- package/src/hooks/use-floating-on-element.ts +9 -6
- package/src/init-settings-transformers.ts +1 -5
- package/src/init-style-transformers.ts +2 -6
- package/src/init-styles-renderer.ts +1 -1
- package/src/renderers/__tests__/create-dom-renderer.test.ts +66 -0
- package/src/renderers/__tests__/create-props-resolver.test.ts +123 -15
- package/src/renderers/create-dom-renderer.ts +56 -0
- package/src/renderers/create-props-resolver.ts +7 -7
- package/src/renderers/render-styles.ts +4 -0
- package/src/transformers/create-transformers-registry.ts +16 -5
- package/src/transformers/styles/background-image-position-offset-transformer.ts +1 -1
- package/src/transformers/styles/background-image-size-scale-transformer.ts +1 -1
- package/src/transformers/types.ts +1 -5
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { getElements, useSelectedElement } from '@elementor/editor-elements';
|
|
3
|
+
import {
|
|
4
|
+
__privateUseIsRouteActive as useIsRouteActive,
|
|
5
|
+
__privateUseListenTo as useListenTo,
|
|
6
|
+
useEditMode,
|
|
7
|
+
windowEvent,
|
|
8
|
+
} from '@elementor/editor-v1-adapters';
|
|
4
9
|
|
|
5
10
|
import { ElementOverlay } from './element-overlay';
|
|
6
11
|
|
|
7
12
|
export function ElementsOverlays() {
|
|
8
13
|
const selected = useSelectedElement();
|
|
9
|
-
const
|
|
14
|
+
const elements = useElementsDom();
|
|
10
15
|
const currentEditMode = useEditMode();
|
|
11
16
|
|
|
12
17
|
const isEditMode = currentEditMode === 'edit';
|
|
@@ -16,12 +21,24 @@ export function ElementsOverlays() {
|
|
|
16
21
|
|
|
17
22
|
return (
|
|
18
23
|
isActive &&
|
|
19
|
-
|
|
20
|
-
<ElementOverlay
|
|
21
|
-
element={ el }
|
|
22
|
-
key={ el.dataset.id }
|
|
23
|
-
isSelected={ selected.element?.id === el.dataset.id }
|
|
24
|
-
/>
|
|
24
|
+
elements.map( ( [ id, element ] ) => (
|
|
25
|
+
<ElementOverlay key={ id } id={ id } element={ element } isSelected={ selected.element?.id === id } />
|
|
25
26
|
) )
|
|
26
27
|
);
|
|
27
28
|
}
|
|
29
|
+
|
|
30
|
+
const ELEMENTS_DATA_ATTR = 'atomic';
|
|
31
|
+
|
|
32
|
+
type IdElementTuple = [ string, HTMLElement ];
|
|
33
|
+
|
|
34
|
+
function useElementsDom() {
|
|
35
|
+
return useListenTo(
|
|
36
|
+
[ windowEvent( 'elementor/editor/element-rendered' ), windowEvent( 'elementor/editor/element-destroyed' ) ],
|
|
37
|
+
() => {
|
|
38
|
+
return getElements()
|
|
39
|
+
.filter( ( el ) => ELEMENTS_DATA_ATTR in ( el.view?.el?.dataset ?? {} ) )
|
|
40
|
+
.map( ( element ) => [ element.id, element.view?.getDomElement?.()?.get?.( 0 ) ] )
|
|
41
|
+
.filter( ( item ): item is IdElementTuple => !! item[ 1 ] );
|
|
42
|
+
}
|
|
43
|
+
);
|
|
44
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useState } from 'react';
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
2
|
import { autoUpdate, offset, size, useFloating } from '@floating-ui/react';
|
|
3
3
|
|
|
4
4
|
type Options = {
|
|
@@ -14,11 +14,7 @@ export function useFloatingOnElement( { element, isSelected }: Options ) {
|
|
|
14
14
|
open: isOpen || isSelected,
|
|
15
15
|
onOpenChange: setIsOpen,
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
whileElementsMounted: ( ...args ) => autoUpdate( ...args, { animationFrame: true } ),
|
|
19
|
-
|
|
20
|
-
// The first element in the canvas is `display: contents` so we need to use the first child.
|
|
21
|
-
elements: { reference: element.firstElementChild },
|
|
17
|
+
whileElementsMounted: autoUpdate,
|
|
22
18
|
|
|
23
19
|
middleware: [
|
|
24
20
|
// Match the floating element's size to the reference element.
|
|
@@ -36,6 +32,13 @@ export function useFloatingOnElement( { element, isSelected }: Options ) {
|
|
|
36
32
|
],
|
|
37
33
|
} );
|
|
38
34
|
|
|
35
|
+
useEffect( () => {
|
|
36
|
+
// Update the reference manually because Floating UI does not recalculate
|
|
37
|
+
// the reference element when it is being used in `option.elements.reference`.
|
|
38
|
+
// @link https://github.com/floating-ui/floating-ui/blob/master/packages/react/src/hooks/useFloatingRootContext.ts
|
|
39
|
+
refs.setReference( element );
|
|
40
|
+
}, [ element, refs ] );
|
|
41
|
+
|
|
39
42
|
return {
|
|
40
43
|
isVisible: isOpen || isSelected,
|
|
41
44
|
context,
|
|
@@ -7,13 +7,9 @@ import { plainTransformer } from './transformers/shared/plain-transformer';
|
|
|
7
7
|
|
|
8
8
|
export function initSettingsTransformers() {
|
|
9
9
|
settingsTransformersRegistry
|
|
10
|
-
.register( 'string', plainTransformer )
|
|
11
|
-
.register( 'url', plainTransformer )
|
|
12
|
-
.register( 'number', plainTransformer )
|
|
13
|
-
.register( 'boolean', plainTransformer )
|
|
14
10
|
.register( 'classes', arrayTransformer )
|
|
15
11
|
.register( 'link', linkTransformer )
|
|
16
12
|
.register( 'image', imageTransformer )
|
|
17
13
|
.register( 'image-src', imageSrcTransformer )
|
|
18
|
-
.
|
|
14
|
+
.registerFallback( plainTransformer );
|
|
19
15
|
}
|
|
@@ -27,10 +27,6 @@ export function initStyleTransformers() {
|
|
|
27
27
|
( { propKey, key } ) => `${ propKey }-${ key }`
|
|
28
28
|
)
|
|
29
29
|
)
|
|
30
|
-
.register( 'color', plainTransformer )
|
|
31
|
-
.register( 'number', plainTransformer )
|
|
32
|
-
.register( 'string', plainTransformer )
|
|
33
|
-
.register( 'url', plainTransformer )
|
|
34
30
|
.register( 'box-shadow', createCombineArrayTransformer( ',' ) )
|
|
35
31
|
.register( 'background', backgroundTransformer )
|
|
36
32
|
.register( 'background-overlay', createCombineArrayTransformer( ',' ) )
|
|
@@ -41,7 +37,6 @@ export function initStyleTransformers() {
|
|
|
41
37
|
.register( 'color-stop', colorStopTransformer )
|
|
42
38
|
.register( 'background-image-position-offset', backgroundImagePositionOffsetTransformer )
|
|
43
39
|
.register( 'background-image-size-scale', backgroundImageSizeScaleTransformer )
|
|
44
|
-
.register( 'image-attachment-id', plainTransformer )
|
|
45
40
|
.register( 'image-src', imageSrcTransformer )
|
|
46
41
|
.register( 'image', imageTransformer )
|
|
47
42
|
.register(
|
|
@@ -61,5 +56,6 @@ export function initStyleTransformers() {
|
|
|
61
56
|
[ 'start-start', 'start-end', 'end-start', 'end-end' ],
|
|
62
57
|
( { key } ) => `border-${ key }-radius`
|
|
63
58
|
)
|
|
64
|
-
)
|
|
59
|
+
)
|
|
60
|
+
.registerFallback( plainTransformer );
|
|
65
61
|
}
|
|
@@ -17,7 +17,7 @@ export function initStylesRenderer() {
|
|
|
17
17
|
let abortController: AbortController | null = null;
|
|
18
18
|
|
|
19
19
|
const resolve = createPropsResolver( {
|
|
20
|
-
transformers: styleTransformersRegistry
|
|
20
|
+
transformers: styleTransformersRegistry,
|
|
21
21
|
schema: getStylesSchema(),
|
|
22
22
|
onPropResolve: enqueueUsedFonts,
|
|
23
23
|
} );
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/* eslint-disable testing-library/render-result-naming-convention */
|
|
2
|
+
import { createDomRenderer } from '../create-dom-renderer';
|
|
3
|
+
|
|
4
|
+
describe( 'createDomRenderer', () => {
|
|
5
|
+
it.each( [
|
|
6
|
+
{
|
|
7
|
+
title: 'basic string',
|
|
8
|
+
template: 'Hello {{ name }}',
|
|
9
|
+
context: { name: 'StyleShit' },
|
|
10
|
+
expected: 'Hello StyleShit',
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
title: 'allowed html tags',
|
|
14
|
+
template: `<{{ tag | e( 'html_tag' ) }}></{{ tag | e( 'html_tag' ) }}>`,
|
|
15
|
+
context: { tag: 'a' },
|
|
16
|
+
expected: '<a></a>',
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
title: 'disallowed html tags',
|
|
20
|
+
template: `<{{ tag | e( 'html_tag' ) }}></{{ tag | e( 'html_tag' ) }}>`,
|
|
21
|
+
context: { tag: 'script' },
|
|
22
|
+
expected: '<div></div>',
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
title: 'allowed url (http)',
|
|
26
|
+
template: `{{ url | e( 'full_url' ) }}`,
|
|
27
|
+
context: { url: 'http://localhost/test-page' },
|
|
28
|
+
expected: 'http://localhost/test-page',
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
title: 'allowed url (https)',
|
|
32
|
+
template: `{{ url | e( 'full_url' ) }}`,
|
|
33
|
+
context: { url: 'https://localhost/test-page' },
|
|
34
|
+
expected: 'https://localhost/test-page',
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
title: 'allowed url (tel)',
|
|
38
|
+
template: `{{ url | e( 'full_url' ) }}`,
|
|
39
|
+
context: { url: 'tel:050-1234567' },
|
|
40
|
+
expected: 'tel:050-1234567',
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
title: 'allowed url (mailto)',
|
|
44
|
+
template: `{{ url | e( 'full_url' ) }}`,
|
|
45
|
+
context: { url: 'mailto:user@example.com' },
|
|
46
|
+
expected: 'mailto:user@example.com',
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
title: 'disallowed url',
|
|
50
|
+
template: `{{ url | e( 'full_url' ) }}`,
|
|
51
|
+
context: { url: 'javascript:alert(123)' },
|
|
52
|
+
expected: '',
|
|
53
|
+
},
|
|
54
|
+
] )( 'should render template with $title', async ( { template, context, expected } ) => {
|
|
55
|
+
// Arrange.
|
|
56
|
+
const domRenderer = createDomRenderer();
|
|
57
|
+
|
|
58
|
+
domRenderer.register( 'test-template', template );
|
|
59
|
+
|
|
60
|
+
// Act.
|
|
61
|
+
const result = await domRenderer.render( 'test-template', context );
|
|
62
|
+
|
|
63
|
+
// Assert.
|
|
64
|
+
expect( result ).toBe( expected );
|
|
65
|
+
} );
|
|
66
|
+
} );
|
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
import { createMockPropType } from 'test-utils';
|
|
2
2
|
|
|
3
3
|
import { createTransformer } from '../../transformers/create-transformer';
|
|
4
|
+
import { createTransformersRegistry } from '../../transformers/create-transformers-registry';
|
|
4
5
|
import { createPropsResolver } from '../create-props-resolver';
|
|
5
6
|
|
|
6
7
|
describe( 'createPropsResolver', () => {
|
|
7
8
|
it( 'should resolve simple props', async () => {
|
|
8
9
|
// Arrange.
|
|
10
|
+
const transformers = createTransformersRegistry().register(
|
|
11
|
+
'int',
|
|
12
|
+
createTransformer( ( value: number ) => value + 1 )
|
|
13
|
+
);
|
|
14
|
+
|
|
9
15
|
const resolve = createPropsResolver( {
|
|
10
|
-
transformers
|
|
16
|
+
transformers,
|
|
11
17
|
schema: { int: createMockPropType( { kind: 'plain', key: 'int' } ) },
|
|
12
18
|
} );
|
|
13
19
|
|
|
@@ -24,8 +30,13 @@ describe( 'createPropsResolver', () => {
|
|
|
24
30
|
|
|
25
31
|
it( 'should skip disabled props', async () => {
|
|
26
32
|
// Arrange.
|
|
33
|
+
const transformers = createTransformersRegistry().register(
|
|
34
|
+
'int',
|
|
35
|
+
createTransformer( ( value: number ) => value + 1 )
|
|
36
|
+
);
|
|
37
|
+
|
|
27
38
|
const resolve = createPropsResolver( {
|
|
28
|
-
transformers
|
|
39
|
+
transformers,
|
|
29
40
|
schema: { int: createMockPropType( { kind: 'plain', key: 'int' } ) },
|
|
30
41
|
} );
|
|
31
42
|
|
|
@@ -41,13 +52,18 @@ describe( 'createPropsResolver', () => {
|
|
|
41
52
|
} );
|
|
42
53
|
|
|
43
54
|
// Assert.
|
|
44
|
-
expect( result ).toEqual( {} );
|
|
55
|
+
expect( result ).toEqual( { int: null } );
|
|
45
56
|
} );
|
|
46
57
|
|
|
47
58
|
it( 'should fallback to default value when there is no value', async () => {
|
|
48
59
|
// Arrange.
|
|
60
|
+
const transformers = createTransformersRegistry().register(
|
|
61
|
+
'int',
|
|
62
|
+
createTransformer( ( value: number ) => value + 1 )
|
|
63
|
+
);
|
|
64
|
+
|
|
49
65
|
const resolve = createPropsResolver( {
|
|
50
|
-
transformers
|
|
66
|
+
transformers,
|
|
51
67
|
schema: {
|
|
52
68
|
int: createMockPropType( {
|
|
53
69
|
kind: 'plain',
|
|
@@ -66,8 +82,13 @@ describe( 'createPropsResolver', () => {
|
|
|
66
82
|
|
|
67
83
|
it( 'should skip props that are not in the schema', async () => {
|
|
68
84
|
// Arrange.
|
|
85
|
+
const transformers = createTransformersRegistry().register(
|
|
86
|
+
'int',
|
|
87
|
+
createTransformer( ( value: number ) => value + 1 )
|
|
88
|
+
);
|
|
89
|
+
|
|
69
90
|
const resolve = createPropsResolver( {
|
|
70
|
-
transformers
|
|
91
|
+
transformers,
|
|
71
92
|
schema: {
|
|
72
93
|
int: createMockPropType( { kind: 'plain', key: 'int' } ),
|
|
73
94
|
},
|
|
@@ -93,8 +114,13 @@ describe( 'createPropsResolver', () => {
|
|
|
93
114
|
|
|
94
115
|
it( "should skip props that don't have a transformer", async () => {
|
|
95
116
|
// Arrange.
|
|
117
|
+
const transformers = createTransformersRegistry().register(
|
|
118
|
+
'int',
|
|
119
|
+
createTransformer( ( value: number ) => value + 1 )
|
|
120
|
+
);
|
|
121
|
+
|
|
96
122
|
const resolve = createPropsResolver( {
|
|
97
|
-
transformers
|
|
123
|
+
transformers,
|
|
98
124
|
schema: {
|
|
99
125
|
int: createMockPropType( { kind: 'plain', key: 'int' } ),
|
|
100
126
|
invalid: createMockPropType( { kind: 'plain', key: 'invalid' } ),
|
|
@@ -116,17 +142,94 @@ describe( 'createPropsResolver', () => {
|
|
|
116
142
|
} );
|
|
117
143
|
|
|
118
144
|
// Assert.
|
|
119
|
-
expect( result ).toEqual( { int: 2 } );
|
|
145
|
+
expect( result ).toEqual( { int: 2, invalid: null } );
|
|
120
146
|
} );
|
|
121
147
|
|
|
122
|
-
it( 'should skip props
|
|
148
|
+
it( 'should not skip props if there is a fallback transformer', async () => {
|
|
149
|
+
// Arrange.
|
|
150
|
+
const transformers = createTransformersRegistry()
|
|
151
|
+
.register(
|
|
152
|
+
'int',
|
|
153
|
+
createTransformer( ( value: number ) => value + 1 )
|
|
154
|
+
)
|
|
155
|
+
.registerFallback( createTransformer( ( value: string ) => value + ' world' ) );
|
|
156
|
+
|
|
123
157
|
const resolve = createPropsResolver( {
|
|
124
|
-
transformers
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
158
|
+
transformers,
|
|
159
|
+
schema: {
|
|
160
|
+
int: createMockPropType( { kind: 'plain', key: 'int' } ),
|
|
161
|
+
greet: createMockPropType( { kind: 'plain', key: 'string' } ),
|
|
162
|
+
},
|
|
163
|
+
} );
|
|
164
|
+
|
|
165
|
+
// Act.
|
|
166
|
+
const result = await resolve( {
|
|
167
|
+
props: {
|
|
168
|
+
int: {
|
|
169
|
+
$$type: 'int',
|
|
170
|
+
value: 1,
|
|
171
|
+
},
|
|
172
|
+
greet: {
|
|
173
|
+
$$type: 'string',
|
|
174
|
+
value: 'hello',
|
|
175
|
+
},
|
|
176
|
+
},
|
|
177
|
+
} );
|
|
178
|
+
|
|
179
|
+
// Assert.
|
|
180
|
+
expect( result ).toEqual( { int: 2, greet: 'hello world' } );
|
|
181
|
+
} );
|
|
182
|
+
|
|
183
|
+
it( 'should return null if the prop is value is not match the prop type', async () => {
|
|
184
|
+
// Arrange.
|
|
185
|
+
const transformers = createTransformersRegistry()
|
|
186
|
+
.register(
|
|
187
|
+
'int',
|
|
188
|
+
createTransformer( ( value: number ) => value + 1 )
|
|
189
|
+
)
|
|
190
|
+
.registerFallback( createTransformer( ( value: string ) => value + ' world' ) );
|
|
191
|
+
|
|
192
|
+
const resolve = createPropsResolver( {
|
|
193
|
+
transformers,
|
|
194
|
+
schema: {
|
|
195
|
+
int: createMockPropType( { kind: 'plain', key: 'int' } ),
|
|
196
|
+
greet: createMockPropType( { kind: 'plain', key: 'string' } ),
|
|
129
197
|
},
|
|
198
|
+
} );
|
|
199
|
+
|
|
200
|
+
// Act.
|
|
201
|
+
const result = await resolve( {
|
|
202
|
+
props: {
|
|
203
|
+
int: {
|
|
204
|
+
$$type: 'int',
|
|
205
|
+
value: 1,
|
|
206
|
+
},
|
|
207
|
+
greet: {
|
|
208
|
+
$$type: 'int',
|
|
209
|
+
value: 2,
|
|
210
|
+
},
|
|
211
|
+
},
|
|
212
|
+
} );
|
|
213
|
+
|
|
214
|
+
// Assert.
|
|
215
|
+
expect( result ).toEqual( { int: 2, greet: null } );
|
|
216
|
+
} );
|
|
217
|
+
|
|
218
|
+
it( 'should skip props when their transformer throws an error', async () => {
|
|
219
|
+
const transformers = createTransformersRegistry()
|
|
220
|
+
.register(
|
|
221
|
+
'int',
|
|
222
|
+
createTransformer( ( value: number ) => value + 1 )
|
|
223
|
+
)
|
|
224
|
+
.register(
|
|
225
|
+
'throws',
|
|
226
|
+
createTransformer< number >( () => {
|
|
227
|
+
throw new Error( 'Not Working!' );
|
|
228
|
+
} )
|
|
229
|
+
);
|
|
230
|
+
|
|
231
|
+
const resolve = createPropsResolver( {
|
|
232
|
+
transformers,
|
|
130
233
|
schema: {
|
|
131
234
|
int: createMockPropType( { kind: 'plain', key: 'int' } ),
|
|
132
235
|
invalid: createMockPropType( { kind: 'plain', key: 'throws' } ),
|
|
@@ -148,14 +251,19 @@ describe( 'createPropsResolver', () => {
|
|
|
148
251
|
} );
|
|
149
252
|
|
|
150
253
|
// Assert.
|
|
151
|
-
expect( result ).toEqual( { int: 2 } );
|
|
254
|
+
expect( result ).toEqual( { int: 2, invalid: null } );
|
|
152
255
|
} );
|
|
153
256
|
|
|
154
257
|
it( 'should trigger onResolve when resolving a prop', async () => {
|
|
258
|
+
const transformers = createTransformersRegistry().register(
|
|
259
|
+
'int',
|
|
260
|
+
createTransformer( ( value: number ) => value + 1 )
|
|
261
|
+
);
|
|
262
|
+
|
|
155
263
|
const onResolve = jest.fn();
|
|
156
264
|
|
|
157
265
|
const resolve = createPropsResolver( {
|
|
158
|
-
transformers
|
|
266
|
+
transformers,
|
|
159
267
|
schema: {
|
|
160
268
|
int: createMockPropType( { kind: 'plain', key: 'int' } ),
|
|
161
269
|
int2: createMockPropType( { kind: 'plain', key: 'int' } ),
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { createArrayLoader, createEnvironment, type TwingArrayLoader, type TwingEnvironment } from '@elementor/twing';
|
|
2
|
+
|
|
3
|
+
type DomRenderer = {
|
|
4
|
+
register: TwingArrayLoader[ 'setTemplate' ];
|
|
5
|
+
render: TwingEnvironment[ 'render' ];
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export function createDomRenderer(): DomRenderer {
|
|
9
|
+
const loader = createArrayLoader( {} );
|
|
10
|
+
const environment = createEnvironment( loader );
|
|
11
|
+
|
|
12
|
+
environment.registerEscapingStrategy( escapeHtmlTag, 'html_tag' );
|
|
13
|
+
environment.registerEscapingStrategy( escapeURL, 'full_url' );
|
|
14
|
+
|
|
15
|
+
return {
|
|
16
|
+
register: loader.setTemplate,
|
|
17
|
+
render: environment.render,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function escapeHtmlTag( value: string ) {
|
|
22
|
+
const allowedTags = [
|
|
23
|
+
'a',
|
|
24
|
+
'article',
|
|
25
|
+
'aside',
|
|
26
|
+
'button',
|
|
27
|
+
'div',
|
|
28
|
+
'footer',
|
|
29
|
+
'h1',
|
|
30
|
+
'h2',
|
|
31
|
+
'h3',
|
|
32
|
+
'h4',
|
|
33
|
+
'h5',
|
|
34
|
+
'h6',
|
|
35
|
+
'header',
|
|
36
|
+
'main',
|
|
37
|
+
'nav',
|
|
38
|
+
'p',
|
|
39
|
+
'section',
|
|
40
|
+
'span',
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
return allowedTags.includes( value ) ? value : 'div';
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function escapeURL( value: string ) {
|
|
47
|
+
const allowedProtocols = [ 'http:', 'https:', 'mailto:', 'tel:' ];
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
const parsed = new URL( value );
|
|
51
|
+
|
|
52
|
+
return allowedProtocols.includes( parsed.protocol ) ? value : '';
|
|
53
|
+
} catch {
|
|
54
|
+
return '';
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -7,11 +7,11 @@ import {
|
|
|
7
7
|
type PropValue,
|
|
8
8
|
} from '@elementor/editor-props';
|
|
9
9
|
|
|
10
|
-
import { type
|
|
10
|
+
import { type TransformersRegistry } from '../transformers/create-transformers-registry';
|
|
11
11
|
import { getMultiPropsValue, isMultiProps } from './multi-props';
|
|
12
12
|
|
|
13
13
|
type CreatePropResolverArgs = {
|
|
14
|
-
transformers:
|
|
14
|
+
transformers: TransformersRegistry;
|
|
15
15
|
schema: PropsSchema;
|
|
16
16
|
onPropResolve?: ( args: { key: string; value: unknown } ) => void;
|
|
17
17
|
};
|
|
@@ -44,10 +44,6 @@ export function createPropsResolver( { transformers, schema: initialSchema, onPr
|
|
|
44
44
|
|
|
45
45
|
const transformed = await transform( { value, key, type, signal } );
|
|
46
46
|
|
|
47
|
-
if ( transformed === null ) {
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
47
|
onPropResolve?.( { key, value: transformed } );
|
|
52
48
|
|
|
53
49
|
if ( isMultiProps( transformed ) ) {
|
|
@@ -86,6 +82,10 @@ export function createPropsResolver( { transformers, schema: initialSchema, onPr
|
|
|
86
82
|
}
|
|
87
83
|
}
|
|
88
84
|
|
|
85
|
+
if ( value.$$type !== type.key ) {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
89
|
// Warning: This variable is loosely-typed - use with caution.
|
|
90
90
|
let resolvedValue = value.value;
|
|
91
91
|
|
|
@@ -105,7 +105,7 @@ export function createPropsResolver( { transformers, schema: initialSchema, onPr
|
|
|
105
105
|
);
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
-
const transformer = transformers
|
|
108
|
+
const transformer = transformers.get( value.$$type );
|
|
109
109
|
|
|
110
110
|
if ( ! transformer ) {
|
|
111
111
|
return null;
|
|
@@ -97,6 +97,10 @@ async function propsToCss( { props, resolve, signal }: PropsToCssArgs ) {
|
|
|
97
97
|
|
|
98
98
|
return Object.entries( transformed )
|
|
99
99
|
.reduce< string[] >( ( acc, [ propName, propValue ] ) => {
|
|
100
|
+
if ( propValue === null ) {
|
|
101
|
+
return acc;
|
|
102
|
+
}
|
|
103
|
+
|
|
100
104
|
acc.push( propName + ':' + propValue + ';' );
|
|
101
105
|
|
|
102
106
|
return acc;
|
|
@@ -1,16 +1,27 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type PropTypeKey } from '@elementor/editor-props';
|
|
2
|
+
|
|
3
|
+
import { type AnyTransformer, type TransformersMap } from './types';
|
|
4
|
+
|
|
5
|
+
export type TransformersRegistry = ReturnType< typeof createTransformersRegistry >;
|
|
2
6
|
|
|
3
7
|
export function createTransformersRegistry() {
|
|
4
8
|
const transformers: TransformersMap = {};
|
|
5
9
|
|
|
10
|
+
let fallbackTransformer: AnyTransformer | null = null;
|
|
11
|
+
|
|
6
12
|
return {
|
|
7
|
-
register(
|
|
8
|
-
transformers[
|
|
13
|
+
register( type: PropTypeKey, transformer: AnyTransformer ) {
|
|
14
|
+
transformers[ type ] = transformer;
|
|
15
|
+
|
|
16
|
+
return this;
|
|
17
|
+
},
|
|
18
|
+
registerFallback( transformer: AnyTransformer ) {
|
|
19
|
+
fallbackTransformer = transformer;
|
|
9
20
|
|
|
10
21
|
return this;
|
|
11
22
|
},
|
|
12
|
-
|
|
13
|
-
return transformers;
|
|
23
|
+
get( type: PropTypeKey ): AnyTransformer | null {
|
|
24
|
+
return transformers[ type ] ?? fallbackTransformer;
|
|
14
25
|
},
|
|
15
26
|
};
|
|
16
27
|
}
|
|
@@ -6,5 +6,5 @@ type BackgroundImagePositionOffset = {
|
|
|
6
6
|
};
|
|
7
7
|
|
|
8
8
|
export const backgroundImagePositionOffsetTransformer = createTransformer(
|
|
9
|
-
( { x
|
|
9
|
+
( { x, y }: BackgroundImagePositionOffset ) => `${ x ?? '0px' } ${ y ?? '0px' }`
|
|
10
10
|
);
|
|
@@ -6,5 +6,5 @@ type BackgroundImageSizeScale = {
|
|
|
6
6
|
};
|
|
7
7
|
|
|
8
8
|
export const backgroundImageSizeScaleTransformer = createTransformer(
|
|
9
|
-
( { width
|
|
9
|
+
( { width, height }: BackgroundImageSizeScale ) => `${ width ?? 'auto' } ${ height ?? 'auto' }`
|
|
10
10
|
);
|
|
@@ -6,14 +6,10 @@ export type UnbrandedTransformer< TValue > = (
|
|
|
6
6
|
}
|
|
7
7
|
) => unknown;
|
|
8
8
|
|
|
9
|
-
const brand = Symbol( 'transformer-brand' );
|
|
10
|
-
|
|
11
9
|
export type Transformer< TValue > = UnbrandedTransformer< TValue > & {
|
|
12
|
-
|
|
10
|
+
__transformer: true;
|
|
13
11
|
};
|
|
14
12
|
|
|
15
|
-
export type TransformerName = string;
|
|
16
|
-
|
|
17
13
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
18
14
|
export type AnyTransformer = Transformer< any >;
|
|
19
15
|
|