@elementor/editor-canvas 4.3.0-999 → 4.4.0-1065

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 (49) hide show
  1. package/dist/index.d.mts +3 -1
  2. package/dist/index.d.ts +3 -1
  3. package/dist/index.js +457 -371
  4. package/dist/index.mjs +438 -348
  5. package/package.json +20 -20
  6. package/src/init-settings-transformers.ts +4 -0
  7. package/src/init-style-transformers.ts +2 -2
  8. package/src/init.tsx +5 -4
  9. package/src/legacy/__tests__/create-templated-element-type.test.ts +50 -0
  10. package/src/legacy/__tests__/list-type.test.ts +89 -0
  11. package/src/legacy/create-nested-templated-element-type.ts +3 -1
  12. package/src/legacy/create-templated-element-type.ts +5 -2
  13. package/src/legacy/list-type.ts +63 -0
  14. package/src/legacy/replacements/inline-editing/__tests__/inline-editing-eligibility.test.ts +7 -7
  15. package/src/legacy/replacements/inline-editing/inline-editing-elements.tsx +22 -9
  16. package/src/legacy/replacements/inline-editing/inline-editing-eligibility.ts +12 -3
  17. package/src/legacy/tabs-model-extensions.ts +2 -5
  18. package/src/legacy/twig-rendering-utils.ts +11 -2
  19. package/src/legacy/types.ts +4 -0
  20. package/src/mcp/canvas-mcp.ts +2 -4
  21. package/src/mcp/mcp-description.ts +18 -30
  22. package/src/mcp/resources/__tests__/available-widgets-resource.test.ts +22 -13
  23. package/src/mcp/resources/__tests__/dynamic-tags-resource.test.ts +22 -30
  24. package/src/mcp/resources/__tests__/widgets-schema-resource.test.ts +11 -6
  25. package/src/mcp/resources/available-widgets-resource.ts +13 -10
  26. package/src/mcp/resources/dynamic-tags-resource.ts +6 -15
  27. package/src/mcp/resources/widgets-schema-resource.ts +8 -5
  28. package/src/mcp/tools/configure-element/__tests__/tool.test.ts +130 -0
  29. package/src/mcp/tools/configure-element/prompt.ts +3 -6
  30. package/src/mcp/tools/configure-element/schema.ts +6 -0
  31. package/src/mcp/tools/configure-element/tool.ts +11 -1
  32. package/src/mcp/tools/get-page-structure/tool.ts +73 -0
  33. package/src/mcp/utils/__tests__/do-update-element-property.test.ts +27 -1
  34. package/src/mcp/utils/do-update-element-property.ts +18 -6
  35. package/src/mcp/utils/get-mcp-error-message.ts +16 -0
  36. package/src/renderers/__tests__/compute-html-tag.test.ts +55 -0
  37. package/src/renderers/__tests__/create-dom-renderer.test.ts +60 -0
  38. package/src/renderers/__tests__/fixtures/html-tag-computer-cases.json +87 -0
  39. package/src/renderers/compute-html-tag.ts +73 -0
  40. package/src/renderers/create-dom-renderer.ts +13 -22
  41. package/src/transformers/settings/escaped-html-transformer.ts +6 -0
  42. package/src/transformers/shared/__tests__/icon-transformer.test.ts +74 -0
  43. package/src/transformers/shared/icon-transformer.ts +122 -0
  44. package/src/transformers/shared/process-svg-content.ts +26 -0
  45. package/src/transformers/shared/svg-src-transformer.ts +1 -26
  46. package/src/utils/__tests__/sanitize-escaped-html.test.ts +105 -0
  47. package/src/utils/sanitize-escaped-html.ts +32 -0
  48. package/src/mcp/tools/build-composition/tool.ts +0 -133
  49. package/src/mcp/tools/get-element-config/tool.ts +0 -114
@@ -0,0 +1,122 @@
1
+ import { createTransformer } from '../create-transformer';
2
+ import type { TransformerOptions } from '../types';
3
+ import { processSvgContent } from './process-svg-content';
4
+
5
+ type IconValue = {
6
+ value?: unknown;
7
+ library?: unknown;
8
+ };
9
+
10
+ type FontAwesomeIconJson = [ number, number, unknown, unknown, string ];
11
+
12
+ const FONT_AWESOME_JSON = {
13
+ width: 0,
14
+ height: 1,
15
+ path: 4,
16
+ } as const;
17
+
18
+ const fontAwesomeJsonCache = new Map< string, Record< string, FontAwesomeIconJson > >();
19
+
20
+ export const iconTransformer = createTransformer( async ( value: IconValue, { signal }: TransformerOptions ) => {
21
+ const iconValue = typeof value.value === 'string' ? value.value : null;
22
+ const library = typeof value.library === 'string' ? value.library : null;
23
+
24
+ if ( ! iconValue || ! library ) {
25
+ return { html: null, url: null };
26
+ }
27
+
28
+ const iconName = getFontAwesomeIconName( iconValue );
29
+ const jsonFileName = getFontAwesomeJsonFileName( library );
30
+
31
+ if ( ! iconName || ! jsonFileName ) {
32
+ return { html: null, url: null };
33
+ }
34
+
35
+ const icons = await fetchFontAwesomeIcons( jsonFileName, signal );
36
+ const iconData = icons?.[ iconName ];
37
+
38
+ if ( ! iconData ) {
39
+ return { html: null, url: null };
40
+ }
41
+
42
+ const svgText = buildFontAwesomeSvg( iconData );
43
+ const html = processSvgContent( svgText );
44
+
45
+ return { html, url: null };
46
+ } );
47
+
48
+ function getFontAwesomeIconName( iconValue: string ): string | null {
49
+ const match = iconValue.match( /^fa\S*\s+fa-(.+)$/ );
50
+
51
+ return match?.[ 1 ] ?? null;
52
+ }
53
+
54
+ function getFontAwesomeJsonFileName( library: string ): string | null {
55
+ if ( ! library.startsWith( 'fa-' ) ) {
56
+ return null;
57
+ }
58
+
59
+ return library.replace( /^fa-/, '' );
60
+ }
61
+
62
+ function getAssetsBaseUrl(): string | null {
63
+ const assetsUrl = window.elementorCommon?.config?.urls?.assets;
64
+
65
+ return typeof assetsUrl === 'string' && assetsUrl !== '' ? assetsUrl : null;
66
+ }
67
+
68
+ async function fetchFontAwesomeIcons(
69
+ jsonFileName: string,
70
+ signal?: AbortSignal
71
+ ): Promise< Record< string, FontAwesomeIconJson > | null > {
72
+ const cached = fontAwesomeJsonCache.get( jsonFileName );
73
+
74
+ if ( cached ) {
75
+ return cached;
76
+ }
77
+
78
+ const icons = await loadFontAwesomeIcons( jsonFileName, signal );
79
+
80
+ if ( icons ) {
81
+ fontAwesomeJsonCache.set( jsonFileName, icons );
82
+ }
83
+
84
+ return icons;
85
+ }
86
+
87
+ async function loadFontAwesomeIcons(
88
+ jsonFileName: string,
89
+ signal?: AbortSignal
90
+ ): Promise< Record< string, FontAwesomeIconJson > | null > {
91
+ const assetsUrl = getAssetsBaseUrl();
92
+
93
+ if ( ! assetsUrl ) {
94
+ return null;
95
+ }
96
+
97
+ try {
98
+ const response = await fetch( `${ assetsUrl }lib/font-awesome/json/${ jsonFileName }.json`, { signal } );
99
+
100
+ if ( ! response.ok ) {
101
+ return null;
102
+ }
103
+
104
+ const data = ( await response.json() ) as { icons?: Record< string, FontAwesomeIconJson > };
105
+
106
+ return data.icons ?? null;
107
+ } catch {
108
+ return null;
109
+ }
110
+ }
111
+
112
+ function buildFontAwesomeSvg( iconData: FontAwesomeIconJson ): string {
113
+ const width = iconData[ FONT_AWESOME_JSON.width ];
114
+ const height = iconData[ FONT_AWESOME_JSON.height ];
115
+ const path = iconData[ FONT_AWESOME_JSON.path ];
116
+
117
+ return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${ width } ${ height }"><path d="${ path }"></path></svg>`;
118
+ }
119
+
120
+ export function resetFontAwesomeIconsCache() {
121
+ fontAwesomeJsonCache.clear();
122
+ }
@@ -0,0 +1,26 @@
1
+ import DOMPurify from 'dompurify';
2
+
3
+ const SVG_INLINE_STYLES = 'width: 100%; height: 100%; overflow: unset;';
4
+
5
+ export function processSvgContent( svgText: string ): string | null {
6
+ const sanitized = DOMPurify.sanitize( svgText, {
7
+ USE_PROFILES: { svg: true, svgFilters: true },
8
+ } );
9
+
10
+ const parser = new DOMParser();
11
+ const doc = parser.parseFromString( sanitized, 'image/svg+xml' );
12
+ const svgElement = doc.querySelector( 'svg' );
13
+
14
+ if ( ! svgElement ) {
15
+ return null;
16
+ }
17
+
18
+ svgElement.setAttribute( 'fill', 'currentColor' );
19
+
20
+ const existingStyle = svgElement.getAttribute( 'style' ) ?? '';
21
+ const trimmed = existingStyle.trim();
22
+ const merged = trimmed ? `${ trimmed.replace( /;$/, '' ) }; ${ SVG_INLINE_STYLES }` : SVG_INLINE_STYLES;
23
+ svgElement.setAttribute( 'style', merged );
24
+
25
+ return svgElement.outerHTML;
26
+ }
@@ -1,39 +1,14 @@
1
- import DOMPurify from 'dompurify';
2
1
  import { getMediaAttachment } from '@elementor/wp-media';
3
2
 
4
3
  import { createTransformer } from '../create-transformer';
5
4
  import type { TransformerOptions } from '../types';
5
+ import { processSvgContent } from './process-svg-content';
6
6
 
7
7
  type SvgSrc = {
8
8
  id?: unknown;
9
9
  url?: unknown;
10
10
  };
11
11
 
12
- const SVG_INLINE_STYLES = 'width: 100%; height: 100%; overflow: unset;';
13
-
14
- function processSvgContent( svgText: string ): string | null {
15
- const sanitized = DOMPurify.sanitize( svgText, {
16
- USE_PROFILES: { svg: true, svgFilters: true },
17
- } );
18
-
19
- const parser = new DOMParser();
20
- const doc = parser.parseFromString( sanitized, 'image/svg+xml' );
21
- const svgElement = doc.querySelector( 'svg' );
22
-
23
- if ( ! svgElement ) {
24
- return null;
25
- }
26
-
27
- svgElement.setAttribute( 'fill', 'currentColor' );
28
-
29
- const existingStyle = svgElement.getAttribute( 'style' ) ?? '';
30
- const trimmed = existingStyle.trim();
31
- const merged = trimmed ? `${ trimmed.replace( /;$/, '' ) }; ${ SVG_INLINE_STYLES }` : SVG_INLINE_STYLES;
32
- svgElement.setAttribute( 'style', merged );
33
-
34
- return svgElement.outerHTML;
35
- }
36
-
37
12
  async function fetchSvgContent( url: string, signal?: AbortSignal ): Promise< string | null > {
38
13
  try {
39
14
  const response = await fetch( url, { signal } );
@@ -0,0 +1,105 @@
1
+ import { sanitizeEscapedHtml } from '../sanitize-escaped-html';
2
+
3
+ const TEST_ALLOWED_HTML_WRAPPER_TAGS = [ 'a', 'div', 'span', 'strong' ];
4
+
5
+ describe( 'sanitizeEscapedHtml', () => {
6
+ beforeEach( () => {
7
+ window.elementorCommon = {
8
+ config: {
9
+ allowedHTMLWrapperTags: TEST_ALLOWED_HTML_WRAPPER_TAGS,
10
+ },
11
+ };
12
+ } );
13
+
14
+ afterEach( () => {
15
+ delete window.elementorCommon;
16
+ } );
17
+
18
+ it( 'returns an empty string for nullish values', () => {
19
+ // Arrange & Act & Assert.
20
+ expect( sanitizeEscapedHtml( null ) ).toBe( '' );
21
+ expect( sanitizeEscapedHtml( '' ) ).toBe( '' );
22
+ } );
23
+
24
+ it( 'keeps allowed tags and strips disallowed tags', () => {
25
+ // Arrange.
26
+ const value = 'Hello <script>alert(1)</script><strong>world</strong>';
27
+
28
+ // Act.
29
+ const result = sanitizeEscapedHtml( value );
30
+
31
+ // Assert.
32
+ expect( result ).toBe( 'Hello <strong>world</strong>' );
33
+ } );
34
+
35
+ it( 'fails closed when the localized allowlist is missing', () => {
36
+ // Arrange.
37
+ delete window.elementorCommon;
38
+ const value = '<strong>world</strong>';
39
+
40
+ // Act.
41
+ const result = sanitizeEscapedHtml( value );
42
+
43
+ // Assert.
44
+ expect( result ).toBe( 'world' );
45
+ } );
46
+
47
+ it( 'strips javascript href from links', () => {
48
+ // Arrange.
49
+ const value = '<a href="javascript:alert(1)">click</a>';
50
+
51
+ // Act.
52
+ const result = sanitizeEscapedHtml( value );
53
+
54
+ // Assert.
55
+ expect( result ).not.toContain( 'javascript:' );
56
+ expect( result ).toContain( 'click' );
57
+ } );
58
+
59
+ it( 'strips data href from links', () => {
60
+ // Arrange.
61
+ const value = '<a href="data:text/html,<script>alert(1)</script>">click</a>';
62
+
63
+ // Act.
64
+ const result = sanitizeEscapedHtml( value );
65
+
66
+ // Assert.
67
+ expect( result ).not.toMatch( /<a\s+href=/i );
68
+ expect( result ).toContain( 'click' );
69
+ } );
70
+
71
+ it( 'preserves non-operational attributes', () => {
72
+ // Arrange.
73
+ const value =
74
+ '<span id="e-1" class="foo bar" style="color: red;" title="t" lang="en" dir="ltr" role="text" data-x="1" aria-label="label">world</span>';
75
+
76
+ // Act.
77
+ const result = sanitizeEscapedHtml( value );
78
+
79
+ // Assert.
80
+ expect( result ).toContain( 'id="e-1"' );
81
+ expect( result ).toContain( 'class="foo bar"' );
82
+ expect( result ).toContain( 'style="color: red;"' );
83
+ expect( result ).toContain( 'title="t"' );
84
+ expect( result ).toContain( 'lang="en"' );
85
+ expect( result ).toContain( 'dir="ltr"' );
86
+ expect( result ).toContain( 'role="text"' );
87
+ expect( result ).toContain( 'data-x="1"' );
88
+ expect( result ).toContain( 'aria-label="label"' );
89
+ expect( result ).toContain( 'world' );
90
+ } );
91
+
92
+ it( 'strips functional event-handler attributes', () => {
93
+ // Arrange.
94
+ const value = '<span id="e-1" onclick="evil()" onmouseover="evil()">world</span>';
95
+
96
+ // Act.
97
+ const result = sanitizeEscapedHtml( value );
98
+
99
+ // Assert.
100
+ expect( result ).toContain( 'id="e-1"' );
101
+ expect( result ).not.toContain( 'onclick' );
102
+ expect( result ).not.toContain( 'onmouseover' );
103
+ expect( result ).toContain( 'world' );
104
+ } );
105
+ } );
@@ -0,0 +1,32 @@
1
+ import DOMPurify from 'dompurify';
2
+
3
+ const ALLOWED_NON_OPERATIONAL_ATTRS = [
4
+ 'href',
5
+ 'target',
6
+ 'class',
7
+ 'id',
8
+ 'style',
9
+ 'title',
10
+ 'lang',
11
+ 'dir',
12
+ 'role',
13
+ ] as const;
14
+
15
+ function getAllowedHtmlWrapperTags(): readonly string[] {
16
+ return window.elementorCommon?.config?.allowedHTMLWrapperTags ?? [];
17
+ }
18
+
19
+ export function sanitizeEscapedHtml( value: string | null ): string {
20
+ if ( ! value ) {
21
+ return '';
22
+ }
23
+
24
+ const allowedTags = [ ...getAllowedHtmlWrapperTags() ];
25
+
26
+ return DOMPurify.sanitize( value, {
27
+ ALLOWED_TAGS: allowedTags,
28
+ ALLOWED_ATTR: [ ...ALLOWED_NON_OPERATIONAL_ATTRS ],
29
+ ALLOW_DATA_ATTR: true,
30
+ ALLOW_ARIA_ATTR: true,
31
+ } );
32
+ }
@@ -1,133 +0,0 @@
1
- import { getCurrentDocument, reloadCurrentDocument } from '@elementor/editor-documents';
2
- import { getContainer, selectElement } from '@elementor/editor-elements';
3
- import { type MCPRegistryEntry } from '@elementor/editor-mcp';
4
- import { AxiosError, type HttpResponse, httpService } from '@elementor/http-client';
5
- import { z } from '@elementor/schema';
6
-
7
- const MCP_PROXY_URL = 'elementor/v1/mcp-proxy';
8
-
9
- type BuildCompositionResponse = {
10
- success: boolean;
11
- post_id: number;
12
- root_element_ids: string[];
13
- preview_url: string;
14
- version: string;
15
- resolved_xml: string;
16
- llm_instructions: string;
17
- warnings?: string[];
18
- };
19
-
20
- export const initBuildCompositionTool = ( reg: MCPRegistryEntry ) => {
21
- const { addTool } = reg;
22
-
23
- addTool( {
24
- name: 'build-composition',
25
- description:
26
- 'Build a V4 element composition on the Elementor canvas via the server-side MCP ability. ' +
27
- 'Pass the raw XML tags directly as xmlStructure — do NOT wrap the value in <![CDATA[ ... ]]>, ' +
28
- 'code fences, or quotes. The document is saved as a draft. Reload the editor after calling ' +
29
- 'this tool to see the result.',
30
- schema: {
31
- xmlStructure: z
32
- .string()
33
- .describe(
34
- 'Valid XML structure with custom Elementor widget tags. Every element MUST have a unique ' +
35
- 'configuration-id attribute (e.g. <e-heading configuration-id="hero-title"></e-heading>). ' +
36
- 'No attributes, classes, IDs, or text nodes in XML. Pass raw XML — do not wrap in CDATA.'
37
- ),
38
- elementConfig: z
39
- .record(
40
- z.string().describe( 'configuration-id' ),
41
- z.record( z.string().describe( 'property name' ), z.any().describe( 'PropValue' ) )
42
- )
43
- .optional()
44
- .describe( 'Map configuration-id → widget PropValues ($$type + value).' ),
45
- style: z
46
- .record(
47
- z.string().describe( 'configuration-id' ),
48
- z.record( z.string().describe( 'CSS property name' ), z.string().describe( 'CSS value' ) )
49
- )
50
- .optional()
51
- .describe(
52
- 'Map configuration-id → raw CSS declarations (property → value strings; no selectors). ' +
53
- 'Server converts to native styles; unconvertible declarations become the element custom CSS.'
54
- ),
55
- parentId: z
56
- .string()
57
- .optional()
58
- .describe( "ID of the parent container. Omit or pass 'document' to insert at document root." ),
59
- dryRun: z
60
- .boolean()
61
- .optional()
62
- .describe( 'If true, validate and return the resolved tree without persisting.' ),
63
- },
64
- outputSchema: {
65
- rootElementIds: z.array( z.string() ),
66
- previewUrl: z.string(),
67
- version: z.string(),
68
- resolvedXml: z.string(),
69
- llmInstructions: z.string(),
70
- warnings: z.array( z.string() ).optional(),
71
- },
72
- handler: async ( { xmlStructure, elementConfig, style, parentId, dryRun } ) => {
73
- const document = getCurrentDocument();
74
-
75
- if ( ! document?.id ) {
76
- throw new Error( 'No active document found.' );
77
- }
78
-
79
- try {
80
- const { data } = await httpService().post< HttpResponse< BuildCompositionResponse > >( MCP_PROXY_URL, {
81
- tool: 'build-composition',
82
- input: {
83
- post_id: document.id,
84
- xml_structure: xmlStructure,
85
- element_config: elementConfig ?? {},
86
- style: style ?? {},
87
- parent_id: parentId ?? 'document',
88
- dry_run: dryRun ?? false,
89
- },
90
- } );
91
-
92
- if ( ! dryRun ) {
93
- await reloadCurrentDocument();
94
-
95
- const [ firstRootId ] = data.data.root_element_ids;
96
- if ( firstRootId ) {
97
- selectElement( firstRootId );
98
- getContainer( firstRootId )?.view?.el?.scrollIntoView( {
99
- behavior: 'smooth',
100
- block: 'center',
101
- } );
102
- }
103
- }
104
-
105
- return {
106
- rootElementIds: data.data.root_element_ids,
107
- previewUrl: data.data.preview_url,
108
- version: data.data.version,
109
- resolvedXml: data.data.resolved_xml,
110
- llmInstructions: data.data.llm_instructions,
111
- warnings: data.data.warnings,
112
- };
113
- } catch ( error ) {
114
- throw new Error( getErrorMessage( error ) );
115
- }
116
- },
117
- } );
118
- };
119
-
120
- function getErrorMessage( error: unknown ): string {
121
- if ( error instanceof AxiosError ) {
122
- const data = error.response?.data as { message?: string; code?: string } | undefined;
123
- if ( data?.message ) {
124
- return data.code ? `${ data.code }: ${ data.message }` : data.message;
125
- }
126
- }
127
-
128
- if ( error instanceof Error ) {
129
- return error.message;
130
- }
131
-
132
- return 'build-composition failed with an unknown error.';
133
- }
@@ -1,114 +0,0 @@
1
- import { getContainer, getElementStyles, getWidgetsCache, type V1Element } from '@elementor/editor-elements';
2
- import { type MCPRegistryEntry } from '@elementor/editor-mcp';
3
- import { type PropValue, Schema } from '@elementor/editor-props';
4
- import { z } from '@elementor/schema';
5
-
6
- const schema = {
7
- elementId: z.string(),
8
- };
9
-
10
- const outputSchema = {
11
- properties: z
12
- .record( z.string(), z.any() )
13
- .describe( 'A record mapping PropTypes to their corresponding PropValues' ),
14
- style: z
15
- .record( z.string(), z.any() )
16
- .describe( 'A record mapping StyleSchema properties to their corresponding PropValues' ),
17
- childElements: z
18
- .array(
19
- z.object( {
20
- id: z.string(),
21
- elementType: z.string(),
22
- childElements: z
23
- .array( z.any() )
24
- .describe( 'An array of child element IDs, when applicable, same structure recursively' ),
25
- } )
26
- )
27
- .describe( 'An array of child element IDs, when applicable, with recursive structure' ),
28
- };
29
- type ElementStructure = {
30
- id: string;
31
- elementType: string;
32
- childElements: ElementStructure[];
33
- };
34
- const structuredElements = ( element: V1Element ): ElementStructure[] => {
35
- const children = element.children || [];
36
- return children.map( ( child ) => {
37
- return {
38
- id: child.id,
39
- elementType: child.model.get( 'elType' ) || child.model.get( 'widgetType' ) || 'unknown',
40
- childElements: structuredElements( child ),
41
- };
42
- } );
43
- };
44
-
45
- export const initGetElementConfigTool = ( reg: MCPRegistryEntry ) => {
46
- const { addTool } = reg;
47
-
48
- addTool( {
49
- name: 'get-element-configuration-values',
50
- description: "Retrieve the element's configuration PropValues for a specific element by unique ID.",
51
- schema,
52
- outputSchema,
53
- handler: async ( { elementId } ) => {
54
- const element = getContainer( elementId );
55
- if ( ! element ) {
56
- throw new Error( `Element with ID ${ elementId } not found.` );
57
- }
58
- const elementType = element.model.get( 'widgetType' ) || element.model.get( 'elType' ) || '';
59
- const widgetData = getWidgetsCache()?.[ elementType ];
60
- if ( ! widgetData ) {
61
- throw new Error(
62
- `Unknown element type: ${ elementType }. Check the available-widgets resource for valid types.`
63
- );
64
- }
65
- if ( ! widgetData.atomic_props_schema ) {
66
- throw new Error(
67
- `This tool does not support V3 elements. Please use the elementor-v3-mcp tools instead for element type: ${ elementType }`
68
- );
69
- }
70
- const elementRawSettings = element.settings;
71
- const propSchema = getWidgetsCache()?.[ elementType ]?.atomic_props_schema;
72
-
73
- if ( ! elementRawSettings || ! propSchema ) {
74
- throw new Error( `No settings or prop schema found for element ID: ${ elementId }` );
75
- }
76
-
77
- const propValues: Record< string, PropValue > = {};
78
- const stylePropValues: Record< string, PropValue > = {};
79
-
80
- Schema.configurableKeys( propSchema ).forEach( ( key ) => {
81
- propValues[ key ] = structuredClone( elementRawSettings.get( key ) );
82
- } );
83
- const elementStyles = getElementStyles( elementId ) || {};
84
- const localStyle = Object.values( elementStyles ).find( ( style ) => style.label === 'local' );
85
-
86
- if ( localStyle ) {
87
- const defaultVariant = localStyle.variants.find(
88
- ( variant ) => variant.meta.breakpoint === 'desktop' && ! variant.meta.state
89
- );
90
- if ( defaultVariant ) {
91
- const styleProps = defaultVariant.props || {};
92
- Object.keys( styleProps ).forEach( ( stylePropName ) => {
93
- if ( typeof styleProps[ stylePropName ] !== 'undefined' ) {
94
- stylePropValues[ stylePropName ] = structuredClone( styleProps[ stylePropName ] );
95
- }
96
- } );
97
- if ( defaultVariant.custom_css ) {
98
- stylePropValues.custom_css = atob( defaultVariant.custom_css.raw );
99
- }
100
- }
101
- }
102
-
103
- return {
104
- properties: {
105
- ...propValues,
106
- },
107
- style: {
108
- ...stylePropValues,
109
- },
110
- childElements: structuredElements( element ),
111
- };
112
- },
113
- } );
114
- };