@elementor/editor-canvas 3.33.0-99 → 3.34.2
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 +133 -10
- package/dist/index.d.ts +133 -10
- package/dist/index.js +1413 -212
- package/dist/index.mjs +1399 -180
- package/package.json +18 -14
- package/src/__tests__/settings-props-resolver.test.ts +0 -40
- package/src/__tests__/styles-prop-resolver.test.ts +13 -0
- package/src/components/__tests__/__snapshots__/style-renderer.test.tsx.snap +2 -6
- package/src/components/__tests__/elements-overlays.test.tsx +96 -12
- package/src/components/__tests__/inline-editor-overlay.test.tsx +245 -0
- package/src/components/__tests__/style-renderer.test.tsx +2 -2
- package/src/components/elements-overlays.tsx +33 -10
- package/src/components/inline-editor-overlay.tsx +79 -0
- package/src/components/interactions-renderer.tsx +33 -0
- package/src/components/{element-overlay.tsx → outline-overlay.tsx} +8 -7
- package/src/components/style-renderer.tsx +2 -4
- package/src/hooks/__tests__/use-has-overlapping.test.ts +187 -0
- package/src/hooks/use-floating-on-element.ts +11 -8
- package/src/hooks/use-has-overlapping.ts +21 -0
- package/src/hooks/use-interactions-items.ts +108 -0
- package/src/hooks/use-style-items.ts +34 -8
- package/src/index.ts +9 -0
- package/src/init-settings-transformers.ts +4 -0
- package/src/init.tsx +18 -0
- package/src/legacy/create-templated-element-type.ts +67 -42
- package/src/legacy/init-legacy-views.ts +27 -5
- package/src/legacy/types.ts +44 -4
- package/src/mcp/canvas-mcp.ts +17 -0
- package/src/mcp/mcp-description.ts +40 -0
- package/src/mcp/resources/widgets-schema-resource.ts +173 -0
- package/src/mcp/tools/build-composition/prompt.ts +128 -0
- package/src/mcp/tools/build-composition/schema.ts +31 -0
- package/src/mcp/tools/build-composition/tool.ts +163 -0
- package/src/mcp/tools/configure-element/prompt.ts +93 -0
- package/src/mcp/tools/configure-element/schema.ts +25 -0
- package/src/mcp/tools/configure-element/tool.ts +67 -0
- package/src/mcp/tools/get-element-config/tool.ts +69 -0
- package/src/mcp/utils/do-update-element-property.ts +129 -0
- package/src/mcp/utils/generate-available-tags.ts +23 -0
- package/src/renderers/__tests__/__snapshots__/create-styles-renderer.test.ts.snap +2 -0
- package/src/renderers/__tests__/create-styles-renderer.test.ts +25 -0
- package/src/renderers/create-props-resolver.ts +8 -1
- package/src/renderers/create-styles-renderer.ts +20 -9
- package/src/renderers/errors.ts +6 -0
- package/src/sync/drag-element-from-panel.ts +49 -0
- package/src/sync/types.ts +32 -1
- package/src/transformers/settings/__tests__/attributes-transformer.test.ts +15 -0
- package/src/transformers/settings/__tests__/classes-transformer.test.ts +83 -0
- package/src/transformers/settings/attributes-transformer.ts +1 -23
- package/src/transformers/settings/classes-transformer.ts +21 -21
- package/src/transformers/settings/date-time-transformer.ts +12 -0
- package/src/transformers/settings/query-transformer.ts +10 -0
- package/src/transformers/styles/__tests__/transform-origin-transformer.test.ts +24 -0
- package/src/transformers/styles/__tests__/transition-transformer.test.ts +52 -0
- package/src/transformers/styles/background-transformer.ts +3 -1
- package/src/transformers/styles/transform-origin-transformer.ts +12 -2
- package/src/transformers/styles/transition-transformer.ts +34 -4
- package/src/types/element-overlay.ts +18 -0
- package/src/utils/inline-editing-utils.ts +43 -0
|
@@ -2,20 +2,22 @@ import type { Props } from '@elementor/editor-props';
|
|
|
2
2
|
import { type Breakpoint, type BreakpointsMap } from '@elementor/editor-responsive';
|
|
3
3
|
import {
|
|
4
4
|
type CustomCss,
|
|
5
|
+
isClassState,
|
|
6
|
+
isPseudoState,
|
|
5
7
|
type StyleDefinition,
|
|
6
8
|
type StyleDefinitionState,
|
|
7
9
|
type StyleDefinitionType,
|
|
8
10
|
} from '@elementor/editor-styles';
|
|
9
|
-
import { EXPERIMENTAL_FEATURES, isExperimentActive } from '@elementor/editor-v1-adapters';
|
|
10
11
|
import { decodeString } from '@elementor/utils';
|
|
11
12
|
|
|
12
13
|
import { type PropsResolver } from './create-props-resolver';
|
|
13
|
-
import { UnknownStyleTypeError } from './errors';
|
|
14
|
+
import { UnknownStyleStateError, UnknownStyleTypeError } from './errors';
|
|
14
15
|
|
|
15
16
|
export type StyleItem = {
|
|
16
17
|
id: string;
|
|
17
18
|
value: string;
|
|
18
19
|
breakpoint: string;
|
|
20
|
+
state: StyleDefinitionState | null;
|
|
19
21
|
};
|
|
20
22
|
|
|
21
23
|
export type StyleRenderer = ReturnType< typeof createStylesRenderer >;
|
|
@@ -66,6 +68,7 @@ export function createStylesRenderer( { resolve, breakpoints, selectorPrefix = '
|
|
|
66
68
|
id: style.id,
|
|
67
69
|
breakpoint: style?.variants[ 0 ]?.meta?.breakpoint || 'desktop',
|
|
68
70
|
value: variantsCss.join( '' ),
|
|
71
|
+
state: style?.variants[ 0 ]?.meta?.state || null,
|
|
69
72
|
};
|
|
70
73
|
} );
|
|
71
74
|
|
|
@@ -88,9 +91,21 @@ function createStyleWrapper( value: string = '', wrapper?: ( css: string ) => st
|
|
|
88
91
|
withPrefix: ( prefix: string ) =>
|
|
89
92
|
createStyleWrapper( [ prefix, value ].filter( Boolean ).join( ' ' ), wrapper ),
|
|
90
93
|
|
|
91
|
-
withState: ( state: StyleDefinitionState ) =>
|
|
92
|
-
|
|
94
|
+
withState: ( state: StyleDefinitionState ) => {
|
|
95
|
+
if ( ! state ) {
|
|
96
|
+
return createStyleWrapper( value, wrapper );
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if ( isClassState( state ) ) {
|
|
100
|
+
return createStyleWrapper( `${ value }.${ state }`, wrapper );
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if ( isPseudoState( state ) ) {
|
|
104
|
+
return createStyleWrapper( `${ value }:${ state }`, wrapper );
|
|
105
|
+
}
|
|
93
106
|
|
|
107
|
+
throw new UnknownStyleStateError( { context: { state } } );
|
|
108
|
+
},
|
|
94
109
|
withMediaQuery: ( breakpoint: Breakpoint | null ) => {
|
|
95
110
|
if ( ! breakpoint?.type ) {
|
|
96
111
|
return createStyleWrapper( value, wrapper );
|
|
@@ -130,11 +145,7 @@ async function propsToCss( { props, resolve, signal }: PropsToCssArgs ) {
|
|
|
130
145
|
}
|
|
131
146
|
|
|
132
147
|
function customCssToString( customCss: CustomCss | null ): string {
|
|
133
|
-
|
|
134
|
-
return '';
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
const decoded = decodeString( customCss.raw );
|
|
148
|
+
const decoded = decodeString( customCss?.raw || '' );
|
|
138
149
|
|
|
139
150
|
if ( ! decoded.trim() ) {
|
|
140
151
|
return '';
|
package/src/renderers/errors.ts
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
|
+
import { type StyleDefinitionState } from '@elementor/editor-styles';
|
|
1
2
|
import { createError } from '@elementor/utils';
|
|
2
3
|
|
|
3
4
|
export const UnknownStyleTypeError = createError< { type: string } >( {
|
|
4
5
|
code: 'unknown_style_type',
|
|
5
6
|
message: 'Unknown style type',
|
|
6
7
|
} );
|
|
8
|
+
|
|
9
|
+
export const UnknownStyleStateError = createError< { state: StyleDefinitionState } >( {
|
|
10
|
+
code: 'unknown_style_state',
|
|
11
|
+
message: 'Unknown style state',
|
|
12
|
+
} );
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { type V1ElementModelProps } from '@elementor/editor-elements';
|
|
2
|
+
|
|
3
|
+
import { type CanvasExtendedWindow } from './types';
|
|
4
|
+
|
|
5
|
+
export const startDragElementFromPanel = ( props: Omit< V1ElementModelProps, 'id' > ) => {
|
|
6
|
+
const channels = getElementorChannels();
|
|
7
|
+
|
|
8
|
+
channels?.editor.reply( 'element:dragged', null );
|
|
9
|
+
|
|
10
|
+
channels?.panelElements
|
|
11
|
+
.reply( 'element:selected', getLegacyPanelElementView( props ) )
|
|
12
|
+
.trigger( 'element:drag:start' );
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export const endDragElementFromPanel = () => {
|
|
16
|
+
getElementorChannels()?.panelElements?.trigger( 'element:drag:end' );
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const getElementorChannels = () => {
|
|
20
|
+
const extendedWindow = window as unknown as CanvasExtendedWindow;
|
|
21
|
+
const channels = extendedWindow.elementor?.channels;
|
|
22
|
+
|
|
23
|
+
if ( ! channels ) {
|
|
24
|
+
throw new Error(
|
|
25
|
+
'Elementor channels not found: Elementor editor is not initialized or channels are unavailable.'
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return channels;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const getLegacyPanelElementView = ( { settings, ...rest }: Omit< V1ElementModelProps, 'id' > ) => {
|
|
33
|
+
const extendedWindow = window as unknown as CanvasExtendedWindow;
|
|
34
|
+
const LegacyElementModel = extendedWindow.elementor?.modules?.elements?.models?.Element;
|
|
35
|
+
|
|
36
|
+
if ( ! LegacyElementModel ) {
|
|
37
|
+
throw new Error( 'Elementor legacy Element model not found in editor modules' );
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const elementModel = new LegacyElementModel( {
|
|
41
|
+
...rest,
|
|
42
|
+
custom: {
|
|
43
|
+
isPreset: !! settings,
|
|
44
|
+
preset_settings: settings,
|
|
45
|
+
},
|
|
46
|
+
} );
|
|
47
|
+
|
|
48
|
+
return { model: elementModel };
|
|
49
|
+
};
|
package/src/sync/types.ts
CHANGED
|
@@ -1,17 +1,48 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type V1ElementModelProps, type V1ElementSettingsProps } from '@elementor/editor-elements';
|
|
2
2
|
|
|
3
|
+
import { type StorageContent } from '../prevent-link-in-link-commands';
|
|
3
4
|
export type EnqueueFont = ( fontFamily: string, context?: 'preview' | 'editor' ) => void;
|
|
4
5
|
|
|
6
|
+
export type ElementModelProps = Omit< V1ElementModelProps, 'id' > & {
|
|
7
|
+
custom: {
|
|
8
|
+
isPreset: boolean;
|
|
9
|
+
preset_settings?: V1ElementSettingsProps;
|
|
10
|
+
};
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
type Channel = {
|
|
14
|
+
reply: ( event: string, data: unknown ) => Channel;
|
|
15
|
+
trigger: ( event: string ) => void;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
type ElementorChannels = {
|
|
19
|
+
editor: Channel;
|
|
20
|
+
panelElements: Channel;
|
|
21
|
+
};
|
|
22
|
+
|
|
5
23
|
export type CanvasExtendedWindow = Window & {
|
|
6
24
|
elementor?: {
|
|
7
25
|
$preview?: [ HTMLIFrameElement ];
|
|
8
26
|
helpers?: {
|
|
9
27
|
enqueueFont?: EnqueueFont;
|
|
10
28
|
};
|
|
29
|
+
channels?: ElementorChannels;
|
|
30
|
+
modules?: {
|
|
31
|
+
elements?: {
|
|
32
|
+
models?: {
|
|
33
|
+
Element?: new ( props: ElementModelProps ) => unknown;
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
};
|
|
11
37
|
};
|
|
12
38
|
elementorCommon?: {
|
|
13
39
|
storage?: {
|
|
14
40
|
get: ( key?: string ) => StorageContent;
|
|
15
41
|
};
|
|
42
|
+
config?: {
|
|
43
|
+
urls?: {
|
|
44
|
+
assets?: string;
|
|
45
|
+
};
|
|
46
|
+
};
|
|
16
47
|
};
|
|
17
48
|
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { attributesTransformer } from '../attributes-transformer';
|
|
2
|
+
|
|
3
|
+
describe( 'attributesTransformer', () => {
|
|
4
|
+
it( 'always returns empty string in core', () => {
|
|
5
|
+
const result = attributesTransformer(
|
|
6
|
+
[
|
|
7
|
+
{ key: 'data-id', value: '123' },
|
|
8
|
+
{ key: 'role', value: 'button' },
|
|
9
|
+
],
|
|
10
|
+
{ key: 'attributes' }
|
|
11
|
+
);
|
|
12
|
+
|
|
13
|
+
expect( result ).toBe( '' );
|
|
14
|
+
} );
|
|
15
|
+
} );
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { createClassesTransformer } from '../classes-transformer';
|
|
2
|
+
|
|
3
|
+
jest.mock( '@elementor/editor-styles-repository', () => ( {
|
|
4
|
+
stylesRepository: {
|
|
5
|
+
getProviders: jest.fn(),
|
|
6
|
+
getProviderByKey: jest.fn(),
|
|
7
|
+
},
|
|
8
|
+
} ) );
|
|
9
|
+
|
|
10
|
+
const mockStylesRepository = require( '@elementor/editor-styles-repository' ).stylesRepository;
|
|
11
|
+
|
|
12
|
+
describe( 'createClassesTransformer', () => {
|
|
13
|
+
const createMockProvider = ( key: string, styles: Array< { id: string } > = [] ) => ( {
|
|
14
|
+
getKey: () => key,
|
|
15
|
+
actions: {
|
|
16
|
+
all: () => styles,
|
|
17
|
+
resolveCssName: jest.fn( ( id: string ): string | null => `resolved-${ id }` ),
|
|
18
|
+
},
|
|
19
|
+
} );
|
|
20
|
+
|
|
21
|
+
const createMockStyle = ( id: string ) => ( { id } );
|
|
22
|
+
|
|
23
|
+
beforeEach( () => {
|
|
24
|
+
jest.clearAllMocks();
|
|
25
|
+
} );
|
|
26
|
+
|
|
27
|
+
it( 'should transform class IDs using provider CSS names', () => {
|
|
28
|
+
const provider = createMockProvider( 'test-provider', [ createMockStyle( 'class-1' ) ] );
|
|
29
|
+
const provider2 = createMockProvider( 'test-provider-2', [ createMockStyle( 'class-2' ) ] );
|
|
30
|
+
mockStylesRepository.getProviders.mockReturnValue( [ provider, provider2 ] );
|
|
31
|
+
mockStylesRepository.getProviderByKey.mockReturnValueOnce( provider ).mockReturnValueOnce( provider2 );
|
|
32
|
+
const transformer = createClassesTransformer();
|
|
33
|
+
|
|
34
|
+
const result = transformer( [ 'class-1', 'class-2' ], { key: 'test-key' } );
|
|
35
|
+
|
|
36
|
+
expect( result ).toEqual( [ 'resolved-class-1', 'resolved-class-2' ] );
|
|
37
|
+
expect( provider.actions.resolveCssName ).toHaveBeenCalledWith( 'class-1' );
|
|
38
|
+
} );
|
|
39
|
+
|
|
40
|
+
it( 'should return original ID when no provider is found', () => {
|
|
41
|
+
mockStylesRepository.getProviders.mockReturnValue( [] );
|
|
42
|
+
const transformer = createClassesTransformer();
|
|
43
|
+
|
|
44
|
+
const result = transformer( [ 'unknown-class' ], { key: 'test-key' } );
|
|
45
|
+
|
|
46
|
+
expect( result ).toEqual( [ 'unknown-class' ] );
|
|
47
|
+
} );
|
|
48
|
+
|
|
49
|
+
it( 'should return original ID when provider has no matching style', () => {
|
|
50
|
+
const provider = createMockProvider( 'test-provider', [ createMockStyle( 'other-class' ) ] );
|
|
51
|
+
mockStylesRepository.getProviders.mockReturnValue( [ provider ] );
|
|
52
|
+
const transformer = createClassesTransformer();
|
|
53
|
+
|
|
54
|
+
const result = transformer( [ 'unknown-class' ], { key: 'test-key' } );
|
|
55
|
+
|
|
56
|
+
expect( result ).toEqual( [ 'unknown-class' ] );
|
|
57
|
+
} );
|
|
58
|
+
|
|
59
|
+
it( 'should cache provider keys to avoid repeated lookups', () => {
|
|
60
|
+
const provider = createMockProvider( 'test-provider', [ createMockStyle( 'class-1' ) ] );
|
|
61
|
+
mockStylesRepository.getProviders.mockReturnValue( [ provider ] );
|
|
62
|
+
mockStylesRepository.getProviderByKey.mockReturnValue( provider );
|
|
63
|
+
const transformer = createClassesTransformer();
|
|
64
|
+
|
|
65
|
+
transformer( [ 'class-1' ], { key: 'test-key' } );
|
|
66
|
+
transformer( [ 'class-1' ], { key: 'test-key' } );
|
|
67
|
+
|
|
68
|
+
expect( mockStylesRepository.getProviders ).toHaveBeenCalledTimes( 1 );
|
|
69
|
+
expect( provider.actions.resolveCssName ).toHaveBeenCalledTimes( 2 );
|
|
70
|
+
} );
|
|
71
|
+
|
|
72
|
+
it( 'should handle multiple class IDs', () => {
|
|
73
|
+
const provider1 = createMockProvider( 'provider-1', [ createMockStyle( 'class-1' ) ] );
|
|
74
|
+
const provider2 = createMockProvider( 'provider-2', [ createMockStyle( 'class-2' ) ] );
|
|
75
|
+
mockStylesRepository.getProviders.mockReturnValue( [ provider1, provider2 ] );
|
|
76
|
+
mockStylesRepository.getProviderByKey.mockReturnValueOnce( provider1 ).mockReturnValueOnce( provider2 );
|
|
77
|
+
const transformer = createClassesTransformer();
|
|
78
|
+
|
|
79
|
+
const result = transformer( [ 'class-1', 'class-2' ], { key: 'test-key' } );
|
|
80
|
+
|
|
81
|
+
expect( result ).toEqual( [ 'resolved-class-1', 'resolved-class-2' ] );
|
|
82
|
+
} );
|
|
83
|
+
} );
|
|
@@ -1,25 +1,3 @@
|
|
|
1
1
|
import { createTransformer } from '../create-transformer';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
const specialChars: Record< string, string > = {
|
|
5
|
-
'&': '&',
|
|
6
|
-
'<': '<',
|
|
7
|
-
'>': '>',
|
|
8
|
-
"'": ''',
|
|
9
|
-
'"': '"',
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
return value.replace( /[&<>'"]/g, ( char ) => specialChars[ char ] || char );
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export const attributesTransformer = createTransformer( ( values: { key: string; value: string }[] ) => {
|
|
16
|
-
return values
|
|
17
|
-
.map( ( value ) => {
|
|
18
|
-
if ( ! value.key || ! value.value ) {
|
|
19
|
-
return '';
|
|
20
|
-
}
|
|
21
|
-
const escapedValue = escapeHtmlAttribute( value.value );
|
|
22
|
-
return `${ value.key }="${ escapedValue }"`;
|
|
23
|
-
} )
|
|
24
|
-
.join( ' ' );
|
|
25
|
-
} );
|
|
3
|
+
export const attributesTransformer = createTransformer< { key: string; value: string }[] >( () => '' );
|
|
@@ -2,30 +2,30 @@ import { stylesRepository } from '@elementor/editor-styles-repository';
|
|
|
2
2
|
|
|
3
3
|
import { createTransformer } from '../create-transformer';
|
|
4
4
|
|
|
5
|
+
function transformClassId( id: string, cache: Map< string, string > ): string {
|
|
6
|
+
if ( ! cache.has( id ) ) {
|
|
7
|
+
const provider = stylesRepository.getProviders().find( ( p ) => {
|
|
8
|
+
return p.actions.all().find( ( style ) => style.id === id );
|
|
9
|
+
} );
|
|
10
|
+
|
|
11
|
+
if ( ! provider ) {
|
|
12
|
+
return id;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
cache.set( id, provider.getKey() );
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const providerKey = cache.get( id ) as string;
|
|
19
|
+
|
|
20
|
+
const provider = stylesRepository.getProviderByKey( providerKey );
|
|
21
|
+
|
|
22
|
+
return provider?.actions.resolveCssName( id ) ?? id;
|
|
23
|
+
}
|
|
24
|
+
|
|
5
25
|
export function createClassesTransformer() {
|
|
6
26
|
const cache = new Map< string, string >();
|
|
7
27
|
|
|
8
28
|
return createTransformer( ( value: string[] ) => {
|
|
9
|
-
return value
|
|
10
|
-
.map( ( id ) => {
|
|
11
|
-
if ( ! cache.has( id ) ) {
|
|
12
|
-
cache.set( id, getCssName( id ) );
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
return cache.get( id );
|
|
16
|
-
} )
|
|
17
|
-
.filter( Boolean );
|
|
18
|
-
} );
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function getCssName( id: string ) {
|
|
22
|
-
const provider = stylesRepository.getProviders().find( ( p ) => {
|
|
23
|
-
return p.actions.all().find( ( style ) => style.id === id );
|
|
29
|
+
return value.map( ( id ) => transformClassId( id, cache ) ).filter( Boolean );
|
|
24
30
|
} );
|
|
25
|
-
|
|
26
|
-
if ( ! provider ) {
|
|
27
|
-
return id;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
return provider.actions.resolveCssName( id );
|
|
31
31
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { createTransformer } from '../create-transformer';
|
|
2
|
+
|
|
3
|
+
export const dateTimeTransformer = createTransformer( ( values: { date?: string; time?: string }[] ) => {
|
|
4
|
+
return values
|
|
5
|
+
.map( ( value ) => {
|
|
6
|
+
const date = ( value.date || '' ).trim();
|
|
7
|
+
const time = ( value.time || '' ).trim();
|
|
8
|
+
|
|
9
|
+
return ! date && ! time ? '' : `${ date } ${ time }`.trim();
|
|
10
|
+
} )
|
|
11
|
+
.join( ' ' );
|
|
12
|
+
} );
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type TransformOrigin, transformOriginTransformer } from '../transform-origin-transformer';
|
|
2
|
+
|
|
3
|
+
function run( val: TransformOrigin ) {
|
|
4
|
+
return transformOriginTransformer(
|
|
5
|
+
{
|
|
6
|
+
x: val.x as string,
|
|
7
|
+
y: val.y as string,
|
|
8
|
+
z: val.z as string,
|
|
9
|
+
},
|
|
10
|
+
{ key: 'transform-origin', signal: undefined }
|
|
11
|
+
);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
describe( 'transform-origin-transformer', () => {
|
|
15
|
+
it( 'returns null for defaults (50% 50% 0px)', () => {
|
|
16
|
+
expect( run( { x: '50%', y: '50%', z: '0px' } ) ).toBeNull();
|
|
17
|
+
} );
|
|
18
|
+
|
|
19
|
+
it( 'returns value when non-default provided', () => {
|
|
20
|
+
expect( run( { x: '51%', y: '50%', z: '0px' } ) ).toBe( '51% 50% 0px' );
|
|
21
|
+
expect( run( { x: '50%', y: '49%', z: '0px' } ) ).toBe( '50% 49% 0px' );
|
|
22
|
+
expect( run( { x: '50%', y: '50%', z: '1px' } ) ).toBe( '50% 50% 1px' );
|
|
23
|
+
} );
|
|
24
|
+
} );
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { transitionProperties } from '@elementor/editor-controls';
|
|
2
|
+
|
|
3
|
+
import { transitionTransformer, type TransitionValue } from '../transition-transformer';
|
|
4
|
+
|
|
5
|
+
function run( values: TransitionValue[] ) {
|
|
6
|
+
return transitionTransformer( values, { key: 'transition', signal: undefined } );
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
describe( 'transition-transformer', () => {
|
|
10
|
+
it( 'returns null when all transitions are invalid', () => {
|
|
11
|
+
const invalidTransitions: TransitionValue[] = [
|
|
12
|
+
{
|
|
13
|
+
selection: { key: 'invalid-property', value: 'invalid-property' },
|
|
14
|
+
size: '200ms',
|
|
15
|
+
},
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
expect( run( invalidTransitions ) ).toBeNull();
|
|
19
|
+
} );
|
|
20
|
+
|
|
21
|
+
it( 'returns valid transition string for allowed property', () => {
|
|
22
|
+
const allowedProperty = transitionProperties[ 0 ].properties[ 0 ].value;
|
|
23
|
+
const validTransitions: TransitionValue[] = [
|
|
24
|
+
{
|
|
25
|
+
selection: { key: 'all', value: allowedProperty },
|
|
26
|
+
size: '200ms',
|
|
27
|
+
},
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
expect( run( validTransitions ) ).toBe( `${ allowedProperty } 200ms` );
|
|
31
|
+
} );
|
|
32
|
+
|
|
33
|
+
it( 'filters out non-allowed transitions and returns only valid ones', () => {
|
|
34
|
+
const allowedProperty = transitionProperties[ 0 ].properties[ 0 ].value;
|
|
35
|
+
const transitionsWithMixedValidity: TransitionValue[] = [
|
|
36
|
+
{
|
|
37
|
+
selection: { key: 'all', value: allowedProperty },
|
|
38
|
+
size: '200ms',
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
selection: { key: 'invalid', value: 'invalid-property' },
|
|
42
|
+
size: '300ms',
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
selection: { key: 'another-invalid', value: 'another-invalid-property' },
|
|
46
|
+
size: '400ms',
|
|
47
|
+
},
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
expect( run( transitionsWithMixedValidity ) ).toBe( `${ allowedProperty } 200ms` );
|
|
51
|
+
} );
|
|
52
|
+
} );
|
|
@@ -5,13 +5,15 @@ import { type BackgroundOverlayTransformed } from './background-overlay-transfor
|
|
|
5
5
|
type Background = {
|
|
6
6
|
'background-overlay'?: BackgroundOverlayTransformed;
|
|
7
7
|
color?: string;
|
|
8
|
+
clip?: 'border-box' | 'padding-box' | 'content-box' | 'text' | null;
|
|
8
9
|
};
|
|
9
10
|
|
|
10
11
|
export const backgroundTransformer = createTransformer( ( value: Background ) => {
|
|
11
|
-
const { color = null, 'background-overlay': overlays = null } = value;
|
|
12
|
+
const { color = null, 'background-overlay': overlays = null, clip = null } = value;
|
|
12
13
|
|
|
13
14
|
return createMultiPropsValue( {
|
|
14
15
|
...overlays,
|
|
15
16
|
'background-color': color,
|
|
17
|
+
'background-clip': clip,
|
|
16
18
|
} );
|
|
17
19
|
} );
|
|
@@ -1,17 +1,27 @@
|
|
|
1
1
|
import { createTransformer } from '../create-transformer';
|
|
2
2
|
|
|
3
|
-
type TransformOrigin = {
|
|
3
|
+
export type TransformOrigin = {
|
|
4
4
|
x: string;
|
|
5
5
|
y: string;
|
|
6
6
|
z: string;
|
|
7
7
|
};
|
|
8
8
|
|
|
9
9
|
const EMPTY_VALUE = '0px';
|
|
10
|
+
const DEFAULT_XY = '50%';
|
|
11
|
+
const DEFAULT_Z = EMPTY_VALUE;
|
|
10
12
|
|
|
11
13
|
function getVal( val: string ) {
|
|
12
14
|
return `${ val ?? EMPTY_VALUE }`;
|
|
13
15
|
}
|
|
14
16
|
|
|
15
17
|
export const transformOriginTransformer = createTransformer( ( value: TransformOrigin ) => {
|
|
16
|
-
|
|
18
|
+
const x = getVal( value.x );
|
|
19
|
+
const y = getVal( value.y );
|
|
20
|
+
const z = getVal( value.z );
|
|
21
|
+
|
|
22
|
+
if ( x === DEFAULT_XY && y === DEFAULT_XY && z === DEFAULT_Z ) {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return `${ x } ${ y } ${ z }`;
|
|
17
27
|
} );
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { transitionProperties } from '@elementor/editor-controls';
|
|
2
|
+
|
|
1
3
|
import { createTransformer } from '../create-transformer';
|
|
2
4
|
|
|
3
|
-
type TransitionValue = {
|
|
5
|
+
export type TransitionValue = {
|
|
4
6
|
selection: {
|
|
5
7
|
key: string;
|
|
6
8
|
value: string;
|
|
@@ -8,18 +10,46 @@ type TransitionValue = {
|
|
|
8
10
|
size: string;
|
|
9
11
|
};
|
|
10
12
|
|
|
13
|
+
const getAllowedProperties = (): Set< string > => {
|
|
14
|
+
const allowedProperties = new Set< string >();
|
|
15
|
+
|
|
16
|
+
transitionProperties.forEach( ( category ) => {
|
|
17
|
+
category.properties.forEach( ( property ) => {
|
|
18
|
+
allowedProperties.add( property.value );
|
|
19
|
+
} );
|
|
20
|
+
} );
|
|
21
|
+
|
|
22
|
+
return allowedProperties;
|
|
23
|
+
};
|
|
24
|
+
|
|
11
25
|
export const transitionTransformer = createTransformer( ( transitionValues: TransitionValue[] ) => {
|
|
12
26
|
if ( transitionValues?.length < 1 ) {
|
|
13
27
|
return null;
|
|
14
28
|
}
|
|
15
29
|
|
|
16
|
-
|
|
30
|
+
const allowedProperties = getAllowedProperties();
|
|
31
|
+
|
|
32
|
+
const validTransitions = transitionValues
|
|
33
|
+
.map( ( value ) => mapToTransitionString( value, allowedProperties ) )
|
|
34
|
+
.filter( Boolean );
|
|
35
|
+
|
|
36
|
+
if ( validTransitions.length === 0 ) {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return validTransitions.join( ', ' );
|
|
17
41
|
} );
|
|
18
42
|
|
|
19
|
-
const mapToTransitionString = ( value: TransitionValue ): string => {
|
|
43
|
+
const mapToTransitionString = ( value: TransitionValue, allowedProperties: Set< string > ): string => {
|
|
20
44
|
if ( ! value.selection || ! value.size ) {
|
|
21
45
|
return '';
|
|
22
46
|
}
|
|
23
47
|
|
|
24
|
-
|
|
48
|
+
const property = value.selection.value;
|
|
49
|
+
|
|
50
|
+
if ( ! allowedProperties.has( property ) ) {
|
|
51
|
+
return '';
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return `${ property } ${ value.size }`;
|
|
25
55
|
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type * as React from 'react';
|
|
2
|
+
|
|
3
|
+
export type ElementOverlayProps = {
|
|
4
|
+
element: HTMLElement;
|
|
5
|
+
id: string;
|
|
6
|
+
isSelected: boolean;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export type OverlayFilterArgs = {
|
|
10
|
+
id: string;
|
|
11
|
+
element: HTMLElement;
|
|
12
|
+
isSelected: boolean;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export type ElementOverlayConfig = {
|
|
16
|
+
component: React.ComponentType< ElementOverlayProps >;
|
|
17
|
+
shouldRender: ( args: OverlayFilterArgs ) => boolean;
|
|
18
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { getContainer, getElementType, type V1Element } from '@elementor/editor-elements';
|
|
2
|
+
|
|
3
|
+
const WIDGET_PROPERTY_MAP: Record< string, string > = {
|
|
4
|
+
'e-heading': 'title',
|
|
5
|
+
'e-paragraph': 'paragraph',
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
const getHtmlPropertyName = ( container: V1Element | null ): string => {
|
|
9
|
+
const widgetType = container?.model?.get( 'widgetType' ) ?? container?.model?.get( 'elType' );
|
|
10
|
+
|
|
11
|
+
if ( ! widgetType ) {
|
|
12
|
+
return '';
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if ( WIDGET_PROPERTY_MAP[ widgetType ] ) {
|
|
16
|
+
return WIDGET_PROPERTY_MAP[ widgetType ];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const propsSchema = getElementType( widgetType )?.propsSchema;
|
|
20
|
+
|
|
21
|
+
if ( ! propsSchema ) {
|
|
22
|
+
return '';
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const entry = Object.entries( propsSchema ).find( ( [ , propType ] ) => propType.key === 'html' );
|
|
26
|
+
|
|
27
|
+
return entry?.[ 0 ] ?? '';
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export const hasInlineEditableProperty = ( containerId: string ): boolean => {
|
|
31
|
+
const container = getContainer( containerId );
|
|
32
|
+
const widgetType = container?.model?.get( 'widgetType' ) ?? container?.model?.get( 'elType' );
|
|
33
|
+
|
|
34
|
+
if ( ! widgetType ) {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return widgetType in WIDGET_PROPERTY_MAP;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export const getInlineEditablePropertyName = ( container: V1Element | null ): string => {
|
|
42
|
+
return getHtmlPropertyName( container );
|
|
43
|
+
};
|