@elementor/editor-canvas 0.12.0 → 0.13.1

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 (42) hide show
  1. package/.turbo/turbo-build.log +10 -10
  2. package/CHANGELOG.md +40 -0
  3. package/dist/index.d.mts +6 -1
  4. package/dist/index.d.ts +6 -1
  5. package/dist/index.js +393 -45
  6. package/dist/index.js.map +1 -1
  7. package/dist/index.mjs +406 -46
  8. package/dist/index.mjs.map +1 -1
  9. package/package.json +11 -10
  10. package/src/__tests__/__mocks__/styles-schema.ts +10 -1
  11. package/src/__tests__/init-styles-renderer.test.ts +19 -5
  12. package/src/__tests__/mock-attachment-data.ts +15 -0
  13. package/src/__tests__/prop-types.ts +65 -0
  14. package/src/__tests__/settings-props-resolver.test.ts +193 -0
  15. package/src/__tests__/styles-prop-resolver.test.ts +8 -16
  16. package/src/index.ts +1 -0
  17. package/src/init-settings-transformers.ts +19 -0
  18. package/src/init-style-transformers.ts +8 -9
  19. package/src/init-styles-renderer.ts +9 -3
  20. package/src/init.tsx +8 -0
  21. package/src/legacy/create-element-type.ts +84 -0
  22. package/src/legacy/init-legacy-views.ts +22 -0
  23. package/src/legacy/types.ts +60 -0
  24. package/src/settings-transformers-registry.ts +3 -0
  25. package/src/style-commands/__tests__/paste-style.test.ts +554 -0
  26. package/src/style-commands/__tests__/reset-style.test.ts +153 -0
  27. package/src/style-commands/init-style-commands.ts +7 -0
  28. package/src/style-commands/paste-style.ts +53 -0
  29. package/src/style-commands/reset-style.ts +34 -0
  30. package/src/style-commands/undoable-actions/paste-element-style.ts +107 -0
  31. package/src/style-commands/undoable-actions/reset-element-style.ts +60 -0
  32. package/src/style-commands/utils.ts +62 -0
  33. package/src/transformers/settings/array-transformer.ts +5 -0
  34. package/src/transformers/settings/link-transformer.ts +14 -0
  35. package/src/transformers/{styles → shared}/image-src-transformer.ts +2 -2
  36. package/src/transformers/shared/image-transformer.ts +41 -0
  37. package/src/transformers/shared/plain-transformer.ts +5 -0
  38. package/src/transformers/styles/background-color-overlay-transformer.ts +13 -3
  39. package/src/transformers/styles/background-image-overlay-transformer.ts +6 -2
  40. package/src/transformers/styles/image-attachment-transformer.ts +0 -15
  41. package/src/transformers/styles/image-transformer.ts +0 -34
  42. package/src/transformers/styles/primitive-transformer.ts +0 -3
@@ -0,0 +1,53 @@
1
+ import { type V1Element } from '@elementor/editor-elements';
2
+ import {
3
+ __privateListenTo as listenTo,
4
+ blockCommand,
5
+ type CommandEvent,
6
+ commandStartEvent,
7
+ } from '@elementor/editor-v1-adapters';
8
+
9
+ import { undoablePasteElementStyle } from './undoable-actions/paste-element-style';
10
+ import { type ContainerArgs, getClipboardElements, hasAtomicWidgets, isAtomicWidget } from './utils';
11
+
12
+ type PasteStylesCommandArgs = ContainerArgs & {
13
+ storageKey?: string;
14
+ };
15
+
16
+ export function initPasteStyleCommand() {
17
+ const pasteElementStyleCommand = undoablePasteElementStyle();
18
+
19
+ blockCommand( {
20
+ command: 'document/elements/paste-style',
21
+ condition: hasAtomicWidgets,
22
+ } );
23
+
24
+ listenTo( commandStartEvent( 'document/elements/paste-style' ), ( e ) =>
25
+ pasteStyles( ( e as CommandEvent ).args, pasteElementStyleCommand )
26
+ );
27
+ }
28
+
29
+ function pasteStyles( args: PasteStylesCommandArgs, pasteCallback: ReturnType< typeof undoablePasteElementStyle > ) {
30
+ const { containers = [ args.container ], storageKey } = args;
31
+
32
+ const clipboardElements = getClipboardElements( storageKey );
33
+ const [ clipboardElement ] = clipboardElements ?? [];
34
+
35
+ if ( ! clipboardElement ) {
36
+ return;
37
+ }
38
+
39
+ const elementStyles = clipboardElement.styles;
40
+ const elementStyle = Object.values( elementStyles ?? {} )[ 0 ]; // we currently support only one local style
41
+
42
+ if ( ! elementStyle ) {
43
+ return;
44
+ }
45
+
46
+ const atomicContainers = containers.filter( isAtomicWidget ) as V1Element[];
47
+
48
+ if ( ! atomicContainers.length ) {
49
+ return;
50
+ }
51
+
52
+ pasteCallback( { containers: atomicContainers, newStyle: elementStyle } );
53
+ }
@@ -0,0 +1,34 @@
1
+ import { type V1Element } from '@elementor/editor-elements';
2
+ import {
3
+ __privateListenTo as listenTo,
4
+ blockCommand,
5
+ type CommandEvent,
6
+ commandStartEvent,
7
+ } from '@elementor/editor-v1-adapters';
8
+
9
+ import { undoableResetElementStyle } from './undoable-actions/reset-element-style';
10
+ import { type ContainerArgs, hasAtomicWidgets, isAtomicWidget } from './utils';
11
+
12
+ export function initResetStyleCommand() {
13
+ const resetElementStyles = undoableResetElementStyle();
14
+
15
+ blockCommand( {
16
+ command: 'document/elements/reset-style',
17
+ condition: hasAtomicWidgets,
18
+ } );
19
+
20
+ listenTo( commandStartEvent( 'document/elements/reset-style' ), ( e ) =>
21
+ resetStyles( ( e as CommandEvent ).args, resetElementStyles )
22
+ );
23
+ }
24
+
25
+ function resetStyles( args: ContainerArgs, resetElementStyles: ReturnType< typeof undoableResetElementStyle > ) {
26
+ const { containers = [ args.container ] } = args;
27
+ const atomicContainers = containers.filter( isAtomicWidget ) as V1Element[];
28
+
29
+ if ( ! atomicContainers.length ) {
30
+ return;
31
+ }
32
+
33
+ resetElementStyles( { containers: atomicContainers } );
34
+ }
@@ -0,0 +1,107 @@
1
+ import {
2
+ createElementStyle,
3
+ deleteElementStyle,
4
+ getElementStyles,
5
+ updateElementStyle,
6
+ type V1Element,
7
+ } from '@elementor/editor-elements';
8
+ import { type StyleDefinition } from '@elementor/editor-styles';
9
+ import { LOCAL_STYLES_RESERVED_LABEL } from '@elementor/editor-styles-repository';
10
+ import { undoable } from '@elementor/editor-v1-adapters';
11
+ import { __ } from '@wordpress/i18n';
12
+
13
+ import { getClassesProp, getTitleForContainers } from '../utils';
14
+
15
+ type PasteElementStyleArgs = {
16
+ containers: V1Element[];
17
+ newStyle: StyleDefinition;
18
+ };
19
+
20
+ export const undoablePasteElementStyle = () =>
21
+ undoable(
22
+ {
23
+ do: ( { containers, newStyle }: PasteElementStyleArgs ) => {
24
+ return containers.map( ( container ) => {
25
+ const elementId = container.id;
26
+ const classesProp = getClassesProp( container );
27
+
28
+ if ( ! classesProp ) {
29
+ return null;
30
+ }
31
+
32
+ const originalStyles = getElementStyles( container.id );
33
+
34
+ const [ styleId, styleDef ] = Object.entries( originalStyles ?? {} )[ 0 ] ?? []; // we currently support only one local style
35
+ const originalStyle = Object.keys( styleDef ?? {} ).length ? styleDef : null;
36
+
37
+ const revertData = {
38
+ styleId,
39
+ originalStyle,
40
+ };
41
+
42
+ if ( styleId ) {
43
+ newStyle.variants.forEach( ( { meta, props } ) => {
44
+ updateElementStyle( {
45
+ elementId,
46
+ styleId,
47
+ meta,
48
+ props,
49
+ } );
50
+ } );
51
+ } else {
52
+ const [ firstVariant ] = newStyle.variants;
53
+ const additionalVariants = newStyle.variants.slice( 1 );
54
+
55
+ revertData.styleId = createElementStyle( {
56
+ elementId,
57
+ classesProp,
58
+ label: LOCAL_STYLES_RESERVED_LABEL,
59
+ ...firstVariant,
60
+ additionalVariants,
61
+ } );
62
+ }
63
+
64
+ return revertData;
65
+ } );
66
+ },
67
+
68
+ undo: ( { containers }, revertDataItems ) => {
69
+ containers.forEach( ( container, index ) => {
70
+ const revertData = revertDataItems[ index ];
71
+
72
+ if ( ! revertData ) {
73
+ return;
74
+ }
75
+
76
+ if ( ! revertData.originalStyle ) {
77
+ // the container didn't have a style before pasting the new style
78
+ deleteElementStyle( container.id, revertData.styleId );
79
+
80
+ return;
81
+ }
82
+
83
+ const classesProp = getClassesProp( container );
84
+
85
+ if ( ! classesProp ) {
86
+ return;
87
+ }
88
+
89
+ const [ firstVariant ] = revertData.originalStyle.variants;
90
+ const additionalVariants = revertData.originalStyle.variants.slice( 1 );
91
+
92
+ createElementStyle( {
93
+ elementId: container.id,
94
+ classesProp,
95
+ label: LOCAL_STYLES_RESERVED_LABEL,
96
+ styleId: revertData.styleId,
97
+ ...firstVariant,
98
+ additionalVariants,
99
+ } );
100
+ } );
101
+ },
102
+ },
103
+ {
104
+ title: ( { containers } ) => getTitleForContainers( containers ),
105
+ subtitle: __( 'Style Pasted', 'elementor' ),
106
+ }
107
+ );
@@ -0,0 +1,60 @@
1
+ import { createElementStyle, deleteElementStyle, getElementStyles, type V1Element } from '@elementor/editor-elements';
2
+ import { LOCAL_STYLES_RESERVED_LABEL } from '@elementor/editor-styles-repository';
3
+ import { undoable } from '@elementor/editor-v1-adapters';
4
+ import { __ } from '@wordpress/i18n';
5
+
6
+ import { getClassesProp, getTitleForContainers } from '../utils';
7
+
8
+ type ResetElementStyleArgs = {
9
+ containers: V1Element[];
10
+ };
11
+
12
+ export const undoableResetElementStyle = () =>
13
+ undoable(
14
+ {
15
+ do: ( { containers }: ResetElementStyleArgs ) => {
16
+ return containers.map( ( container ) => {
17
+ const elementId = container.model.get( 'id' );
18
+
19
+ const containerStyles = getElementStyles( elementId );
20
+
21
+ Object.keys( containerStyles ?? {} ).forEach( ( styleId ) =>
22
+ deleteElementStyle( elementId, styleId )
23
+ );
24
+
25
+ return containerStyles;
26
+ } );
27
+ },
28
+
29
+ undo: ( { containers }, revertDataItems ) => {
30
+ containers.forEach( ( container, index ) => {
31
+ const classesProp = getClassesProp( container );
32
+
33
+ if ( ! classesProp ) {
34
+ return;
35
+ }
36
+
37
+ const elementId = container.model.get( 'id' );
38
+ const containerStyles = revertDataItems[ index ];
39
+
40
+ Object.entries( containerStyles ?? {} ).forEach( ( [ styleId, style ] ) => {
41
+ const [ firstVariant ] = style.variants;
42
+ const additionalVariants = style.variants.slice( 1 );
43
+
44
+ createElementStyle( {
45
+ elementId,
46
+ classesProp,
47
+ styleId,
48
+ label: LOCAL_STYLES_RESERVED_LABEL,
49
+ ...firstVariant,
50
+ additionalVariants,
51
+ } );
52
+ } );
53
+ } );
54
+ },
55
+ },
56
+ {
57
+ title: ( { containers } ) => getTitleForContainers( containers ),
58
+ subtitle: __( 'Style Reset', 'elementor' ),
59
+ }
60
+ );
@@ -0,0 +1,62 @@
1
+ import { getElementLabel, getWidgetsCache, type V1Element, type V1ElementModelProps } from '@elementor/editor-elements';
2
+ import { CLASSES_PROP_KEY, type PropsSchema } from '@elementor/editor-props';
3
+ import { __ } from '@wordpress/i18n';
4
+
5
+ export type ContainerArgs = {
6
+ container?: V1Element;
7
+ containers?: V1Element[];
8
+ };
9
+
10
+ export function hasAtomicWidgets( args: ContainerArgs ): boolean {
11
+ const { containers = [ args.container ] } = args;
12
+
13
+ return containers.some( isAtomicWidget );
14
+ }
15
+
16
+ export function isAtomicWidget( container: V1Element | undefined ): boolean {
17
+ if ( ! container ) {
18
+ return false;
19
+ }
20
+
21
+ return Boolean( getContainerSchema( container ) );
22
+ }
23
+
24
+ export function getClassesProp( container: V1Element ): string | null {
25
+ const propsSchema = getContainerSchema( container );
26
+
27
+ if ( ! propsSchema ) {
28
+ return null;
29
+ }
30
+
31
+ const [ propKey ] =
32
+ Object.entries( propsSchema ).find(
33
+ ( [ , propType ] ) => propType.kind === 'plain' && propType.key === CLASSES_PROP_KEY
34
+ ) ?? [];
35
+
36
+ return propKey ?? null;
37
+ }
38
+
39
+ function getContainerSchema( container: V1Element ): PropsSchema | null {
40
+ const type = container?.model.get( 'widgetType' ) || container?.model.get( 'elType' );
41
+
42
+ const widgetsCache = getWidgetsCache();
43
+ const elementType = widgetsCache?.[ type ];
44
+
45
+ return elementType?.atomic_props_schema ?? null;
46
+ }
47
+
48
+ type ClipboardElements = V1ElementModelProps[];
49
+
50
+ export function getClipboardElements( storageKey: string = 'clipboard' ): ClipboardElements | undefined {
51
+ try {
52
+ const storedData = JSON.parse( localStorage.getItem( 'elementor' ) ?? '{}' );
53
+
54
+ return storedData[ storageKey ]?.elements as ClipboardElements;
55
+ } catch {
56
+ return undefined;
57
+ }
58
+ }
59
+
60
+ export function getTitleForContainers( containers: V1Element[] ): string {
61
+ return containers.length > 1 ? __( 'Elements', 'elementor' ) : getElementLabel( containers[ 0 ].id );
62
+ }
@@ -0,0 +1,5 @@
1
+ import { createTransformer } from '../create-transformer';
2
+
3
+ export const arrayTransformer = createTransformer( ( value: unknown[] ) => {
4
+ return value.filter( Boolean );
5
+ } );
@@ -0,0 +1,14 @@
1
+ import { createTransformer } from '../create-transformer';
2
+
3
+ type Link = {
4
+ destination: string | number;
5
+ isTargetBlank: boolean;
6
+ };
7
+
8
+ export const linkTransformer = createTransformer( ( { destination, isTargetBlank }: Link ) => {
9
+ return {
10
+ // The real post URL is not relevant in the Editor.
11
+ href: typeof destination === 'number' ? '#post-id-' + destination : destination,
12
+ target: isTargetBlank ? '_blank' : '_self',
13
+ };
14
+ } );
@@ -6,6 +6,6 @@ type ImageSrc = {
6
6
  };
7
7
 
8
8
  export const imageSrcTransformer = createTransformer( ( value: ImageSrc ) => ( {
9
- attachment: value.id,
10
- url: value.url,
9
+ id: value.id ?? null,
10
+ url: value.url ?? null,
11
11
  } ) );
@@ -0,0 +1,41 @@
1
+ import { getMediaAttachment } from '@elementor/wp-media';
2
+
3
+ import { createTransformer } from '../create-transformer';
4
+
5
+ type Image = {
6
+ src?: {
7
+ id: number | null;
8
+ url: string | null;
9
+ };
10
+ size?: string;
11
+ };
12
+
13
+ export const imageTransformer = createTransformer( async ( value: Image ) => {
14
+ const { src, size } = value;
15
+
16
+ if ( ! src?.id ) {
17
+ return src?.url ? { src: src.url } : null;
18
+ }
19
+
20
+ const attachment = await getMediaAttachment( { id: src.id } );
21
+
22
+ const sizedAttachment = attachment?.sizes?.[ size ?? '' ];
23
+
24
+ if ( sizedAttachment ) {
25
+ return {
26
+ src: sizedAttachment.url,
27
+ height: sizedAttachment.height,
28
+ width: sizedAttachment.width,
29
+ };
30
+ }
31
+
32
+ if ( attachment ) {
33
+ return {
34
+ src: attachment.url,
35
+ height: attachment.height,
36
+ width: attachment.width,
37
+ };
38
+ }
39
+
40
+ return null;
41
+ } );
@@ -0,0 +1,5 @@
1
+ import { createTransformer } from '../create-transformer';
2
+
3
+ export const plainTransformer = createTransformer( ( value: unknown ) => {
4
+ return value;
5
+ } );
@@ -1,5 +1,15 @@
1
1
  import { createTransformer } from '../create-transformer';
2
2
 
3
- export const backgroundColorOverlayTransformer = createTransformer(
4
- ( value: string ) => `linear-gradient(${ value }, ${ value })`
5
- );
3
+ type BackgroundColorOverlay = {
4
+ color?: string;
5
+ };
6
+
7
+ export const backgroundColorOverlayTransformer = createTransformer( ( value: BackgroundColorOverlay ) => {
8
+ const { color = null } = value;
9
+
10
+ if ( ! color ) {
11
+ return null;
12
+ }
13
+
14
+ return `linear-gradient(${ color }, ${ color })`;
15
+ } );
@@ -3,7 +3,9 @@ import { createTransformer } from '../create-transformer';
3
3
  const DEFAULT_POSITION_VALUE = '0% 0%';
4
4
 
5
5
  type BackgroundImageOverlay = {
6
- image?: string;
6
+ image?: {
7
+ src?: string;
8
+ };
7
9
  size?: string;
8
10
  position?: string;
9
11
  repeat?: string;
@@ -17,8 +19,10 @@ export const backgroundImageOverlayTransformer = createTransformer( ( value: Bac
17
19
  return null;
18
20
  }
19
21
 
22
+ const src = image.src ? `url(${ image.src })` : null;
23
+
20
24
  const backgroundStyles = [
21
- image,
25
+ src,
22
26
  repeat,
23
27
  attachment,
24
28
  size ? `${ position || DEFAULT_POSITION_VALUE } / ${ size }` : position,
@@ -1,15 +0,0 @@
1
- import { getMediaAttachment } from '@elementor/wp-media';
2
-
3
- import { createTransformer } from '../create-transformer';
4
-
5
- type ImageAttachmentId = number;
6
-
7
- export const imageAttachmentTransformer = createTransformer( async ( value: ImageAttachmentId ) => {
8
- const attachment = await getMediaAttachment( { id: value } );
9
-
10
- if ( ! attachment ) {
11
- return null;
12
- }
13
-
14
- return attachment;
15
- } );
@@ -1,34 +0,0 @@
1
- import { createTransformer } from '../create-transformer';
2
-
3
- type Image = {
4
- src?: {
5
- url?: string;
6
- attachment?: {
7
- url?: string;
8
- sizes?: Record< string, { url: string } >;
9
- };
10
- };
11
- size?: string;
12
- };
13
-
14
- export const imageTransformer = createTransformer( ( value: Image ) => {
15
- const { src, size } = value;
16
-
17
- if ( src?.url ) {
18
- return `url(${ src.url })`;
19
- }
20
-
21
- const sizeUrl = src?.attachment?.sizes?.[ size ?? '' ]?.url;
22
-
23
- if ( sizeUrl ) {
24
- return `url(${ sizeUrl })`;
25
- }
26
-
27
- const attachmentUrl = src?.attachment?.url;
28
-
29
- if ( attachmentUrl ) {
30
- return `url(${ attachmentUrl })`;
31
- }
32
-
33
- return null;
34
- } );
@@ -1,3 +0,0 @@
1
- import { createTransformer } from '../create-transformer';
2
-
3
- export const primitiveTransformer = createTransformer( ( value: unknown ) => value );