@elementor/editor-canvas 4.3.0-993 → 4.3.0-995
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 +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +181 -901
- package/dist/index.mjs +124 -855
- package/package.json +20 -20
- package/src/index.ts +1 -1
- package/src/mcp/canvas-mcp.ts +2 -2
- package/src/mcp/tools/build-composition/tool.ts +107 -218
- package/src/sync/element-added-event.ts +8 -0
- package/src/composition-builder/__tests__/composition-builder.test.ts +0 -352
- package/src/composition-builder/composition-builder.ts +0 -350
- package/src/composition-builder/utils/__tests__/required-children-enforcer.test.ts +0 -79
- package/src/composition-builder/utils/__tests__/required-default-child-tags.test.ts +0 -35
- package/src/composition-builder/utils/required-children-enforcer.ts +0 -56
- package/src/composition-builder/utils/required-default-child-tags.ts +0 -22
- package/src/mcp/tools/build-composition/__tests__/xml-leaf-wrapper.test.ts +0 -117
- package/src/mcp/tools/build-composition/prompt.ts +0 -167
- package/src/mcp/tools/build-composition/schema.ts +0 -42
- package/src/mcp/tools/build-composition/xml-leaf-wrapper.ts +0 -68
- package/src/mcp/utils/__tests__/get-composition-target-container.test.ts +0 -59
- package/src/mcp/utils/get-composition-target-container.ts +0 -15
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
import { type V1ElementConfig } from '@elementor/editor-elements';
|
|
2
|
-
|
|
3
|
-
import { RequiredChildrenEnforcer } from '../required-children-enforcer';
|
|
4
|
-
|
|
5
|
-
describe( 'RequiredChildrenEnforcer', () => {
|
|
6
|
-
const FULL_FORM_DIRECT_CHILD_COUNT = 3;
|
|
7
|
-
|
|
8
|
-
const createWidgetsCache = (): Record< string, V1ElementConfig > => ( {
|
|
9
|
-
'e-form': {
|
|
10
|
-
title: 'Form',
|
|
11
|
-
controls: {},
|
|
12
|
-
elType: 'widget',
|
|
13
|
-
default_children: [
|
|
14
|
-
{
|
|
15
|
-
elType: 'e-form-success-message',
|
|
16
|
-
meta: { required: true },
|
|
17
|
-
elements: [],
|
|
18
|
-
},
|
|
19
|
-
{
|
|
20
|
-
elType: 'e-form-error-message',
|
|
21
|
-
meta: { required: true },
|
|
22
|
-
elements: [],
|
|
23
|
-
},
|
|
24
|
-
{
|
|
25
|
-
elType: 'widget',
|
|
26
|
-
widgetType: 'e-form-input',
|
|
27
|
-
elements: [],
|
|
28
|
-
},
|
|
29
|
-
],
|
|
30
|
-
} as V1ElementConfig,
|
|
31
|
-
'e-form-input': { title: 'Input', controls: {}, elType: 'widget' } as V1ElementConfig,
|
|
32
|
-
'e-form-success-message': {
|
|
33
|
-
title: 'Success',
|
|
34
|
-
controls: {},
|
|
35
|
-
elType: 'e-form-success-message',
|
|
36
|
-
} as V1ElementConfig,
|
|
37
|
-
'e-form-error-message': {
|
|
38
|
-
title: 'Error',
|
|
39
|
-
controls: {},
|
|
40
|
-
elType: 'e-form-error-message',
|
|
41
|
-
} as V1ElementConfig,
|
|
42
|
-
} );
|
|
43
|
-
|
|
44
|
-
it( 'throws when required direct children are missing', () => {
|
|
45
|
-
// Arrange
|
|
46
|
-
const xml = new DOMParser().parseFromString( '<e-form><e-form-input /></e-form>', 'application/xml' );
|
|
47
|
-
const enforcer = new RequiredChildrenEnforcer( 'e-form', createWidgetsCache() );
|
|
48
|
-
|
|
49
|
-
// Act & Assert
|
|
50
|
-
expect( () => enforcer.enforce( xml ) ).toThrow(
|
|
51
|
-
/Missing required direct child element tag\(s\): e-form-success-message, e-form-error-message/
|
|
52
|
-
);
|
|
53
|
-
} );
|
|
54
|
-
|
|
55
|
-
it( 'throws when only some required direct children exist', () => {
|
|
56
|
-
// Arrange
|
|
57
|
-
const xml = new DOMParser().parseFromString( '<e-form><e-form-success-message /></e-form>', 'application/xml' );
|
|
58
|
-
const enforcer = new RequiredChildrenEnforcer( 'e-form', createWidgetsCache() );
|
|
59
|
-
|
|
60
|
-
// Act & Assert
|
|
61
|
-
expect( () => enforcer.enforce( xml ) ).toThrow(
|
|
62
|
-
/Missing required direct child element tag\(s\): e-form-error-message/
|
|
63
|
-
);
|
|
64
|
-
} );
|
|
65
|
-
|
|
66
|
-
it( 'does not throw when all required direct children exist', () => {
|
|
67
|
-
// Arrange
|
|
68
|
-
const xmlStr = '<e-form><e-form-success-message /><e-form-error-message /><e-form-input /></e-form>';
|
|
69
|
-
const xml = new DOMParser().parseFromString( xmlStr, 'application/xml' );
|
|
70
|
-
const enforcer = new RequiredChildrenEnforcer( 'e-form', createWidgetsCache() );
|
|
71
|
-
|
|
72
|
-
// Act
|
|
73
|
-
expect( () => enforcer.enforce( xml ) ).not.toThrow();
|
|
74
|
-
|
|
75
|
-
// Assert
|
|
76
|
-
const form = xml.querySelector( 'e-form' );
|
|
77
|
-
expect( form?.children.length ).toBe( FULL_FORM_DIRECT_CHILD_COUNT );
|
|
78
|
-
} );
|
|
79
|
-
} );
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import { type V1ElementConfig } from '@elementor/editor-elements';
|
|
2
|
-
|
|
3
|
-
import { getRequiredDefaultChildTypes } from '../required-default-child-tags';
|
|
4
|
-
|
|
5
|
-
describe( 'required-default-child-tags', () => {
|
|
6
|
-
it( 'returns XML tag names for default children marked meta.required', () => {
|
|
7
|
-
// Arrange
|
|
8
|
-
const config = {
|
|
9
|
-
title: 'Form',
|
|
10
|
-
controls: {},
|
|
11
|
-
default_children: [
|
|
12
|
-
{
|
|
13
|
-
elType: 'e-form-success-message',
|
|
14
|
-
meta: { required: true },
|
|
15
|
-
elements: [],
|
|
16
|
-
},
|
|
17
|
-
{
|
|
18
|
-
elType: 'widget',
|
|
19
|
-
widgetType: 'e-form-input',
|
|
20
|
-
meta: { required: true },
|
|
21
|
-
},
|
|
22
|
-
{
|
|
23
|
-
elType: 'e-form-label',
|
|
24
|
-
elements: [],
|
|
25
|
-
},
|
|
26
|
-
],
|
|
27
|
-
} as unknown as V1ElementConfig;
|
|
28
|
-
|
|
29
|
-
// Act
|
|
30
|
-
const tags = getRequiredDefaultChildTypes( config );
|
|
31
|
-
|
|
32
|
-
// Assert
|
|
33
|
-
expect( tags ).toEqual( [ 'e-form-success-message', 'e-form-input' ] );
|
|
34
|
-
} );
|
|
35
|
-
} );
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import { type V1ElementConfig } from '@elementor/editor-elements';
|
|
2
|
-
|
|
3
|
-
import { type ChildTemplate, getRequiredDefaultChildTemplates } from './required-default-child-tags';
|
|
4
|
-
|
|
5
|
-
const REQUIRED_CHILD_SCHEMA_HINT =
|
|
6
|
-
'Use the widget schema resource; under llm_guidance.required_direct_children for V4 widgets.';
|
|
7
|
-
|
|
8
|
-
export class RequiredChildrenEnforcer {
|
|
9
|
-
private readonly elementType: string;
|
|
10
|
-
private readonly requiredTemplates: ChildTemplate[];
|
|
11
|
-
|
|
12
|
-
constructor( elementType: string, widgetsCache: Record< string, V1ElementConfig > ) {
|
|
13
|
-
this.elementType = elementType;
|
|
14
|
-
this.requiredTemplates = getRequiredDefaultChildTemplates( widgetsCache[ elementType ] );
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
enforce( xml: Document ) {
|
|
18
|
-
if ( this.requiredTemplates.length === 0 ) {
|
|
19
|
-
return;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
const errors: string[] = [];
|
|
23
|
-
|
|
24
|
-
for ( const rootNode of Array.from( xml.children ) ) {
|
|
25
|
-
this.collectMissingRequiredErrors( rootNode, errors );
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
if ( errors.length ) {
|
|
29
|
-
throw new Error( `${ errors.join( '\n' ) }\n${ REQUIRED_CHILD_SCHEMA_HINT }` );
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
private collectMissingRequiredErrors( node: Element, errors: string[] ) {
|
|
34
|
-
if ( node.tagName === this.elementType ) {
|
|
35
|
-
const existingChildTags = new Set( Array.from( node.children ).map( ( child ) => child.tagName ) );
|
|
36
|
-
const missingTags = this.requiredTemplates
|
|
37
|
-
.map( ( child ) => child.widgetType ?? child.elType ?? '' )
|
|
38
|
-
.filter( ( type ) => type && ! existingChildTags.has( type ) ) as string[];
|
|
39
|
-
|
|
40
|
-
if ( missingTags.length ) {
|
|
41
|
-
const configurationId = node.getAttribute( 'configuration-id' );
|
|
42
|
-
const location = configurationId
|
|
43
|
-
? `<${ node.tagName } configuration-id="${ configurationId }">`
|
|
44
|
-
: `<${ node.tagName }>`;
|
|
45
|
-
|
|
46
|
-
errors.push(
|
|
47
|
-
`${ location } Missing required direct child element tag(s): ${ missingTags.join( ', ' ) }.`
|
|
48
|
-
);
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
for ( const childNode of Array.from( node.children ) ) {
|
|
53
|
-
this.collectMissingRequiredErrors( childNode, errors );
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
}
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { type V1ElementConfig } from '@elementor/editor-elements';
|
|
2
|
-
|
|
3
|
-
export type ChildTemplate = {
|
|
4
|
-
widgetType?: string;
|
|
5
|
-
elType?: string;
|
|
6
|
-
meta?: { required?: boolean };
|
|
7
|
-
};
|
|
8
|
-
export function getRequiredDefaultChildTemplates( elementConfig: V1ElementConfig | undefined ): ChildTemplate[] {
|
|
9
|
-
const defaultChildren = elementConfig?.default_children as ChildTemplate[];
|
|
10
|
-
|
|
11
|
-
if ( ! Array.isArray( defaultChildren ) ) {
|
|
12
|
-
return [];
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
return defaultChildren.filter( ( child ) => child?.meta?.required ?? false );
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export function getRequiredDefaultChildTypes( elementConfig: V1ElementConfig | undefined ): string[] {
|
|
19
|
-
return getRequiredDefaultChildTemplates( elementConfig )
|
|
20
|
-
.map( ( child ) => child.widgetType ?? child.elType ?? '' )
|
|
21
|
-
.filter( ( type ) => Boolean( type ) );
|
|
22
|
-
}
|
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
import { type V1ElementConfig } from '@elementor/editor-elements';
|
|
2
|
-
|
|
3
|
-
import { adaptLeafRootParams, type BuildCompositionParams, DIV_BLOCK_TAG, ZERO_SPACING } from '../xml-leaf-wrapper';
|
|
4
|
-
|
|
5
|
-
const LEAF_WIDGET_TAG = 'e-heading';
|
|
6
|
-
const CONTAINER_TAG = 'e-div-block';
|
|
7
|
-
const DIV_BLOCK_TITLE = 'Div Block';
|
|
8
|
-
|
|
9
|
-
const makeWidgetsCache = (
|
|
10
|
-
overrides: Record< string, Partial< V1ElementConfig > > = {}
|
|
11
|
-
): Record< string, V1ElementConfig > =>
|
|
12
|
-
( {
|
|
13
|
-
[ LEAF_WIDGET_TAG ]: { title: 'Heading', controls: {}, elType: 'widget' },
|
|
14
|
-
[ CONTAINER_TAG ]: { title: DIV_BLOCK_TITLE, controls: {}, elType: 'e-div-block' },
|
|
15
|
-
...overrides,
|
|
16
|
-
} ) as Record< string, V1ElementConfig >;
|
|
17
|
-
|
|
18
|
-
const parseXml = ( xml: string ) => new DOMParser().parseFromString( xml, 'application/xml' );
|
|
19
|
-
|
|
20
|
-
const expectElementAttribute = ( element: Element, attribute: string, value: string ) => {
|
|
21
|
-
// eslint-disable-next-line jest-dom/prefer-to-have-attribute -- XML Element, not HTMLElement
|
|
22
|
-
expect( element.getAttribute( attribute ) ).toBe( value );
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
const makeParams = ( xmlStructure: string, overrides = {} ) =>
|
|
26
|
-
( {
|
|
27
|
-
xmlStructure,
|
|
28
|
-
stylesConfig: {},
|
|
29
|
-
elementConfig: {},
|
|
30
|
-
widgetsCache: makeWidgetsCache(),
|
|
31
|
-
...overrides,
|
|
32
|
-
} ) as BuildCompositionParams;
|
|
33
|
-
|
|
34
|
-
describe( 'adaptLeafRootParams', () => {
|
|
35
|
-
it( 'wraps a leaf widget root in a div-block', () => {
|
|
36
|
-
// Arrange
|
|
37
|
-
const params = makeParams( `<${ LEAF_WIDGET_TAG } configuration-id="heading-1" />` );
|
|
38
|
-
|
|
39
|
-
// Act
|
|
40
|
-
const result = adaptLeafRootParams( params );
|
|
41
|
-
|
|
42
|
-
// Assert
|
|
43
|
-
const doc = parseXml( result.xmlStructure );
|
|
44
|
-
expect( doc.documentElement.tagName ).toBe( DIV_BLOCK_TAG );
|
|
45
|
-
expect( doc.documentElement.children[ 0 ].tagName ).toBe( LEAF_WIDGET_TAG );
|
|
46
|
-
expectElementAttribute( doc.documentElement, 'configuration-id', DIV_BLOCK_TITLE );
|
|
47
|
-
} );
|
|
48
|
-
|
|
49
|
-
it( 'preserves existing stylesConfig entries when wrapping', () => {
|
|
50
|
-
// Arrange
|
|
51
|
-
const params = makeParams( `<${ LEAF_WIDGET_TAG } configuration-id="heading-1" />`, {
|
|
52
|
-
stylesConfig: { 'heading-1': { color: 'red' } },
|
|
53
|
-
} );
|
|
54
|
-
|
|
55
|
-
// Act
|
|
56
|
-
const result = adaptLeafRootParams( params );
|
|
57
|
-
|
|
58
|
-
// Assert
|
|
59
|
-
expect( result.stylesConfig[ 'heading-1' ] ).toEqual( { color: 'red' } );
|
|
60
|
-
} );
|
|
61
|
-
|
|
62
|
-
it( 'does not wrap when root is a container', () => {
|
|
63
|
-
// Arrange
|
|
64
|
-
const params = makeParams(
|
|
65
|
-
`<${ CONTAINER_TAG } configuration-id="container-1"><${ LEAF_WIDGET_TAG } configuration-id="heading-1" /></${ CONTAINER_TAG }>`
|
|
66
|
-
);
|
|
67
|
-
|
|
68
|
-
// Act
|
|
69
|
-
const result = adaptLeafRootParams( params );
|
|
70
|
-
|
|
71
|
-
// Assert
|
|
72
|
-
expect( result ).toBe( params );
|
|
73
|
-
} );
|
|
74
|
-
|
|
75
|
-
it( 'preserves extra params fields unchanged', () => {
|
|
76
|
-
// Arrange
|
|
77
|
-
const params = makeParams( `<${ LEAF_WIDGET_TAG } configuration-id="heading-1" />`, {
|
|
78
|
-
elementConfig: { 'heading-1': { title: 'Hello' } },
|
|
79
|
-
stylesConfig: { 'heading-1': { color: 'red' } },
|
|
80
|
-
} );
|
|
81
|
-
|
|
82
|
-
// Act
|
|
83
|
-
const result = adaptLeafRootParams( params );
|
|
84
|
-
|
|
85
|
-
// Assert
|
|
86
|
-
expect( result.stylesConfig ).toEqual( {
|
|
87
|
-
'Div Block': {
|
|
88
|
-
margin: ZERO_SPACING,
|
|
89
|
-
padding: ZERO_SPACING,
|
|
90
|
-
},
|
|
91
|
-
...params.stylesConfig,
|
|
92
|
-
} );
|
|
93
|
-
} );
|
|
94
|
-
|
|
95
|
-
it( 'preserves leaf element attributes when wrapping', () => {
|
|
96
|
-
// Arrange
|
|
97
|
-
const params = makeParams( `<${ LEAF_WIDGET_TAG } configuration-id="heading-1" />` );
|
|
98
|
-
|
|
99
|
-
// Act
|
|
100
|
-
const { xmlStructure } = adaptLeafRootParams( params );
|
|
101
|
-
|
|
102
|
-
// Assert
|
|
103
|
-
const doc = parseXml( xmlStructure );
|
|
104
|
-
expectElementAttribute( doc.documentElement.children[ 0 ], 'configuration-id', 'heading-1' );
|
|
105
|
-
} );
|
|
106
|
-
|
|
107
|
-
it( 'returns unchanged params for unknown element types', () => {
|
|
108
|
-
// Arrange
|
|
109
|
-
const params = makeParams( '<unknown-widget configuration-id="w1" />' );
|
|
110
|
-
|
|
111
|
-
// Act
|
|
112
|
-
const result = adaptLeafRootParams( params );
|
|
113
|
-
|
|
114
|
-
// Assert
|
|
115
|
-
expect( result ).toBe( params );
|
|
116
|
-
} );
|
|
117
|
-
} );
|
|
@@ -1,167 +0,0 @@
|
|
|
1
|
-
import { toolPrompts } from '@elementor/editor-mcp';
|
|
2
|
-
|
|
3
|
-
import { AVAILABLE_WIDGETS_URI } from '../../resources/available-widgets-resource';
|
|
4
|
-
import { DYNAMIC_TAGS_URI } from '../../resources/dynamic-tags-resource';
|
|
5
|
-
|
|
6
|
-
export const BUILD_COMPOSITIONS_GUIDE_URI = 'elementor://canvas/tools/build-compositions-guide';
|
|
7
|
-
|
|
8
|
-
export const generatePrompt = () => {
|
|
9
|
-
const buildCompositionsToolPrompt = toolPrompts( 'build-compositions' );
|
|
10
|
-
|
|
11
|
-
buildCompositionsToolPrompt.description( `
|
|
12
|
-
# RESOURCES (Read before use)
|
|
13
|
-
- [elementor://global-classes] - Check FIRST for reusable classes
|
|
14
|
-
- [elementor://global-variables] - ONLY use variables defined here
|
|
15
|
-
- [${ AVAILABLE_WIDGETS_URI }/v4]
|
|
16
|
-
|
|
17
|
-
# TOOL SUPPORT
|
|
18
|
-
This tool support v4 elements only
|
|
19
|
-
|
|
20
|
-
# WORKFLOW
|
|
21
|
-
1. Check/create global classes via "manage-global-classes" tool
|
|
22
|
-
2. Build composition (THIS TOOL) - minimal inline styles
|
|
23
|
-
3. Apply classes via "apply-global-class" tool
|
|
24
|
-
|
|
25
|
-
# XML STRUCTURE
|
|
26
|
-
- Use widget tags: \`<e-button configuration-id="btn1"></e-button>\`
|
|
27
|
-
- Containers: "e-flexbox", "e-div-block", "e-tabs"
|
|
28
|
-
- Every element needs unique "configuration-id"
|
|
29
|
-
- No attributes, classes, IDs, or text nodes in XML
|
|
30
|
-
|
|
31
|
-
## NESTED ELEMENTS
|
|
32
|
-
Some elements have internal tree structures (nesting). When using these elements, you MUST build the FULL tree in XML.
|
|
33
|
-
- Check \`llm_guidance.nesting\` in widget schemas for structure requirements
|
|
34
|
-
- \`llm_guidance.required_direct_children\` lists element types that must appear as direct child tags in XML (from widget defaults)
|
|
35
|
-
- \`allowed_child_types\` lists which element types can be nested inside
|
|
36
|
-
- \`allowed_parents\` lists which element types this element can be placed inside
|
|
37
|
-
|
|
38
|
-
# CONFIGURATION
|
|
39
|
-
- Map configuration-id → elementConfig (props) + style (raw CSS declarations)
|
|
40
|
-
- elementConfig PropValues require \`$$type\` matching schema
|
|
41
|
-
- style is raw CSS (property → value strings); the server converts it to native styles and stores any unconvertible declarations as the element custom CSS
|
|
42
|
-
- NO LINKS in configuration
|
|
43
|
-
- Retry on errors up to 10x
|
|
44
|
-
- Check \`llm_guidance.default_settings\` in widget schemas — omit only keys listed there from elementConfig unless the user explicitly asks to change them
|
|
45
|
-
|
|
46
|
-
# DYNAMIC TAGS
|
|
47
|
-
- A value can be made dynamic wherever its schema exposes a \`"$$type": "dynamic"\` variant. This may be the property root OR a NESTED field (e.g. an image's \`src\`, not the whole \`image\`).
|
|
48
|
-
- Put the dynamic object EXACTLY at that node, in place of the static variant. The variant's \`name\` lists the allowed tags; read [${ DYNAMIC_TAGS_URI }] for each tag's settings schema.
|
|
49
|
-
- Provide at that node: \`{ "$$type": "dynamic", "value": { "name": "<allowed tag>", "settings": { ... } } }\`
|
|
50
|
-
- Example (image): \`{ "$$type": "image", "value": { "src": { "$$type": "dynamic", "value": { "name": "<image tag>", "settings": { ... } } } } }\`
|
|
51
|
-
- Do NOT send \`group\` (it is resolved automatically). Populate \`settings\` strictly per the tag's schema; use \`{}\` only when it has none.
|
|
52
|
-
|
|
53
|
-
Note about configuration ids: These names are visible to the end-user, make sure they make sense, related and relevant.
|
|
54
|
-
|
|
55
|
-
# DESIGN PHILOSOPHY: CONTEXT-DRIVEN CREATIVITY
|
|
56
|
-
|
|
57
|
-
**Use the user's context aggressively.** Business type, brand personality, target audience, and purpose should drive every design decision. A law firm needs gravitas; a children's app needs playfulness. Don't default to generic.
|
|
58
|
-
|
|
59
|
-
## SIZING: DEFAULT IS NO SIZE (CRITICAL)
|
|
60
|
-
|
|
61
|
-
**DO NOT specify height or width unless you have a specific visual reason.**
|
|
62
|
-
|
|
63
|
-
Flexbox and CSS already handle sizing automatically:
|
|
64
|
-
- Containers grow to fit their content
|
|
65
|
-
- Flex children distribute space via flex properties, not width/height
|
|
66
|
-
- Text elements size to their content
|
|
67
|
-
|
|
68
|
-
WHEN TO SPECIFY SIZE:
|
|
69
|
-
- min-height on ROOT section for viewport-spanning hero (use min-height, NOT height)
|
|
70
|
-
- max-width for contained content areas (e.g., max-width: 60rem)
|
|
71
|
-
- Explicit aspect ratios for media containers
|
|
72
|
-
|
|
73
|
-
NEVER SPECIFY:
|
|
74
|
-
- height on nested containers (causes overflow)
|
|
75
|
-
- width on flex children (use flex-basis or gap instead)
|
|
76
|
-
- 100vh on anything except root-level sections
|
|
77
|
-
- Any size "just to be safe" - if unsure, OMIT IT
|
|
78
|
-
|
|
79
|
-
vh units are VIEWPORT-relative. Nested 100vh inside 100vh = 200vh overflow.
|
|
80
|
-
|
|
81
|
-
GOOD: \`<e-flexbox>content naturally sizes</e-flexbox>\`
|
|
82
|
-
BAD: \`<e-flexbox style="height:100vh"><e-div-block style="height:100vh">overflow</e-div-block></e-flexbox>\`
|
|
83
|
-
|
|
84
|
-
## Layout Variety (Break the Template)
|
|
85
|
-
- AVOID: Full-width 100vh hero → three columns → testimonials → CTA (every AI does this)
|
|
86
|
-
- VARY heights: Use auto-height sections with generous padding (6rem+). Let content breathe
|
|
87
|
-
- VARY widths: Not everything spans full width. Use contained sections (max-width: 960px) mixed with edge-to-edge
|
|
88
|
-
- ASYMMETRIC grids: 2:1, 1:3, offset layouts. Avoid equal column widths
|
|
89
|
-
- Negative space as design element: Large margins create focus and sophistication
|
|
90
|
-
- Break alignment intentionally: Offset headings, overlapping elements, broken grids
|
|
91
|
-
|
|
92
|
-
## Visual Depth & Effects
|
|
93
|
-
- Layer elements: Overlapping cards, text over images, floating elements
|
|
94
|
-
- Subtle shadows with color tint (not pure black): \`box-shadow: 0 20px 60px rgba(<brand-color-here>, 0.15)\`
|
|
95
|
-
- Gradient overlays on images for text readability
|
|
96
|
-
- Border radius variation: Mix sharp (0) and soft (1rem+) corners purposefully
|
|
97
|
-
- Backdrop blur for glassmorphism where appropriate
|
|
98
|
-
- Micro-interactions via CSS: hover transforms, transitions (0.3s ease)
|
|
99
|
-
|
|
100
|
-
## Typography with Character
|
|
101
|
-
- Display fonts for headlines (from user's brand or contextually appropriate)
|
|
102
|
-
- Size contrast: 4rem+ headlines vs 1rem body. Make hierarchy unmistakable
|
|
103
|
-
- Letter-spacing: Tight for large headlines (-0.02em), loose for small caps (0.1em)
|
|
104
|
-
- Line-height: Tight for headlines (1.1), generous for body (1.6-1.8)
|
|
105
|
-
- Text decoration: Underlines, highlights, gradient text for emphasis
|
|
106
|
-
|
|
107
|
-
## Color with Purpose
|
|
108
|
-
- Extract palette from user context (brand colors, industry norms, mood)
|
|
109
|
-
- 60-30-10 rule: dominant, secondary, accent
|
|
110
|
-
- Tinted neutrals over pure grays: warm (#faf8f5, #2d2a26) or cool (#f5f7fa, #1e2430)
|
|
111
|
-
- Color blocking: Large colored sections create visual rhythm
|
|
112
|
-
- Gradient directions: Diagonal (135deg, 225deg) feel more dynamic than vertical
|
|
113
|
-
|
|
114
|
-
## Spacing Strategy
|
|
115
|
-
- Section padding: 6rem-10rem vertical, creating breathing room
|
|
116
|
-
- Rhythm variation: Tight groups (2rem) with generous gaps between (6rem)
|
|
117
|
-
- Use rem/em exclusively for responsive scaling
|
|
118
|
-
- Generous padding on CTAs: min 1rem 2.5rem
|
|
119
|
-
|
|
120
|
-
# HARD CONSTRAINTS
|
|
121
|
-
- Variables ONLY from [elementor://global-variables] (others throw errors)
|
|
122
|
-
- Avoid SVG widgets unless assets are pre-uploaded
|
|
123
|
-
- Check \`llm_guidance\` in widget schemas (\`default_styles\`, nesting, required children)
|
|
124
|
-
|
|
125
|
-
# PARAMETERS
|
|
126
|
-
- **xmlStructure**: Valid XML with configuration-id attributes
|
|
127
|
-
- **elementConfig**: configuration-id → widget PropValues
|
|
128
|
-
- **style**: configuration-id → raw CSS declarations (property → value strings; no selectors)
|
|
129
|
-
` );
|
|
130
|
-
|
|
131
|
-
buildCompositionsToolPrompt.example( `
|
|
132
|
-
Section with heading + button (NO explicit heights - content sizes naturally):
|
|
133
|
-
{
|
|
134
|
-
xmlStructure: "<e-flexbox configuration-id="Main Section"><e-heading configuration-id="Section Title"></e-heading><e-button configuration-id="Call to Action"></e-button></e-flexbox>",
|
|
135
|
-
elementConfig: {
|
|
136
|
-
"section1": { "tag": { "$$type": "string", "value": "section" } }
|
|
137
|
-
},
|
|
138
|
-
style: {
|
|
139
|
-
"Section Title": {
|
|
140
|
-
"padding": "6rem 4rem",
|
|
141
|
-
"background": "linear-gradient(135deg, #faf8f5 0%, #f0ebe4 100%)",
|
|
142
|
-
"font-size": "3.5rem",
|
|
143
|
-
"color": "#2d2a26"
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
Note: No height/width specified on any element - flexbox handles layout automatically.
|
|
148
|
-
` );
|
|
149
|
-
|
|
150
|
-
buildCompositionsToolPrompt.parameter(
|
|
151
|
-
'xmlStructure',
|
|
152
|
-
`Valid XML structure with custom elementor tags and configuration-id attributes.`
|
|
153
|
-
);
|
|
154
|
-
|
|
155
|
-
buildCompositionsToolPrompt.parameter( 'elementConfig', `Record mapping configuration IDs to widget PropValues.` );
|
|
156
|
-
|
|
157
|
-
buildCompositionsToolPrompt.parameter(
|
|
158
|
-
'style',
|
|
159
|
-
`Record mapping configuration IDs to raw CSS declarations (property → value strings).`
|
|
160
|
-
);
|
|
161
|
-
|
|
162
|
-
buildCompositionsToolPrompt.instruction(
|
|
163
|
-
`Element IDs in the returned XML represent actual widgets. Use these IDs for subsequent styling or configuration changes.`
|
|
164
|
-
);
|
|
165
|
-
|
|
166
|
-
return buildCompositionsToolPrompt.prompt();
|
|
167
|
-
};
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import { z } from '@elementor/schema';
|
|
2
|
-
|
|
3
|
-
import { WIDGET_SCHEMA_URI } from '../../resources/widgets-schema-resource';
|
|
4
|
-
|
|
5
|
-
export const inputSchema = {
|
|
6
|
-
xmlStructure: z.string().describe( 'The XML structure representing the composition to be built' ),
|
|
7
|
-
elementConfig: z
|
|
8
|
-
.record(
|
|
9
|
-
z.string().describe( 'The configuration id' ),
|
|
10
|
-
z.record(
|
|
11
|
-
z.string().describe( 'property name' ),
|
|
12
|
-
z.any().describe( `The PropValue for the property, refer to ${ WIDGET_SCHEMA_URI }` )
|
|
13
|
-
)
|
|
14
|
-
)
|
|
15
|
-
.describe( 'A record mapping element IDs to their configuration objects. REQUIRED' ),
|
|
16
|
-
style: z
|
|
17
|
-
.record(
|
|
18
|
-
z.string().describe( 'The configuration id' ),
|
|
19
|
-
z.record(
|
|
20
|
-
z.string().describe( 'A CSS property name, e.g. "color", "padding".' ),
|
|
21
|
-
z.string().describe( 'A CSS value, e.g. "6rem 4rem", "#2d2a26".' )
|
|
22
|
-
)
|
|
23
|
-
)
|
|
24
|
-
.describe(
|
|
25
|
-
'A record mapping element configuration IDs to their raw CSS declarations (property→value). Converted to native styles server-side; any declaration that cannot be converted is stored as the element custom CSS.'
|
|
26
|
-
)
|
|
27
|
-
.default( {} ),
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
export const outputSchema = {
|
|
31
|
-
errors: z.string().describe( 'Error message if the composition building failed' ).optional(),
|
|
32
|
-
xmlStructure: z
|
|
33
|
-
.string()
|
|
34
|
-
.describe(
|
|
35
|
-
'The built XML structure as a string. Must use this XML after completion of building the composition, it contains real IDs.'
|
|
36
|
-
)
|
|
37
|
-
.optional(),
|
|
38
|
-
llm_instructions: z
|
|
39
|
-
.string()
|
|
40
|
-
.describe( 'Instructions what to do next, Important to follow these instructions!' )
|
|
41
|
-
.optional(),
|
|
42
|
-
};
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
import { type V1ElementConfig } from '@elementor/editor-elements';
|
|
2
|
-
|
|
3
|
-
export const DIV_BLOCK_TAG = 'e-div-block';
|
|
4
|
-
|
|
5
|
-
export const ZERO_SPACING = {
|
|
6
|
-
$$type: 'size',
|
|
7
|
-
value: {
|
|
8
|
-
size: {
|
|
9
|
-
$$type: 'number',
|
|
10
|
-
value: 0,
|
|
11
|
-
},
|
|
12
|
-
unit: {
|
|
13
|
-
$$type: 'string',
|
|
14
|
-
value: 'px',
|
|
15
|
-
},
|
|
16
|
-
},
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
export type BuildCompositionParams = {
|
|
20
|
-
xmlStructure: string;
|
|
21
|
-
stylesConfig: Record< string, Record< string, unknown > >;
|
|
22
|
-
widgetsCache: Record< string, V1ElementConfig >;
|
|
23
|
-
elementConfig: Record< string, Record< string, unknown > >;
|
|
24
|
-
[ key: string ]: unknown;
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
export function adaptLeafRootParams< T extends BuildCompositionParams >( params: T ): T {
|
|
28
|
-
const doc = new DOMParser().parseFromString( params.xmlStructure, 'application/xml' );
|
|
29
|
-
const rootElement = doc.documentElement;
|
|
30
|
-
|
|
31
|
-
if ( ! isLeafWidget( rootElement.tagName, params.widgetsCache ) ) {
|
|
32
|
-
return params;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const wrapperConfigId = getDivBlockWrapperConfigId( params.widgetsCache );
|
|
36
|
-
|
|
37
|
-
return {
|
|
38
|
-
...params,
|
|
39
|
-
xmlStructure: serializeWrapped( doc, rootElement, wrapperConfigId ),
|
|
40
|
-
stylesConfig: {
|
|
41
|
-
...params.stylesConfig,
|
|
42
|
-
[ wrapperConfigId ]: {
|
|
43
|
-
margin: ZERO_SPACING,
|
|
44
|
-
padding: ZERO_SPACING,
|
|
45
|
-
...params.stylesConfig[ wrapperConfigId ],
|
|
46
|
-
},
|
|
47
|
-
},
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function getDivBlockWrapperConfigId( widgetsCache: Record< string, V1ElementConfig > ): string {
|
|
52
|
-
return widgetsCache[ DIV_BLOCK_TAG ]?.title ?? DIV_BLOCK_TAG;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function isLeafWidget( tagName: string, widgetsCache: Record< string, V1ElementConfig > ): boolean {
|
|
56
|
-
return widgetsCache[ tagName ]?.elType === 'widget';
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function serializeWrapped( doc: Document, rootElement: Element, wrapperConfigId: string ): string {
|
|
60
|
-
const wrapper = doc.createElement( DIV_BLOCK_TAG );
|
|
61
|
-
wrapper.setAttribute( 'configuration-id', wrapperConfigId );
|
|
62
|
-
wrapper.appendChild( rootElement.cloneNode( true ) );
|
|
63
|
-
|
|
64
|
-
const wrappedDoc = new DOMParser().parseFromString( `<${ DIV_BLOCK_TAG } />`, 'application/xml' );
|
|
65
|
-
wrappedDoc.replaceChild( wrapper, wrappedDoc.documentElement );
|
|
66
|
-
|
|
67
|
-
return new XMLSerializer().serializeToString( wrappedDoc );
|
|
68
|
-
}
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
import { type V1Element } from '@elementor/editor-elements';
|
|
2
|
-
|
|
3
|
-
import { getCompositionTargetContainer } from '../get-composition-target-container';
|
|
4
|
-
|
|
5
|
-
const createMockContainer = ( id: string, children?: V1Element[] ): V1Element =>
|
|
6
|
-
( {
|
|
7
|
-
id,
|
|
8
|
-
model: { get: jest.fn(), set: jest.fn(), toJSON: jest.fn() },
|
|
9
|
-
settings: { get: jest.fn(), set: jest.fn(), toJSON: jest.fn() },
|
|
10
|
-
children,
|
|
11
|
-
} ) as unknown as V1Element;
|
|
12
|
-
|
|
13
|
-
describe( 'getCompositionTargetContainer', () => {
|
|
14
|
-
it( 'should return first child when document type is elementor_component', () => {
|
|
15
|
-
// Arrange
|
|
16
|
-
const firstChild = createMockContainer( 'child-1' );
|
|
17
|
-
const documentContainer = createMockContainer( 'document', [ firstChild ] );
|
|
18
|
-
|
|
19
|
-
// Act
|
|
20
|
-
const result = getCompositionTargetContainer( documentContainer, 'elementor_component' );
|
|
21
|
-
|
|
22
|
-
// Assert
|
|
23
|
-
expect( result ).toBe( firstChild );
|
|
24
|
-
} );
|
|
25
|
-
|
|
26
|
-
it( 'should return document container when document type is not a component', () => {
|
|
27
|
-
// Arrange
|
|
28
|
-
const firstChild = createMockContainer( 'child-1' );
|
|
29
|
-
const documentContainer = createMockContainer( 'document', [ firstChild ] );
|
|
30
|
-
|
|
31
|
-
// Act
|
|
32
|
-
const result = getCompositionTargetContainer( documentContainer, 'page' );
|
|
33
|
-
|
|
34
|
-
// Assert
|
|
35
|
-
expect( result ).toBe( documentContainer );
|
|
36
|
-
} );
|
|
37
|
-
|
|
38
|
-
it( 'should return document container when document type is undefined', () => {
|
|
39
|
-
// Arrange
|
|
40
|
-
const documentContainer = createMockContainer( 'document' );
|
|
41
|
-
|
|
42
|
-
// Act
|
|
43
|
-
const result = getCompositionTargetContainer( documentContainer, undefined );
|
|
44
|
-
|
|
45
|
-
// Assert
|
|
46
|
-
expect( result ).toBe( documentContainer );
|
|
47
|
-
} );
|
|
48
|
-
|
|
49
|
-
it( 'should return document container when component has no children', () => {
|
|
50
|
-
// Arrange
|
|
51
|
-
const documentContainer = createMockContainer( 'document' );
|
|
52
|
-
|
|
53
|
-
// Act
|
|
54
|
-
const result = getCompositionTargetContainer( documentContainer, 'elementor_component' );
|
|
55
|
-
|
|
56
|
-
// Assert
|
|
57
|
-
expect( result ).toBe( documentContainer );
|
|
58
|
-
} );
|
|
59
|
-
} );
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { COMPONENT_DOCUMENT_TYPE } from '@elementor/editor-documents';
|
|
2
|
-
import { type V1Element } from '@elementor/editor-elements';
|
|
3
|
-
|
|
4
|
-
export function getCompositionTargetContainer(
|
|
5
|
-
documentContainer: V1Element,
|
|
6
|
-
documentType: string | undefined
|
|
7
|
-
): V1Element {
|
|
8
|
-
const firstChild = documentContainer.children?.[ 0 ];
|
|
9
|
-
|
|
10
|
-
if ( documentType === COMPONENT_DOCUMENT_TYPE && firstChild ) {
|
|
11
|
-
return firstChild;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
return documentContainer;
|
|
15
|
-
}
|