@elementor/editor-global-classes 4.2.0-887 → 4.2.0-894

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.2.0-887",
3
+ "version": "4.2.0-894",
4
4
  "private": false,
5
5
  "author": "Elementor Team",
6
6
  "homepage": "https://elementor.com/",
@@ -39,29 +39,29 @@
39
39
  "dev": "tsup --config=../../tsup.dev.ts"
40
40
  },
41
41
  "dependencies": {
42
- "@elementor/editor": "4.2.0-887",
43
- "@elementor/editor-current-user": "4.2.0-887",
44
- "@elementor/editor-documents": "4.2.0-887",
45
- "@elementor/editor-editing-panel": "4.2.0-887",
46
- "@elementor/editor-mcp": "4.2.0-887",
47
- "@elementor/editor-panels": "4.2.0-887",
48
- "@elementor/editor-props": "4.2.0-887",
49
- "@elementor/editor-variables": "4.2.0-887",
50
- "@elementor/editor-styles": "4.2.0-887",
51
- "@elementor/editor-canvas": "4.2.0-887",
52
- "@elementor/editor-styles-repository": "4.2.0-887",
53
- "@elementor/editor-ui": "4.2.0-887",
54
- "@elementor/editor-v1-adapters": "4.2.0-887",
55
- "@elementor/http-client": "4.2.0-887",
42
+ "@elementor/editor": "4.2.0-894",
43
+ "@elementor/editor-current-user": "4.2.0-894",
44
+ "@elementor/editor-documents": "4.2.0-894",
45
+ "@elementor/editor-editing-panel": "4.2.0-894",
46
+ "@elementor/editor-mcp": "4.2.0-894",
47
+ "@elementor/editor-panels": "4.2.0-894",
48
+ "@elementor/editor-props": "4.2.0-894",
49
+ "@elementor/editor-variables": "4.2.0-894",
50
+ "@elementor/editor-styles": "4.2.0-894",
51
+ "@elementor/editor-canvas": "4.2.0-894",
52
+ "@elementor/editor-styles-repository": "4.2.0-894",
53
+ "@elementor/editor-ui": "4.2.0-894",
54
+ "@elementor/editor-v1-adapters": "4.2.0-894",
55
+ "@elementor/http-client": "4.2.0-894",
56
56
  "@elementor/icons": "~1.75.1",
57
- "@elementor/query": "4.2.0-887",
58
- "@elementor/schema": "4.2.0-887",
59
- "@elementor/store": "4.2.0-887",
57
+ "@elementor/query": "4.2.0-894",
58
+ "@elementor/schema": "4.2.0-894",
59
+ "@elementor/store": "4.2.0-894",
60
60
  "@elementor/ui": "1.37.5",
61
- "@elementor/utils": "4.2.0-887",
61
+ "@elementor/utils": "4.2.0-894",
62
62
  "@tanstack/react-virtual": "^3.13.24",
63
63
  "@wordpress/i18n": "^5.13.0",
64
- "@elementor/events": "4.2.0-887"
64
+ "@elementor/events": "4.2.0-894"
65
65
  },
66
66
  "peerDependencies": {
67
67
  "react": "^18.3.1",
@@ -1,10 +1,5 @@
1
1
  import * as React from 'react';
2
- import {
3
- __useActiveDocument as useActiveDocument,
4
- __useActiveDocumentActions as useActiveDocumentActions,
5
- } from '@elementor/editor-documents';
6
2
  import { useUserStylesCapability } from '@elementor/editor-styles-repository';
7
- import { SaveChangesDialog, useDialog } from '@elementor/editor-ui';
8
3
  import { IconButton, Tooltip } from '@elementor/ui';
9
4
  import { __ } from '@wordpress/i18n';
10
5
 
@@ -13,17 +8,9 @@ import { usePrefetchCssClassUsage } from '../../hooks/use-prefetch-css-class-usa
13
8
  import { trackGlobalClasses } from '../../utils/tracking';
14
9
  import { FlippedColorSwatchIcon } from './flipped-color-swatch-icon';
15
10
 
16
- const trackGlobalClassesButton = () => {
17
- trackGlobalClasses( {
18
- event: 'classManagerOpened',
19
- source: 'style-panel',
20
- } );
21
- };
11
+ const EVENT_TOGGLE_DESIGN_SYSTEM = 'elementor/toggle-design-system';
22
12
 
23
13
  export const ClassManagerButton = () => {
24
- const document = useActiveDocument();
25
- const { save: saveDocument } = useActiveDocumentActions();
26
- const { open: openSaveChangesDialog, close: closeSaveChangesDialog, isOpen: isSaveChangesDialogOpen } = useDialog();
27
14
  const { prefetchClassesUsage } = usePrefetchCssClassUsage();
28
15
 
29
16
  const { userCan } = useUserStylesCapability();
@@ -34,64 +21,25 @@ export const ClassManagerButton = () => {
34
21
  return null;
35
22
  }
36
23
 
37
- const toggleClassesManagerPanel = () => {
24
+ const handleOpenPanel = () => {
38
25
  window.dispatchEvent(
39
- new CustomEvent( 'elementor/toggle-design-system', {
26
+ new CustomEvent( EVENT_TOGGLE_DESIGN_SYSTEM, {
40
27
  detail: { tab: 'classes' as const },
41
28
  } )
42
29
  );
43
- };
44
-
45
- const handleOpenPanel = () => {
46
- if ( document?.isDirty ) {
47
- openSaveChangesDialog();
48
- return;
49
- }
50
-
51
- toggleClassesManagerPanel();
52
30
 
53
- trackGlobalClassesButton();
31
+ trackGlobalClasses( {
32
+ event: 'classManagerOpened',
33
+ source: 'style-panel',
34
+ } );
54
35
  prefetchClassesUsage();
55
36
  };
56
37
 
57
38
  return (
58
- <>
59
- <Tooltip title={ __( 'Class Manager', 'elementor' ) } placement="top">
60
- <IconButton size="tiny" onClick={ handleOpenPanel } sx={ { marginInlineEnd: -0.75 } }>
61
- <FlippedColorSwatchIcon fontSize="tiny" />
62
- </IconButton>
63
- </Tooltip>
64
- { isSaveChangesDialogOpen && (
65
- <SaveChangesDialog>
66
- <SaveChangesDialog.Title>{ __( 'You have unsaved changes', 'elementor' ) }</SaveChangesDialog.Title>
67
- <SaveChangesDialog.Content>
68
- <SaveChangesDialog.ContentText sx={ { mb: 2 } }>
69
- { __(
70
- "To open the Class Manager, save your page first. You can't continue without saving.",
71
- 'elementor'
72
- ) }
73
- </SaveChangesDialog.ContentText>
74
- </SaveChangesDialog.Content>
75
- <SaveChangesDialog.Actions
76
- actions={ {
77
- cancel: {
78
- label: __( 'Stay here', 'elementor' ),
79
- action: closeSaveChangesDialog,
80
- },
81
- confirm: {
82
- label: __( 'Save & Continue', 'elementor' ),
83
- action: async () => {
84
- await saveDocument();
85
- closeSaveChangesDialog();
86
- toggleClassesManagerPanel();
87
- trackGlobalClassesButton();
88
- prefetchClassesUsage();
89
- },
90
- },
91
- } }
92
- />
93
- </SaveChangesDialog>
94
- ) }
95
- </>
39
+ <Tooltip title={ __( 'Class Manager', 'elementor' ) } placement="top">
40
+ <IconButton size="tiny" onClick={ handleOpenPanel } sx={ { marginInlineEnd: -0.75 } }>
41
+ <FlippedColorSwatchIcon fontSize="tiny" />
42
+ </IconButton>
43
+ </Tooltip>
96
44
  );
97
45
  };
@@ -37,18 +37,34 @@ type StopSyncConfirmationDialogProps = {
37
37
  export type ClassManagerPanelEmbeddedProps = {
38
38
  onRequestClose: () => void | Promise< void >;
39
39
  onExposeCloseAttempt?: ( attemptClose: ( () => void ) | null ) => void;
40
+ isActive?: boolean;
40
41
  };
41
42
 
42
- export function ClassManagerPanelEmbedded( { onRequestClose, onExposeCloseAttempt }: ClassManagerPanelEmbeddedProps ) {
43
- return <ClassManagerPanelContent onRequestClose={ onRequestClose } onExposeCloseAttempt={ onExposeCloseAttempt } />;
43
+ export function ClassManagerPanelEmbedded( {
44
+ onRequestClose,
45
+ onExposeCloseAttempt,
46
+ isActive,
47
+ }: ClassManagerPanelEmbeddedProps ) {
48
+ return (
49
+ <ClassManagerPanelContent
50
+ onRequestClose={ onRequestClose }
51
+ onExposeCloseAttempt={ onExposeCloseAttempt }
52
+ isActive={ isActive }
53
+ />
54
+ );
44
55
  }
45
56
 
46
57
  type ClassManagerPanelContentProps = {
47
58
  onRequestClose: () => void | Promise< void >;
48
59
  onExposeCloseAttempt?: ( attemptClose: ( () => void ) | null ) => void;
60
+ isActive?: boolean;
49
61
  };
50
62
 
51
- function ClassManagerPanelContent( { onRequestClose, onExposeCloseAttempt }: ClassManagerPanelContentProps ) {
63
+ function ClassManagerPanelContent( {
64
+ onRequestClose,
65
+ onExposeCloseAttempt,
66
+ isActive = true,
67
+ }: ClassManagerPanelContentProps ) {
52
68
  const isDirty = useDirtyState();
53
69
  const { open: openSaveChangesDialog, close: closeSaveChangesDialog, isOpen: isSaveChangesDialogOpen } = useDialog();
54
70
  const [ stopSyncConfirmation, setStopSyncConfirmation ] = useState< string | null >( null );
@@ -192,7 +208,7 @@ function ClassManagerPanelContent( { onRequestClose, onExposeCloseAttempt }: Cla
192
208
  </Stack>
193
209
  </SearchAndFilterProvider>
194
210
  </ErrorBoundary>
195
- <ClassManagerIntroduction />
211
+ { isActive && <ClassManagerIntroduction /> }
196
212
  { startSyncConfirmation && (
197
213
  <StartSyncToV3Modal
198
214
  externalOpen
@@ -40,8 +40,10 @@ async function fetchAndMergeClasses(): Promise< void > {
40
40
  return;
41
41
  }
42
42
 
43
- const previewResponse = await apiClient.getStylesByIds( idsToFetch, 'preview' );
44
- const frontendResponse = await apiClient.getStylesByIds( idsToFetch, 'frontend' );
43
+ const [ previewResponse, frontendResponse ] = await Promise.all( [
44
+ apiClient.getStylesByIds( idsToFetch, 'preview' ),
45
+ apiClient.getStylesByIds( idsToFetch, 'frontend' ),
46
+ ] );
45
47
  const previewItems = styleDefinitionsMapWithoutNull( previewResponse.data.data );
46
48
  const frontendItems = styleDefinitionsMapWithoutNull( frontendResponse.data.data );
47
49
 
@@ -1,4 +1,4 @@
1
- import { BREAKPOINTS_SCHEMA_URI, STYLE_SCHEMA_URI } from '@elementor/editor-canvas';
1
+ import { BREAKPOINTS_SCHEMA_FULL_URI, STYLE_SCHEMA_FULL_URI } from '@elementor/editor-canvas';
2
2
  import { type MCPRegistryEntry } from '@elementor/editor-mcp';
3
3
  import { type Props, Schema } from '@elementor/editor-props';
4
4
  import { type BreakpointId } from '@elementor/editor-responsive';
@@ -8,6 +8,7 @@ import { type Utils as IUtils } from '@elementor/editor-variables';
8
8
  import { z } from '@elementor/schema';
9
9
 
10
10
  import { globalClassesStylesProvider } from '../global-classes-styles-provider';
11
+ import { loadExistingClasses } from '../load-existing-classes';
11
12
  import { saveGlobalClasses } from '../save-global-classes';
12
13
  import { GLOBAL_CLASSES_URI } from './classes-resource';
13
14
 
@@ -23,21 +24,39 @@ const schema = {
23
24
  globalClassName: z.string().optional().describe( 'Global class name (required for create)' ),
24
25
  props: z.object( {
25
26
  default: z
26
- .record( z.any() )
27
+ .record(
28
+ z.string().describe( 'The style property name' ),
29
+ z.any().describe( `The style PropValue, refer to [${ STYLE_SCHEMA_FULL_URI }] how to generate values` )
30
+ )
27
31
  .describe(
28
- 'key-value of style-schema PropValues. Available properties at dynamic resource "elementor://styles/schema/{property-name}"'
32
+ 'An object record containing style property names and their new values. MUST contain at least one property — empty objects are rejected.'
29
33
  ),
30
34
  hover: z
31
- .record( z.any() )
32
- .describe( 'key-value of style-schema PropValues, for :hover css state. optional' )
35
+ .record(
36
+ z.string().describe( 'The style property name' ),
37
+ z.any().describe( `The style PropValue, refer to [${ STYLE_SCHEMA_FULL_URI }] how to generate values` )
38
+ )
39
+ .describe(
40
+ 'An object record containing style property names and their new values to be set on the element. for :hover css state. optional'
41
+ )
33
42
  .optional(),
34
43
  focus: z
35
- .record( z.any() )
36
- .describe( 'key-value of style-schema PropValues, for :focus css state. optional' )
44
+ .record(
45
+ z.string().describe( 'The style property name' ),
46
+ z.any().describe( `The style PropValue, refer to [${ STYLE_SCHEMA_FULL_URI }] how to generate values` )
47
+ )
48
+ .describe(
49
+ 'An object record containing style property names and their new values to be set on the element. for :focus css state. optional'
50
+ )
37
51
  .optional(),
38
52
  active: z
39
- .record( z.any() )
40
- .describe( 'key-value of style-schema PropValues, for :active css state. optional' )
53
+ .record(
54
+ z.string().describe( 'The style property name' ),
55
+ z.any().describe( `The style PropValue, refer to [${ STYLE_SCHEMA_FULL_URI }] how to generate values` )
56
+ )
57
+ .describe(
58
+ 'An object record containing style property names and their new values to be set on the element. for :active css state. optional'
59
+ )
41
60
  .optional(),
42
61
  } ),
43
62
  breakpoint: z
@@ -105,13 +124,25 @@ const handler = async ( input: InputSchema ): Promise< OutputSchema > => {
105
124
  } );
106
125
  } );
107
126
 
127
+ if ( action !== 'delete' ) {
128
+ const hasAnyProps = Object.values( propsWithStates ).some(
129
+ ( stateProps ) => Object.keys( stateProps ).length > 0
130
+ );
131
+ if ( ! hasAnyProps ) {
132
+ throw new Error(
133
+ `Props must not be empty. Each prop must be a PropValue object from the style schema.\n\nExample: { "display": { "$$type": "string", "value": "flex" }, "flex-direction": { "$$type": "string", "value": "column" } }\n\n${ STYLE_SCHEMA_FULL_URI } to get the allowed values (look at the "value" enum in the schema response), then construct { "$$type": "string", "value": "<chosen value>" } for each property.\nAvailable Properties: ${ validProps.join(
134
+ ', '
135
+ ) }`
136
+ );
137
+ }
138
+ }
139
+
108
140
  if ( errors.length > 0 ) {
109
- return {
110
- status: 'error',
111
- message: `Validation errors:\n${ errors.join( '\n' ) }\nAvailable Properties: ${ validProps.join(
141
+ throw new Error(
142
+ `Validation errors:\n${ errors.join( '\n' ) }\nAvailable Properties: ${ validProps.join(
112
143
  ', '
113
- ) }\nUpdate your input and try again.`,
114
- };
144
+ ) }\nUpdate your input and try again.`
145
+ );
115
146
  }
116
147
 
117
148
  // TODO: see https://elementor.atlassian.net/browse/ED-22513 for better cross-module access
@@ -133,6 +164,17 @@ const handler = async ( input: InputSchema ): Promise< OutputSchema > => {
133
164
  } as { status: 'error' | 'ok'; message?: string; classId?: string };
134
165
 
135
166
  try {
167
+ if ( action === 'delete' ) {
168
+ const deleted = await attemptDelete( {
169
+ classId,
170
+ stylesProvider: globalClassesStylesProvider,
171
+ } );
172
+ if ( deleted ) {
173
+ return { status: 'ok', message: `deleted global class with ID ${ classId }` };
174
+ }
175
+ throw new Error( 'error deleting class' );
176
+ }
177
+
136
178
  let currentAction = action;
137
179
  for await ( const [ state, props ] of Object.entries( propsWithStates ) ) {
138
180
  switch ( currentAction ) {
@@ -148,16 +190,13 @@ const handler = async ( input: InputSchema ): Promise< OutputSchema > => {
148
190
  // NOTE: for multiple iterations as the state changes, the next execution would be update an existing class
149
191
  currentAction = 'modify';
150
192
  classId = newClassId;
193
+ result = {
194
+ status: 'ok',
195
+ message: `created global class with ID ${ newClassId }`,
196
+ };
197
+ } else {
198
+ throw new Error( 'error creating class' );
151
199
  }
152
- result = newClassId
153
- ? {
154
- status: 'ok',
155
- message: `created global class with ID ${ newClassId }`,
156
- }
157
- : {
158
- status: 'error',
159
- message: 'error creating class',
160
- };
161
200
  break;
162
201
  case 'modify':
163
202
  const updated = await attemptUpdate( {
@@ -167,24 +206,11 @@ const handler = async ( input: InputSchema ): Promise< OutputSchema > => {
167
206
  breakpoint: breakpointValue as BreakpointId,
168
207
  state: state as StyleDefinitionState,
169
208
  } );
170
- result = updated
171
- ? { status: 'ok', classId }
172
- : {
173
- status: 'error',
174
- message: 'error modifying class',
175
- };
176
- break;
177
- case 'delete':
178
- const deleted = await attemptDelete( {
179
- classId,
180
- stylesProvider: globalClassesStylesProvider,
181
- } );
182
- result = deleted
183
- ? { status: 'ok', message: `deleted global class with ID ${ classId }` }
184
- : {
185
- status: 'error',
186
- message: 'error deleting class',
187
- };
209
+ if ( updated ) {
210
+ result = { status: 'ok', classId };
211
+ } else {
212
+ throw new Error( 'error modifying class' );
213
+ }
188
214
  break;
189
215
  default:
190
216
  throw new Error( `Unsupported action ${ action }` );
@@ -206,10 +232,21 @@ export const initManageGlobalClasses = ( reg: MCPRegistryEntry ) => {
206
232
  name: 'manage-global-classes',
207
233
  requiredResources: [
208
234
  { uri: GLOBAL_CLASSES_URI, description: 'Global classes list' },
209
- { uri: STYLE_SCHEMA_URI, description: 'Style schema resources' },
210
- { uri: BREAKPOINTS_SCHEMA_URI, description: 'Breakpoints list' },
235
+ { uri: STYLE_SCHEMA_FULL_URI, description: 'Style schema resources' },
236
+ { uri: BREAKPOINTS_SCHEMA_FULL_URI, description: 'Breakpoints list' },
211
237
  ],
212
- 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 compositions. Do NOT create classes for one-off styles or layout-specific properties.`,
238
+ 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.
239
+
240
+ IMPORTANT: props must contain actual CSS property values — never pass empty objects.
241
+ Fetch ${ STYLE_SCHEMA_FULL_URI } to get the allowed values for each property, then use them to build the props object.
242
+
243
+ Example — creating a flex column class:
244
+ props.default = {
245
+ "display": { "$$type": "string", "value": "flex" },
246
+ "flex-direction": { "$$type": "string", "value": "column" }
247
+ }
248
+
249
+ The style schema returns a JSON Schema. Extract the "value" enum to pick the right value, then construct { "$$type": "string", "value": "<picked value>" }.`,
213
250
  schema,
214
251
  outputSchema,
215
252
  handler,
@@ -249,7 +286,7 @@ async function attemptCreate( opts: Opts ) {
249
286
  return newClassId;
250
287
  } catch {
251
288
  deleteClass( newClassId );
252
- return null;
289
+ throw new Error( 'error creating class' );
253
290
  }
254
291
  }
255
292
 
@@ -262,6 +299,7 @@ async function attemptUpdate( opts: Opts ) {
262
299
  if ( ! updateProps || ! update ) {
263
300
  throw new Error( 'User is unable to update global classes' );
264
301
  }
302
+ await loadExistingClasses( [ classId ] );
265
303
  const snapshot = structuredClone( stylesProvider.actions.all() );
266
304
  try {
267
305
  updateProps( {
@@ -269,7 +307,7 @@ async function attemptUpdate( opts: Opts ) {
269
307
  props,
270
308
  meta: {
271
309
  breakpoint,
272
- state,
310
+ state: ( state as string ) === 'default' ? null : state,
273
311
  },
274
312
  } );
275
313
  await saveGlobalClasses( { context: 'frontend' } );
@@ -282,7 +320,7 @@ async function attemptUpdate( opts: Opts ) {
282
320
  } );
283
321
  } );
284
322
  await saveGlobalClasses( { context: 'frontend' } );
285
- return false;
323
+ throw new Error( 'error updating class' );
286
324
  }
287
325
  }
288
326
 
@@ -300,11 +338,7 @@ async function attemptDelete( opts: Pick< Opts, 'classId' | 'stylesProvider' > )
300
338
  if ( ! targetClass ) {
301
339
  throw new Error( `Class with ID "${ classId }" not found` );
302
340
  }
303
- try {
304
- deleteClass( classId );
305
- await saveGlobalClasses( { context: 'frontend' } );
306
- return true;
307
- } catch {
308
- return false;
309
- }
341
+ deleteClass( classId );
342
+ await saveGlobalClasses( { context: 'frontend' } );
343
+ return true;
310
344
  }