@elementor/editor-global-classes 4.3.0-1001 → 4.3.0-1002

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elementor/editor-global-classes",
3
- "version": "4.3.0-1001",
3
+ "version": "4.3.0-1002",
4
4
  "private": false,
5
5
  "author": "Elementor Team",
6
6
  "homepage": "https://elementor.com/",
@@ -39,30 +39,30 @@
39
39
  "dev": "tsup --config=../../tsup.dev.ts"
40
40
  },
41
41
  "dependencies": {
42
- "@elementor/editor": "4.3.0-1001",
43
- "@elementor/editor-current-user": "4.3.0-1001",
44
- "@elementor/editor-documents": "4.3.0-1001",
45
- "@elementor/editor-embedded-documents-manager": "4.3.0-1001",
46
- "@elementor/editor-editing-panel": "4.3.0-1001",
47
- "@elementor/editor-mcp": "4.3.0-1001",
48
- "@elementor/editor-panels": "4.3.0-1001",
49
- "@elementor/editor-props": "4.3.0-1001",
50
- "@elementor/editor-variables": "4.3.0-1001",
51
- "@elementor/editor-styles": "4.3.0-1001",
52
- "@elementor/editor-canvas": "4.3.0-1001",
53
- "@elementor/editor-styles-repository": "4.3.0-1001",
54
- "@elementor/editor-ui": "4.3.0-1001",
55
- "@elementor/editor-v1-adapters": "4.3.0-1001",
56
- "@elementor/http-client": "4.3.0-1001",
42
+ "@elementor/editor": "4.3.0-1002",
43
+ "@elementor/editor-current-user": "4.3.0-1002",
44
+ "@elementor/editor-documents": "4.3.0-1002",
45
+ "@elementor/editor-embedded-documents-manager": "4.3.0-1002",
46
+ "@elementor/editor-editing-panel": "4.3.0-1002",
47
+ "@elementor/editor-mcp": "4.3.0-1002",
48
+ "@elementor/editor-panels": "4.3.0-1002",
49
+ "@elementor/editor-props": "4.3.0-1002",
50
+ "@elementor/editor-variables": "4.3.0-1002",
51
+ "@elementor/editor-styles": "4.3.0-1002",
52
+ "@elementor/editor-canvas": "4.3.0-1002",
53
+ "@elementor/editor-styles-repository": "4.3.0-1002",
54
+ "@elementor/editor-ui": "4.3.0-1002",
55
+ "@elementor/editor-v1-adapters": "4.3.0-1002",
56
+ "@elementor/http-client": "4.3.0-1002",
57
57
  "@elementor/icons": "~1.75.1",
58
- "@elementor/query": "4.3.0-1001",
59
- "@elementor/schema": "4.3.0-1001",
60
- "@elementor/store": "4.3.0-1001",
58
+ "@elementor/query": "4.3.0-1002",
59
+ "@elementor/schema": "4.3.0-1002",
60
+ "@elementor/store": "4.3.0-1002",
61
61
  "@elementor/ui": "1.37.5",
62
- "@elementor/utils": "4.3.0-1001",
62
+ "@elementor/utils": "4.3.0-1002",
63
63
  "@tanstack/react-virtual": "^3.13.24",
64
64
  "@wordpress/i18n": "^5.13.0",
65
- "@elementor/events": "4.3.0-1001"
65
+ "@elementor/events": "4.3.0-1002"
66
66
  },
67
67
  "peerDependencies": {
68
68
  "react": "^18.3.1",
@@ -1,13 +1,13 @@
1
1
  import { type MCPRegistryEntry } from '@elementor/editor-mcp';
2
2
 
3
3
  import { initClassesResource } from './classes-resource';
4
+ import { initManageClassesTool } from './manage-classes-tool';
4
5
  import initMcpApplyUnapplyGlobalClasses from './mcp-apply-unapply-global-classes';
5
6
  import initMcpApplyGetGlobalClassUsages from './mcp-get-global-class-usages';
6
- import { initManageGlobalClasses } from './mcp-manage-global-classes';
7
7
 
8
8
  export const initMcpIntegration = ( reg: MCPRegistryEntry, canvasMcpEntry: MCPRegistryEntry ) => {
9
9
  initMcpApplyUnapplyGlobalClasses( reg );
10
10
  initMcpApplyGetGlobalClassUsages( reg );
11
- initManageGlobalClasses( reg );
11
+ initManageClassesTool( reg );
12
12
  initClassesResource( reg, canvasMcpEntry );
13
13
  };
@@ -0,0 +1,85 @@
1
+ import { type MCPRegistryEntry } from '@elementor/editor-mcp';
2
+ import { type StyleDefinition } from '@elementor/editor-styles';
3
+ import { type HttpResponse, httpService } from '@elementor/http-client';
4
+ import { z } from '@elementor/schema';
5
+ import { __dispatch as dispatch } from '@elementor/store';
6
+
7
+ import { globalClassesStylesProvider } from '../global-classes-styles-provider';
8
+ import { slice } from '../store';
9
+ import { GLOBAL_CLASSES_URI } from './classes-resource';
10
+
11
+ const MCP_PROXY_URL = 'elementor/v1/mcp-proxy';
12
+ const TOOL_NAME = 'manage-classes';
13
+
14
+ type ManageClassesResponse = {
15
+ status: string;
16
+ class?: StyleDefinition;
17
+ order?: string[];
18
+ };
19
+
20
+ export const initManageClassesTool = ( reg: MCPRegistryEntry ) => {
21
+ const { addTool } = reg;
22
+
23
+ addTool( {
24
+ name: TOOL_NAME,
25
+ description:
26
+ 'Manage V4 global CSS classes on the active kit. Create, update, or delete a single class using raw CSS declarations.',
27
+ schema: {
28
+ action: z.enum( [ 'create', 'update', 'delete' ] ),
29
+ id: z
30
+ .string()
31
+ .optional()
32
+ .describe( 'Class id — required for update/delete. Get from the global-classes resource.' ),
33
+ label: z
34
+ .string()
35
+ .optional()
36
+ .describe( 'Class label (lowercase, dash-separated) — required for create/update.' ),
37
+ css: z
38
+ .record( z.string() )
39
+ .optional()
40
+ .describe( 'Raw CSS declarations (property → value) — required for create/update.' ),
41
+ },
42
+ outputSchema: {
43
+ status: z.enum( [ 'ok' ] ).describe( 'Operation status' ),
44
+ },
45
+ requiredResources: [
46
+ {
47
+ uri: GLOBAL_CLASSES_URI,
48
+ description: 'Current global classes — check before creating to avoid duplicates',
49
+ },
50
+ ],
51
+ isDestructive: true,
52
+ handler: async ( params ) => {
53
+ const { data } = await httpService().post< HttpResponse< ManageClassesResponse > >( MCP_PROXY_URL, {
54
+ tool: TOOL_NAME,
55
+ input: params,
56
+ } );
57
+
58
+ const payload = data.data;
59
+ const { create, update, delete: del } = globalClassesStylesProvider.actions;
60
+
61
+ switch ( params.action ) {
62
+ case 'create':
63
+ if ( payload.class && create ) {
64
+ create( payload.class.label, payload.class.variants, payload.class.id );
65
+ }
66
+ break;
67
+ case 'update':
68
+ if ( payload.class && update ) {
69
+ update( payload.class );
70
+ }
71
+ break;
72
+ case 'delete':
73
+ if ( params.id && del ) {
74
+ del( params.id );
75
+ }
76
+ break;
77
+ }
78
+
79
+ dispatch( slice.actions.reset( { context: 'frontend' } ) );
80
+ window.dispatchEvent( new CustomEvent( 'classes:updated', { detail: { context: 'frontend' } } ) );
81
+
82
+ return { status: 'ok' };
83
+ },
84
+ } );
85
+ };
@@ -1,314 +0,0 @@
1
- import { BREAKPOINTS_SCHEMA_FULL_URI, convertStyleBlocksToAtomic, type StyleBlock } from '@elementor/editor-canvas';
2
- import { dispatchMcpStylesAppliedEvent } from '@elementor/editor-mcp';
3
- import { type MCPRegistryEntry } from '@elementor/editor-mcp';
4
- import { type Props } from '@elementor/editor-props';
5
- import { type BreakpointId } from '@elementor/editor-responsive';
6
- import { type CustomCss, type StyleDefinitionState } from '@elementor/editor-styles';
7
- import { type StylesProvider } from '@elementor/editor-styles-repository';
8
- import { z } from '@elementor/schema';
9
-
10
- import { globalClassesStylesProvider } from '../global-classes-styles-provider';
11
- import { loadExistingClasses } from '../load-existing-classes';
12
- import { saveGlobalClasses } from '../save-global-classes';
13
- import { GLOBAL_CLASSES_URI } from './classes-resource';
14
-
15
- const schema = {
16
- action: z.enum( [ 'create', 'modify', 'delete' ] ).describe( 'Operation to perform' ),
17
- classId: z
18
- .string()
19
- .optional()
20
- .describe( 'Global class ID (required for modify). Get from elementor://global-classes resource.' ),
21
- globalClassName: z.string().optional().describe( 'Global class name (required for create)' ),
22
- style: z
23
- .object( {
24
- default: z
25
- .string()
26
- .describe( 'Plaintext CSS for the default state. MUST be non-empty — blank strings are rejected.' ),
27
- hover: z.string().describe( 'Plaintext CSS for the :hover state. optional' ).optional(),
28
- focus: z.string().describe( 'Plaintext CSS for the :focus state. optional' ).optional(),
29
- active: z.string().describe( 'Plaintext CSS for the :active state. optional' ).optional(),
30
- } )
31
- .describe(
32
- 'Plaintext CSS per pseudo-state. All states are converted in one bulk request; unconvertible declarations are stored as custom CSS.'
33
- ),
34
- breakpoint: z
35
- .nullable( z.string().describe( 'Responsive breakpoint name for styles. Defaults to desktop (null).' ) )
36
- .default( null )
37
- .describe( 'Responsive breakpoint name for styles. Defaults to desktop (null).' ),
38
- };
39
-
40
- const outputSchema = {
41
- status: z.enum( [ 'ok', 'error' ] ).describe( 'Operation status' ),
42
- classId: z.string().optional().describe( 'Class ID (returned on create success)' ),
43
- message: z.string().optional().describe( 'Error details if status is error' ),
44
- };
45
-
46
- type InputSchema = z.infer< ReturnType< typeof z.object< typeof schema > > >;
47
- type OutputSchema = z.infer< ReturnType< typeof z.object< typeof outputSchema > > >;
48
-
49
- type ConvertedStateStyle = {
50
- props: Props;
51
- customCss: CustomCss | null;
52
- };
53
-
54
- const handler = async ( input: InputSchema ): Promise< OutputSchema > => {
55
- const { action, classId: rawClassId, globalClassName, style: rawStyle, breakpoint } = input;
56
- let classId = rawClassId;
57
- if ( action === 'create' && ! globalClassName ) {
58
- return {
59
- status: 'error',
60
- message: 'Create requires globalClassName',
61
- };
62
- }
63
-
64
- if ( action === 'modify' && ! classId ) {
65
- return {
66
- status: 'error',
67
- message: 'Modify requires classId',
68
- };
69
- }
70
-
71
- if ( action === 'delete' && ! classId ) {
72
- return {
73
- status: 'error',
74
- message: 'Delete requires classId',
75
- };
76
- }
77
-
78
- const { create, update, delete: deleteClass } = globalClassesStylesProvider.actions;
79
- if ( ! create || ! update || ! deleteClass ) {
80
- return {
81
- status: 'error',
82
- message: 'Required actions not available',
83
- };
84
- }
85
-
86
- const styleBlocks = collectNonEmptyStyleBlocks( rawStyle );
87
-
88
- if ( action !== 'delete' && Object.keys( styleBlocks ).length === 0 ) {
89
- throw new Error(
90
- 'Style must not be empty. Provide plaintext CSS per state.\n\nExample: style.default = "display: flex; flex-direction: column; gap: 1rem;"'
91
- );
92
- }
93
-
94
- let convertedByState: Record< string, ConvertedStateStyle > = {};
95
- if ( action !== 'delete' ) {
96
- const conversionResults = await convertStyleBlocksToAtomic( styleBlocks );
97
- convertedByState = Object.fromEntries(
98
- Object.entries( conversionResults ).map( ( [ state, { props, customCss } ] ) => [
99
- state,
100
- { props: props as Props, customCss: toStoredCustomCss( customCss ) },
101
- ] )
102
- );
103
- }
104
-
105
- const breakpointValue = breakpoint ?? 'desktop';
106
- let result = {
107
- status: 'error',
108
- classId: '',
109
- message: 'unknown error',
110
- } as { status: 'error' | 'ok'; message?: string; classId?: string };
111
-
112
- try {
113
- if ( action === 'delete' ) {
114
- const deleted = await attemptDelete( {
115
- classId,
116
- stylesProvider: globalClassesStylesProvider,
117
- } );
118
- if ( deleted ) {
119
- return { status: 'ok', message: `deleted global class with ID ${ classId }` };
120
- }
121
- throw new Error( 'error deleting class' );
122
- }
123
-
124
- let currentAction = action;
125
- for await ( const [ state, { props, customCss } ] of Object.entries( convertedByState ) ) {
126
- switch ( currentAction ) {
127
- case 'create':
128
- const newClassId = await attemptCreate( {
129
- props,
130
- customCss,
131
- className: globalClassName,
132
- stylesProvider: globalClassesStylesProvider,
133
- breakpoint: breakpointValue as BreakpointId,
134
- state: state as StyleDefinitionState,
135
- } );
136
- if ( newClassId && currentAction === 'create' ) {
137
- currentAction = 'modify';
138
- classId = newClassId;
139
- result = {
140
- status: 'ok',
141
- message: `created global class with ID ${ newClassId }`,
142
- };
143
- globalClassesStylesProvider.actions.tracking?.( {
144
- event: 'classCreated',
145
- executedBy: 'mcp_tool',
146
- classId: newClassId,
147
- } );
148
- dispatchMcpStylesAppliedEvent( { styleValue: props } );
149
- } else {
150
- throw new Error( 'error creating class' );
151
- }
152
- break;
153
- case 'modify':
154
- const updated = await attemptUpdate( {
155
- classId,
156
- props,
157
- customCss,
158
- stylesProvider: globalClassesStylesProvider,
159
- breakpoint: breakpointValue as BreakpointId,
160
- state: state as StyleDefinitionState,
161
- } );
162
- if ( updated ) {
163
- result = { status: 'ok', classId };
164
- dispatchMcpStylesAppliedEvent( { styleValue: props } );
165
- } else {
166
- throw new Error( 'error modifying class' );
167
- }
168
- break;
169
- default:
170
- throw new Error( `Unsupported action ${ action }` );
171
- }
172
- }
173
- } catch ( error ) {
174
- return {
175
- status: 'error',
176
- message: `${ action } failed: ${ ( error as Error ).message || 'Unknown error' }`,
177
- };
178
- }
179
- return result;
180
- };
181
-
182
- export const initManageGlobalClasses = ( reg: MCPRegistryEntry ) => {
183
- const { addTool } = reg;
184
-
185
- addTool( {
186
- name: 'manage-global-classes',
187
- requiredResources: [
188
- { uri: GLOBAL_CLASSES_URI, description: 'Global classes list' },
189
- { uri: BREAKPOINTS_SCHEMA_FULL_URI, description: 'Breakpoints list' },
190
- ],
191
- description: `Create or modify global classes for reusable design-system styling. Class names must reflect purpose (e.g. heading-primary, button-cta). Create classes BEFORE applying them. Do NOT create classes for one-off styles.
192
-
193
- IMPORTANT: style must contain plaintext CSS rules per state — never pass empty strings.
194
- CSS is converted server-side in one bulk request; any declaration that cannot be converted is stored as the class custom CSS.
195
-
196
- Example — creating a flex column class:
197
- style.default = "display: flex; flex-direction: column; gap: 1rem;"
198
-
199
- Example — hover state:
200
- style.hover = "opacity: 0.85;"`,
201
- schema,
202
- outputSchema,
203
- handler,
204
- } );
205
- };
206
-
207
- type Opts = {
208
- stylesProvider: StylesProvider;
209
- className?: string;
210
- classId?: string;
211
- breakpoint: BreakpointId;
212
- props: Props;
213
- customCss: CustomCss | null;
214
- state: StyleDefinitionState;
215
- };
216
-
217
- async function attemptCreate( opts: Opts ) {
218
- const { props, customCss, breakpoint, className, stylesProvider, state } = opts;
219
- const { create, delete: deleteClass } = stylesProvider.actions;
220
- if ( ! className ) {
221
- throw new Error( 'Global class name is a required for creation' );
222
- }
223
- if ( ! create || ! deleteClass ) {
224
- throw new Error( 'User is unable to create global classes' );
225
- }
226
- const newClassId = create( className, [
227
- {
228
- meta: {
229
- breakpoint,
230
- state: ( state as string ) === 'default' ? null : state,
231
- },
232
- custom_css: customCss,
233
- props,
234
- },
235
- ] );
236
- try {
237
- await saveGlobalClasses( { context: 'frontend' } );
238
- return newClassId;
239
- } catch {
240
- deleteClass( newClassId );
241
- throw new Error( 'error creating class' );
242
- }
243
- }
244
-
245
- async function attemptUpdate( opts: Opts ) {
246
- const { props, customCss, breakpoint, classId, stylesProvider, state } = opts;
247
- const { updateProps, update } = stylesProvider.actions;
248
- if ( ! classId ) {
249
- throw new Error( 'Class ID is required for modification' );
250
- }
251
- if ( ! updateProps || ! update ) {
252
- throw new Error( 'User is unable to update global classes' );
253
- }
254
- await loadExistingClasses( [ classId ] );
255
- const snapshot = structuredClone( stylesProvider.actions.all() );
256
- try {
257
- updateProps( {
258
- id: classId,
259
- props,
260
- custom_css: customCss,
261
- meta: {
262
- breakpoint,
263
- state: ( state as string ) === 'default' ? null : state,
264
- },
265
- } );
266
- await saveGlobalClasses( { context: 'frontend' } );
267
- return true;
268
- } catch {
269
- snapshot.forEach( ( style ) => {
270
- update( {
271
- id: style.id,
272
- variants: style.variants,
273
- } );
274
- } );
275
- await saveGlobalClasses( { context: 'frontend' } );
276
- throw new Error( 'error updating class' );
277
- }
278
- }
279
-
280
- async function attemptDelete( opts: Pick< Opts, 'classId' | 'stylesProvider' > ) {
281
- const { classId, stylesProvider } = opts;
282
- const { delete: deleteClass, create } = stylesProvider.actions;
283
- if ( ! classId ) {
284
- throw new Error( 'Class ID is required for deletion' );
285
- }
286
- if ( ! deleteClass || ! create ) {
287
- throw new Error( 'User is unable to delete global classes' );
288
- }
289
- const snapshot = structuredClone( stylesProvider.actions.all() );
290
- const targetClass = snapshot.find( ( style ) => style.id === classId );
291
- if ( ! targetClass ) {
292
- throw new Error( `Class with ID "${ classId }" not found` );
293
- }
294
- deleteClass( classId );
295
- await saveGlobalClasses( { context: 'frontend' } );
296
- return true;
297
- }
298
-
299
- function collectNonEmptyStyleBlocks( style: InputSchema[ 'style' ] ): Record< string, StyleBlock > {
300
- const blocks: Record< string, StyleBlock > = {};
301
- Object.entries( style ).forEach( ( [ state, cssText ] ) => {
302
- if ( cssText?.trim() ) {
303
- blocks[ state ] = cssText.trim();
304
- }
305
- } );
306
- return blocks;
307
- }
308
-
309
- function toStoredCustomCss( customCss: string ): CustomCss | null {
310
- if ( ! customCss?.trim() ) {
311
- return null;
312
- }
313
- return { raw: btoa( customCss ) };
314
- }