@elementor/editor-canvas 0.13.1 → 0.15.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.
Files changed (34) hide show
  1. package/.turbo/turbo-build.log +10 -10
  2. package/CHANGELOG.md +33 -0
  3. package/dist/index.d.mts +9 -8
  4. package/dist/index.d.ts +9 -8
  5. package/dist/index.js +184 -31
  6. package/dist/index.js.map +1 -1
  7. package/dist/index.mjs +192 -34
  8. package/dist/index.mjs.map +1 -1
  9. package/package.json +9 -8
  10. package/src/__tests__/__mocks__/styles-schema.ts +3 -3
  11. package/src/__tests__/settings-props-resolver.test.ts +1 -1
  12. package/src/__tests__/styles-prop-resolver.test.ts +7 -7
  13. package/src/components/__tests__/elements-overlays.test.tsx +40 -35
  14. package/src/components/element-overlay.tsx +3 -2
  15. package/src/components/elements-overlays.tsx +26 -9
  16. package/src/hooks/use-floating-on-element.ts +9 -6
  17. package/src/init-settings-transformers.ts +1 -5
  18. package/src/init-style-transformers.ts +2 -6
  19. package/src/init-styles-renderer.ts +1 -1
  20. package/src/legacy/__tests__/signalized-process.test.ts +80 -0
  21. package/src/legacy/create-element-type.ts +2 -2
  22. package/src/legacy/create-templated-element-type.ts +131 -0
  23. package/src/legacy/init-legacy-views.ts +7 -1
  24. package/src/legacy/signalized-process.ts +35 -0
  25. package/src/legacy/types.ts +27 -3
  26. package/src/renderers/__tests__/create-dom-renderer.test.ts +66 -0
  27. package/src/renderers/__tests__/create-props-resolver.test.ts +123 -15
  28. package/src/renderers/create-dom-renderer.ts +56 -0
  29. package/src/renderers/create-props-resolver.ts +10 -8
  30. package/src/renderers/render-styles.ts +4 -0
  31. package/src/transformers/create-transformers-registry.ts +16 -5
  32. package/src/transformers/styles/background-image-position-offset-transformer.ts +1 -1
  33. package/src/transformers/styles/background-image-size-scale-transformer.ts +1 -1
  34. package/src/transformers/types.ts +1 -5
@@ -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: { int: createTransformer( ( value: number ) => value + 1 ) },
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: { int: createTransformer( ( value: number ) => value + 1 ) },
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: { int: createTransformer( ( value: number ) => value + 1 ) },
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: { int: createTransformer( ( value: number ) => value + 1 ) },
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: { int: createTransformer( ( value: number ) => value + 1 ) },
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 when their transformer throws an error', async () => {
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
- int: createTransformer( ( value: number ) => value + 1 ),
126
- throws: createTransformer< number >( () => {
127
- throw new Error( 'Not Working!' );
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: { int: createTransformer( ( value: number ) => value + 1 ) },
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
+ export 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 TransformersMap } from '../transformers/types';
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: TransformersMap;
14
+ transformers: TransformersRegistry;
15
15
  schema: PropsSchema;
16
16
  onPropResolve?: ( args: { key: string; value: unknown } ) => void;
17
17
  };
@@ -30,12 +30,14 @@ type TransformArgs = {
30
30
  depth?: number;
31
31
  };
32
32
 
33
+ type ResolvedProps = Record< string, unknown >;
34
+
33
35
  export type PropsResolver = ReturnType< typeof createPropsResolver >;
34
36
 
35
37
  const TRANSFORM_DEPTH_LIMIT = 3;
36
38
 
37
39
  export function createPropsResolver( { transformers, schema: initialSchema, onPropResolve }: CreatePropResolverArgs ) {
38
- async function resolve( { props, schema, signal }: ResolveArgs ) {
40
+ async function resolve( { props, schema, signal }: ResolveArgs ): Promise< ResolvedProps > {
39
41
  schema = schema ?? initialSchema;
40
42
 
41
43
  const promises = Promise.all(
@@ -44,10 +46,6 @@ export function createPropsResolver( { transformers, schema: initialSchema, onPr
44
46
 
45
47
  const transformed = await transform( { value, key, type, signal } );
46
48
 
47
- if ( transformed === null ) {
48
- return;
49
- }
50
-
51
49
  onPropResolve?.( { key, value: transformed } );
52
50
 
53
51
  if ( isMultiProps( transformed ) ) {
@@ -86,6 +84,10 @@ export function createPropsResolver( { transformers, schema: initialSchema, onPr
86
84
  }
87
85
  }
88
86
 
87
+ if ( value.$$type !== type.key ) {
88
+ return null;
89
+ }
90
+
89
91
  // Warning: This variable is loosely-typed - use with caution.
90
92
  let resolvedValue = value.value;
91
93
 
@@ -105,7 +107,7 @@ export function createPropsResolver( { transformers, schema: initialSchema, onPr
105
107
  );
106
108
  }
107
109
 
108
- const transformer = transformers[ value.$$type ];
110
+ const transformer = transformers.get( value.$$type );
109
111
 
110
112
  if ( ! transformer ) {
111
113
  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 AnyTransformer, type TransformerName, type TransformersMap } from './types';
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( name: TransformerName, transformer: AnyTransformer ) {
8
- transformers[ name ] = transformer;
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
- all() {
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 = '0px', y = '0px' }: BackgroundImagePositionOffset ) => `${ x } ${ y }`
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 = 'auto', height = 'auto' }: BackgroundImageSizeScale ) => `${ width } ${ height }`
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
- [ brand ]: true;
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