@elementor/editor-canvas 0.7.1 → 0.9.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 (44) hide show
  1. package/.turbo/turbo-build.log +9 -9
  2. package/CHANGELOG.md +57 -0
  3. package/dist/index.js +241 -215
  4. package/dist/index.js.map +1 -1
  5. package/dist/index.mjs +243 -217
  6. package/dist/index.mjs.map +1 -1
  7. package/package.json +15 -12
  8. package/src/__tests__/init-styles-renderer.test.ts +19 -9
  9. package/src/init-styles-renderer.ts +20 -7
  10. package/src/{styles-renderer → renderers}/__tests__/__mocks__/styles-schema.ts +214 -16
  11. package/src/renderers/__tests__/create-props-resolver.test.ts +175 -0
  12. package/src/renderers/__tests__/create-props-resolver.transformers.test.ts +325 -0
  13. package/src/renderers/__tests__/render-styles.test.ts +126 -0
  14. package/src/renderers/create-props-resolver.ts +123 -0
  15. package/src/{styles-renderer → renderers}/multi-props.ts +1 -1
  16. package/src/{styles-renderer/render.ts → renderers/render-styles.ts} +18 -17
  17. package/src/renderers/style-transformers/background-image-overlay-transformer.ts +24 -0
  18. package/src/renderers/style-transformers/background-image-position-offset-transformer.ts +10 -0
  19. package/src/renderers/style-transformers/background-image-size-scale-transformer.ts +10 -0
  20. package/src/{styles-renderer/transformers → renderers/style-transformers}/create-corner-sizes-transformer.ts +7 -9
  21. package/src/{styles-renderer/transformers → renderers/style-transformers}/create-edge-sizes-transformer.ts +2 -2
  22. package/src/{styles-renderer/transformers → renderers/style-transformers}/image-attachment.ts +1 -1
  23. package/src/{styles-renderer/transformers → renderers/style-transformers}/image-src.ts +4 -7
  24. package/src/renderers/style-transformers/image.ts +25 -0
  25. package/src/{styles-renderer/transformers → renderers/style-transformers}/index.ts +9 -2
  26. package/src/renderers/style-transformers/stroke-transformer.ts +16 -0
  27. package/src/renderers/types.ts +12 -0
  28. package/src/styles-renderer/__tests__/enqueue-used-fonts.test.ts +0 -60
  29. package/src/styles-renderer/__tests__/index.test.ts +0 -777
  30. package/src/styles-renderer/enqueue-used-fonts.ts +0 -22
  31. package/src/styles-renderer/index.ts +0 -2
  32. package/src/styles-renderer/resolve.ts +0 -103
  33. package/src/styles-renderer/transformers/background-image-overlay-transformer.ts +0 -31
  34. package/src/styles-renderer/transformers/stroke-transformer.ts +0 -9
  35. package/src/styles-renderer/types.ts +0 -16
  36. /package/src/{styles-renderer → renderers}/errors.ts +0 -0
  37. /package/src/{styles-renderer/transformers → renderers/style-transformers}/background-color-overlay-transformer.ts +0 -0
  38. /package/src/{styles-renderer/transformers → renderers/style-transformers}/background-transformer.ts +0 -0
  39. /package/src/{styles-renderer/transformers → renderers/style-transformers}/create-combine-array-transformer.ts +0 -0
  40. /package/src/{styles-renderer/transformers → renderers/style-transformers}/dimensions.ts +0 -0
  41. /package/src/{styles-renderer/transformers → renderers/style-transformers}/layout-direction-transformer.ts +0 -0
  42. /package/src/{styles-renderer/transformers → renderers/style-transformers}/primitive-transformer.ts +0 -0
  43. /package/src/{styles-renderer/transformers → renderers/style-transformers}/shadow-transformer.ts +0 -0
  44. /package/src/{styles-renderer/transformers → renderers/style-transformers}/size-transformer.ts +0 -0
@@ -0,0 +1,123 @@
1
+ import {
2
+ isTransformable,
3
+ type PropKey,
4
+ type Props,
5
+ type PropsSchema,
6
+ type PropType,
7
+ type PropValue,
8
+ } from '@elementor/editor-props';
9
+
10
+ import { getMultiPropsValue, isMultiProps } from './multi-props';
11
+ import { type TransformersMap } from './types';
12
+
13
+ type CreatePropResolverOptions = {
14
+ onPropResolve?: ( args: { key: string; value: unknown } ) => void;
15
+ };
16
+
17
+ type ResolveArgs = {
18
+ props: Props;
19
+ schema: PropsSchema;
20
+ signal?: AbortSignal;
21
+ };
22
+
23
+ type TransformArgs = {
24
+ value: unknown;
25
+ key: PropKey;
26
+ type: PropType;
27
+ signal?: AbortSignal;
28
+ depth?: number;
29
+ };
30
+
31
+ export type PropsResolver = ReturnType< typeof createPropsResolver >;
32
+
33
+ const TRANSFORM_DEPTH_LIMIT = 3;
34
+
35
+ export function createPropsResolver(
36
+ transformers: TransformersMap,
37
+ { onPropResolve }: CreatePropResolverOptions = {}
38
+ ) {
39
+ async function transform( { value, key, type, signal, depth = 0 }: TransformArgs ) {
40
+ if ( value === null || value === undefined ) {
41
+ return null;
42
+ }
43
+
44
+ if ( ! isTransformable( value ) ) {
45
+ return value;
46
+ }
47
+
48
+ if ( depth > TRANSFORM_DEPTH_LIMIT ) {
49
+ return null;
50
+ }
51
+
52
+ if ( value.disabled === true ) {
53
+ return null;
54
+ }
55
+
56
+ if ( type.kind === 'union' ) {
57
+ type = type.prop_types[ value.$$type ];
58
+
59
+ if ( ! type ) {
60
+ return null;
61
+ }
62
+ }
63
+
64
+ // Warning: This variable is loosely-typed - use with caution.
65
+ let resolvedValue = value.value;
66
+
67
+ if ( type.kind === 'object' ) {
68
+ resolvedValue = await resolve( {
69
+ props: resolvedValue,
70
+ schema: type.shape,
71
+ signal,
72
+ } );
73
+ }
74
+
75
+ if ( type.kind === 'array' ) {
76
+ resolvedValue = await Promise.all(
77
+ resolvedValue.map( ( item: PropValue ) =>
78
+ transform( { value: item, key, type: type.item_prop_type, depth, signal } )
79
+ )
80
+ );
81
+ }
82
+
83
+ const transformer = transformers[ value.$$type ];
84
+
85
+ if ( ! transformer ) {
86
+ return null;
87
+ }
88
+
89
+ try {
90
+ const transformed = await transformer( resolvedValue, { key, signal } );
91
+
92
+ return transform( { value: transformed, key, type, signal, depth: depth + 1 } );
93
+ } catch {
94
+ return null;
95
+ }
96
+ }
97
+
98
+ async function resolve( { props, schema, signal }: ResolveArgs ) {
99
+ const promises = Promise.all(
100
+ Object.entries( schema ).map( async ( [ key, type ] ) => {
101
+ const value = props[ key ] ?? type.default;
102
+
103
+ const transformed = await transform( { value, key, type, signal } );
104
+
105
+ if ( transformed === null ) {
106
+ return;
107
+ }
108
+
109
+ onPropResolve?.( { key, value: transformed } );
110
+
111
+ if ( isMultiProps( transformed ) ) {
112
+ return getMultiPropsValue( transformed );
113
+ }
114
+
115
+ return { [ key ]: transformed };
116
+ } )
117
+ );
118
+
119
+ return Object.assign( {}, ...( await promises ).filter( Boolean ) );
120
+ }
121
+
122
+ return resolve;
123
+ }
@@ -1,6 +1,6 @@
1
1
  import { type Props, type PropValue } from '@elementor/editor-props';
2
2
 
3
- export type MultiProps = {
3
+ type MultiProps = {
4
4
  '$$multi-props': true;
5
5
  value: Props;
6
6
  };
@@ -1,38 +1,41 @@
1
- import { type Props } from '@elementor/editor-props';
1
+ import { type Props, type PropsSchema } from '@elementor/editor-props';
2
2
  import { type Breakpoint, type BreakpointsMap } from '@elementor/editor-responsive';
3
- import {
4
- getStylesSchema,
5
- type StyleDefinition,
6
- type StyleDefinitionState,
7
- type StyleDefinitionType,
8
- } from '@elementor/editor-styles';
3
+ import { type StyleDefinition, type StyleDefinitionState, type StyleDefinitionType } from '@elementor/editor-styles';
9
4
 
5
+ import { type PropsResolver } from './create-props-resolver';
10
6
  import { UnknownStyleTypeError } from './errors';
11
- import { resolve } from './resolve';
12
- import { type TransformersMap } from './types';
13
7
 
14
8
  type RenderParams = {
15
- transformers: TransformersMap;
9
+ resolve: PropsResolver;
16
10
  styles: StyleDefinition[];
11
+ schema: PropsSchema;
17
12
  breakpoints: BreakpointsMap;
18
13
  selectorPrefix?: string;
19
14
  signal?: AbortSignal;
20
15
  };
21
16
 
17
+ type PropsToCssArgs = {
18
+ props: Props;
19
+ schema: PropsSchema;
20
+ resolve: PropsResolver;
21
+ signal?: AbortSignal;
22
+ };
23
+
22
24
  const SELECTORS_MAP: Record< StyleDefinitionType, string > = {
23
25
  class: '.',
24
26
  };
25
27
 
26
- export default async function render( {
27
- transformers,
28
+ export default async function renderStyles( {
29
+ resolve,
28
30
  styles,
31
+ schema,
29
32
  breakpoints,
30
33
  selectorPrefix = '',
31
34
  signal,
32
35
  }: RenderParams ) {
33
36
  const stylesCssPromises = styles.map( async ( style ) => {
34
37
  const variantCssPromises = Object.values( style.variants ).map( async ( variant ) => {
35
- const css = await propsToCss( variant.props, transformers, signal );
38
+ const css = await propsToCss( { props: variant.props, schema, resolve, signal } );
36
39
 
37
40
  return createStyleWrapper()
38
41
  .forStyle( style )
@@ -92,10 +95,8 @@ function createStyleWrapper( value: string = '', wrapper?: ( css: string ) => st
92
95
  };
93
96
  }
94
97
 
95
- async function propsToCss( props: Props, transformers: TransformersMap, signal?: AbortSignal ) {
96
- const schema = getStylesSchema();
97
-
98
- const transformed = await resolve( { props, schema, transformers, signal } );
98
+ async function propsToCss( { props, resolve, signal, schema }: PropsToCssArgs ) {
99
+ const transformed = await resolve( { props, schema, signal } );
99
100
 
100
101
  return Object.entries( transformed )
101
102
  .reduce< string[] >( ( acc, [ propName, propValue ] ) => {
@@ -0,0 +1,24 @@
1
+ import { type BackgroundImageOverlayPropValue } from '@elementor/editor-props';
2
+
3
+ import { type Transformer } from '../types';
4
+
5
+ const DEFAULT_POSITION_VALUE = '0% 0%';
6
+
7
+ const backgroundImageOverlayTransformer: Transformer< BackgroundImageOverlayPropValue[ 'value' ] > = ( value ) => {
8
+ const { image: imageSrc, size = null, position = null, repeat = null, attachment = null } = value;
9
+
10
+ if ( ! imageSrc ) {
11
+ return null;
12
+ }
13
+
14
+ const backgroundStyles = [
15
+ imageSrc,
16
+ repeat,
17
+ attachment,
18
+ size ? `${ position || DEFAULT_POSITION_VALUE } / ${ size }` : position,
19
+ ].filter( Boolean );
20
+
21
+ return backgroundStyles.join( ' ' );
22
+ };
23
+
24
+ export default backgroundImageOverlayTransformer;
@@ -0,0 +1,10 @@
1
+ import type { BackgroundImagePositionOffsetPropValue } from '@elementor/editor-props';
2
+
3
+ import { type Transformer } from '../types';
4
+
5
+ const backgroundImagePositionOffsetTransformer: Transformer< BackgroundImagePositionOffsetPropValue[ 'value' ] > = ( {
6
+ x = '0px',
7
+ y = '0px',
8
+ } ) => `${ x } ${ y }`;
9
+
10
+ export default backgroundImagePositionOffsetTransformer;
@@ -0,0 +1,10 @@
1
+ import type { BackgroundImageSizeScalePropValue } from '@elementor/editor-props';
2
+
3
+ import { type Transformer } from '../types';
4
+
5
+ const backgroundImageSizeScaleTransformer: Transformer< BackgroundImageSizeScalePropValue[ 'value' ] > = ( {
6
+ width = 'auto',
7
+ height = 'auto',
8
+ } ) => `${ width } ${ height }`;
9
+
10
+ export default backgroundImageSizeScaleTransformer;
@@ -3,18 +3,16 @@ import { type Props, type PropValue } from '@elementor/editor-props';
3
3
  import { createMultiPropsValue } from '../multi-props';
4
4
  import { type Transformer } from '../types';
5
5
 
6
- export type CornerSizes = {
7
- top?: PropValue;
8
- right?: PropValue;
9
- bottom?: PropValue;
10
- left?: PropValue;
6
+ type CornerSizes = {
7
+ 'start-start'?: PropValue;
8
+ 'start-end'?: PropValue;
9
+ 'end-start'?: PropValue;
10
+ 'end-end'?: PropValue;
11
11
  };
12
12
 
13
- export type CreateCornerSizesTransformer = (
14
- keyGenerator: ( cornerKey: string ) => string
15
- ) => Transformer< CornerSizes >;
13
+ type CreateCornerSizesTransformer = ( keyGenerator: ( cornerKey: string ) => string ) => Transformer< CornerSizes >;
16
14
 
17
- const validCorners = [ 'top-left', 'top-right', 'bottom-left', 'bottom-right' ];
15
+ const validCorners = [ 'start-start', 'start-end', 'end-start', 'end-end' ];
18
16
 
19
17
  const createCornerSizesTransformer: CreateCornerSizesTransformer = ( keyGenerator ) => ( value ) => {
20
18
  const props = Object.entries( value ).reduce< Props >( ( acc, [ corner, cornerValue ] ) => {
@@ -3,14 +3,14 @@ import { type Props, type PropValue } from '@elementor/editor-props';
3
3
  import { createMultiPropsValue } from '../multi-props';
4
4
  import { type Transformer } from '../types';
5
5
 
6
- export type EdgeSizes = {
6
+ type EdgeSizes = {
7
7
  top?: PropValue;
8
8
  right?: PropValue;
9
9
  bottom?: PropValue;
10
10
  left?: PropValue;
11
11
  };
12
12
 
13
- export type CreateEdgeSizesTransformer = ( keyGenerator: ( edgeKey: string ) => string ) => Transformer< EdgeSizes >;
13
+ type CreateEdgeSizesTransformer = ( keyGenerator: ( edgeKey: string ) => string ) => Transformer< EdgeSizes >;
14
14
 
15
15
  const validEdges = [ 'top', 'right', 'bottom', 'left' ];
16
16
 
@@ -10,5 +10,5 @@ export const imageAttachmentTransformer: Transformer< ImageSrcPropValue[ 'value'
10
10
  return null;
11
11
  }
12
12
 
13
- return attachment.url;
13
+ return attachment;
14
14
  };
@@ -2,10 +2,7 @@ import { type ImageSrcPropValue } from '@elementor/editor-props';
2
2
 
3
3
  import { type Transformer } from '../types';
4
4
 
5
- export const imageSrcTransformer: Transformer< ImageSrcPropValue[ 'value' ] > = ( value ) => {
6
- const url = value.id ?? value.url?.value;
7
-
8
- if ( url ) {
9
- return `url(${ url })`;
10
- }
11
- };
5
+ export const imageSrcTransformer: Transformer< ImageSrcPropValue[ 'value' ] > = ( value ) => ( {
6
+ attachment: value.id,
7
+ url: value.url,
8
+ } );
@@ -0,0 +1,25 @@
1
+ import { type ImagePropValue } from '@elementor/editor-props';
2
+
3
+ import { type Transformer } from '../types';
4
+
5
+ export const imageTransformer: Transformer< ImagePropValue[ 'value' ] > = ( value ) => {
6
+ const { src, size } = value;
7
+
8
+ if ( src.url ) {
9
+ return `url(${ src.url })`;
10
+ }
11
+
12
+ const sizeUrl = src.attachment?.sizes[ size ]?.url;
13
+
14
+ if ( sizeUrl ) {
15
+ return `url(${ sizeUrl })`;
16
+ }
17
+
18
+ const attachmentUrl = src.attachment?.url;
19
+
20
+ if ( attachmentUrl ) {
21
+ return `url(${ attachmentUrl })`;
22
+ }
23
+
24
+ return null;
25
+ };
@@ -1,11 +1,14 @@
1
1
  import { type TransformersMap } from '../types';
2
2
  import backgroundColorOverlayTransformer from './background-color-overlay-transformer';
3
3
  import backgroundImageOverlayTransformer from './background-image-overlay-transformer';
4
+ import backgroundImagePositionOffsetTransformer from './background-image-position-offset-transformer';
5
+ import backgroundImageSizeScaleTransformer from './background-image-size-scale-transformer';
4
6
  import { default as background } from './background-transformer';
5
7
  import { default as createCombineArrayTransformer } from './create-combine-array-transformer';
6
8
  import createCornerSizesTransformer from './create-corner-sizes-transformer';
7
9
  import createEdgeSizesTransformer from './create-edge-sizes-transformer';
8
10
  import { default as dimensions } from './dimensions';
11
+ import { imageTransformer } from './image';
9
12
  import { imageAttachmentTransformer } from './image-attachment';
10
13
  import { imageSrcTransformer } from './image-src';
11
14
  import { default as layoutDirection } from './layout-direction-transformer';
@@ -14,7 +17,7 @@ import { default as shadow } from './shadow-transformer';
14
17
  import { default as size } from './size-transformer';
15
18
  import { default as stroke } from './stroke-transformer';
16
19
 
17
- export default {
20
+ export const styleTransformers: TransformersMap = {
18
21
  size,
19
22
  shadow,
20
23
  stroke,
@@ -23,10 +26,13 @@ export default {
23
26
  color: primitiveTransformer,
24
27
  number: primitiveTransformer,
25
28
  string: primitiveTransformer,
29
+ url: primitiveTransformer,
26
30
 
27
31
  dimensions,
28
32
  'background-color-overlay': backgroundColorOverlayTransformer,
29
33
  'background-image-overlay': backgroundImageOverlayTransformer,
34
+ 'background-image-position-offset': backgroundImagePositionOffsetTransformer,
35
+ 'background-image-size-scale': backgroundImageSizeScaleTransformer,
30
36
  'background-overlay': createCombineArrayTransformer( ',' ),
31
37
 
32
38
  'box-shadow': createCombineArrayTransformer( ',' ),
@@ -36,6 +42,7 @@ export default {
36
42
 
37
43
  'image-attachment-id': imageAttachmentTransformer,
38
44
  'image-src': imageSrcTransformer,
45
+ image: imageTransformer,
39
46
 
40
47
  'layout-direction': layoutDirection,
41
- } satisfies TransformersMap;
48
+ };
@@ -0,0 +1,16 @@
1
+ import { type StrokePropValue } from '@elementor/editor-props';
2
+
3
+ import { createMultiPropsValue } from '../multi-props';
4
+ import { type Transformer } from '../types';
5
+
6
+ const strokeTransformer: Transformer< StrokePropValue[ 'value' ] > = ( value ) => {
7
+ const parsed = {
8
+ '-webkit-text-stroke': `${ value.width } ${ value.color }`,
9
+ stroke: `${ value.color }`,
10
+ 'stroke-width': `${ value.width }`,
11
+ };
12
+
13
+ return createMultiPropsValue( parsed );
14
+ };
15
+
16
+ export default strokeTransformer;
@@ -0,0 +1,12 @@
1
+ import type { PropValue } from '@elementor/editor-props';
2
+
3
+ export type Transformer< TValue extends PropValue > = (
4
+ value: TValue,
5
+ options: {
6
+ key: string;
7
+ signal?: AbortSignal;
8
+ }
9
+ ) => unknown;
10
+
11
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
12
+ export type TransformersMap = Record< string, Transformer< any > >;
@@ -1,60 +0,0 @@
1
- import { type StyleDefinition } from '@elementor/editor-styles';
2
-
3
- import { enqueueFont } from '../../sync/enqueue-font';
4
- import enqueueUsedFonts from '../enqueue-used-fonts';
5
-
6
- jest.mock( '../../sync/enqueue-font' );
7
-
8
- describe( 'enqueueUsedFonts', () => {
9
- it( 'should run enqueueFont for each font family found in the style defs', () => {
10
- // Arrange.
11
- jest.mocked( enqueueFont ).mockImplementation( () => {} );
12
-
13
- const styles = [
14
- {
15
- id: '1',
16
- type: 'class',
17
- variants: [
18
- {
19
- meta: {
20
- breakpoint: null,
21
- state: null,
22
- },
23
- props: {
24
- 'font-family': {
25
- $$type: 'string',
26
- value: 'Open Sans',
27
- },
28
- },
29
- },
30
- ],
31
- },
32
- {
33
- id: '2',
34
- type: 'class',
35
- variants: [
36
- {
37
- meta: {
38
- breakpoint: null,
39
- state: 'hover',
40
- },
41
- props: {
42
- 'font-family': {
43
- $$type: 'string',
44
- value: 'Roboto',
45
- },
46
- },
47
- },
48
- ],
49
- },
50
- ] as unknown as StyleDefinition[];
51
-
52
- // Act.
53
- enqueueUsedFonts( styles );
54
-
55
- // Assert.
56
- expect( enqueueFont ).toHaveBeenCalledTimes( 2 );
57
- expect( enqueueFont ).toHaveBeenCalledWith( 'Open Sans' );
58
- expect( enqueueFont ).toHaveBeenCalledWith( 'Roboto' );
59
- } );
60
- } );