@elementor/editor-global-classes 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.
Files changed (37) hide show
  1. package/dist/index.js +1000 -430
  2. package/dist/index.js.map +1 -1
  3. package/dist/index.mjs +940 -366
  4. package/dist/index.mjs.map +1 -1
  5. package/package.json +21 -18
  6. package/src/api.ts +4 -0
  7. package/src/components/class-manager/class-manager-button.tsx +15 -1
  8. package/src/components/class-manager/class-manager-panel.tsx +2 -2
  9. package/src/components/class-manager/delete-class.ts +9 -3
  10. package/src/components/class-manager/delete-confirmation-dialog.tsx +2 -2
  11. package/src/components/class-manager/duplicate-label-dialog.tsx +159 -0
  12. package/src/components/class-manager/global-classes-list.tsx +53 -22
  13. package/src/components/convert-local-class-to-global-class.tsx +7 -0
  14. package/src/components/css-class-usage/components/css-class-usage-popover.tsx +10 -1
  15. package/src/components/css-class-usage/components/css-class-usage-trigger.tsx +22 -7
  16. package/src/components/search-and-filter/components/filter/active-filters.tsx +8 -0
  17. package/src/components/search-and-filter/components/filter/clear-icon-button.tsx +12 -3
  18. package/src/components/search-and-filter/components/filter/css-class-filter.tsx +10 -0
  19. package/src/components/search-and-filter/components/filter/filter-list.tsx +7 -0
  20. package/src/components/search-and-filter/components/search/class-manager-search.tsx +6 -0
  21. package/src/components/search-and-filter/context.tsx +12 -2
  22. package/src/errors.ts +5 -0
  23. package/src/global-classes-styles-provider.ts +13 -3
  24. package/src/hooks/use-css-class-by-id.ts +8 -0
  25. package/src/hooks/use-prefetch-css-class-usage.ts +6 -0
  26. package/src/init.ts +14 -5
  27. package/src/mcp-integration/classes-resource.ts +20 -0
  28. package/src/mcp-integration/index.ts +15 -0
  29. package/src/mcp-integration/mcp-apply-unapply-global-classes.ts +117 -0
  30. package/src/mcp-integration/mcp-get-global-class-usages.ts +72 -0
  31. package/src/save-global-classes.tsx +55 -0
  32. package/src/store.ts +32 -1
  33. package/src/sync-with-document-save.ts +9 -6
  34. package/src/sync-with-document.tsx +19 -0
  35. package/src/utils/tracking.ts +204 -0
  36. package/src/components/class-manager/save-changes-dialog.tsx +0 -92
  37. package/src/save-global-classes.ts +0 -42
@@ -17,7 +17,7 @@ type SearchContextType = {
17
17
  type FilterAndSortContextType = {
18
18
  filters: CheckedFilters;
19
19
  setFilters: React.Dispatch< React.SetStateAction< CheckedFilters > >;
20
- onClearFilter: () => void;
20
+ onClearFilter: ( type?: 'menu' | 'header' ) => void;
21
21
  };
22
22
 
23
23
  export type SearchAndFilterContextType = {
@@ -35,9 +35,19 @@ const INIT_CHECKED_FILTERS: CheckedFilters = {
35
35
 
36
36
  export const SearchAndFilterProvider = ( { children }: React.PropsWithChildren ) => {
37
37
  const [ filters, setFilters ] = React.useState< CheckedFilters >( INIT_CHECKED_FILTERS );
38
+
39
+ const getInitialSearchValue = () => {
40
+ const storedValue = localStorage.getItem( 'elementor-global-classes-search' );
41
+ if ( storedValue ) {
42
+ localStorage.removeItem( 'elementor-global-classes-search' );
43
+ return storedValue;
44
+ }
45
+ return '';
46
+ };
47
+
38
48
  const { debouncedValue, inputValue, handleChange } = useDebounceState( {
39
49
  delay: 300,
40
- initialValue: '',
50
+ initialValue: getInitialSearchValue(),
41
51
  } );
42
52
 
43
53
  const onClearSearch = () => {
package/src/errors.ts CHANGED
@@ -9,3 +9,8 @@ export const GlobalClassLabelAlreadyExistsError = createError< { label: string }
9
9
  code: 'global_class_label_already_exists',
10
10
  message: 'Class with this name already exists.',
11
11
  } );
12
+
13
+ export const GlobalClassTrackingError = createError< { cause: string } >( {
14
+ code: 'global_class_tracking_error',
15
+ message: 'Error tracking global classes event.',
16
+ } );
@@ -8,7 +8,7 @@ import {
8
8
  import { __ } from '@wordpress/i18n';
9
9
 
10
10
  import { getCapabilities } from './capabilities';
11
- import { GlobalClassLabelAlreadyExistsError } from './errors';
11
+ import { GlobalClassLabelAlreadyExistsError, GlobalClassTrackingError } from './errors';
12
12
  import {
13
13
  selectClass,
14
14
  selectData,
@@ -17,8 +17,9 @@ import {
17
17
  slice,
18
18
  type StateWithGlobalClasses,
19
19
  } from './store';
20
+ import { trackGlobalClasses, type TrackingEvent } from './utils/tracking';
20
21
 
21
- const MAX_CLASSES = 50;
22
+ const MAX_CLASSES = 100;
22
23
 
23
24
  export const GLOBAL_CLASSES_PROVIDER_KEY = 'global-classes';
24
25
 
@@ -33,7 +34,11 @@ export const globalClassesStylesProvider = createStylesProvider( {
33
34
  subscribe: ( cb ) => subscribeWithStates( cb ),
34
35
  capabilities: getCapabilities(),
35
36
  actions: {
36
- all: () => selectOrderedClasses( getState() ),
37
+ all: () => {
38
+ const selectAllClasses = selectOrderedClasses( getState() );
39
+ localStorage.setItem( 'elementor-global-classes', JSON.stringify( selectAllClasses ) );
40
+ return selectAllClasses;
41
+ },
37
42
  get: ( id ) => selectClass( getState(), id ),
38
43
  resolveCssName: ( id: string ) => {
39
44
  return selectClass( getState(), id )?.label ?? id;
@@ -90,6 +95,11 @@ export const globalClassesStylesProvider = createStylesProvider( {
90
95
  } )
91
96
  );
92
97
  },
98
+ tracking: ( data: { event: string; [ key: string ]: unknown } ) => {
99
+ trackGlobalClasses( data as TrackingEvent ).catch( ( error ) => {
100
+ throw new GlobalClassTrackingError( { cause: error } );
101
+ } );
102
+ },
93
103
  },
94
104
  } );
95
105
 
@@ -0,0 +1,8 @@
1
+ import { type StyleDefinitionID } from '@elementor/editor-styles';
2
+ import { __useSelector as useSelector, type SliceState } from '@elementor/store';
3
+
4
+ import { selectClass, type slice } from '../store';
5
+
6
+ export const useCssClassById = ( id: StyleDefinitionID ) => {
7
+ return useSelector( ( state: SliceState< typeof slice > ) => selectClass( state, id ) );
8
+ };
@@ -14,3 +14,9 @@ export function usePrefetchCssClassUsage() {
14
14
 
15
15
  return { prefetchClassesUsage };
16
16
  }
17
+
18
+ export const PrefetchCssClassUsage = () => {
19
+ const { prefetchClassesUsage } = usePrefetchCssClassUsage();
20
+ prefetchClassesUsage();
21
+ return null;
22
+ };
package/src/init.ts CHANGED
@@ -6,7 +6,6 @@ import {
6
6
  } from '@elementor/editor-editing-panel';
7
7
  import { __registerPanel as registerPanel } from '@elementor/editor-panels';
8
8
  import { stylesRepository } from '@elementor/editor-styles-repository';
9
- import { __privateListenTo as listenTo, v1ReadyEvent } from '@elementor/editor-v1-adapters';
10
9
  import { __registerSlice as registerSlice } from '@elementor/store';
11
10
 
12
11
  import { ClassManagerButton } from './components/class-manager/class-manager-button';
@@ -14,8 +13,10 @@ import { panel } from './components/class-manager/class-manager-panel';
14
13
  import { ConvertLocalClassToGlobalClass } from './components/convert-local-class-to-global-class';
15
14
  import { PopulateStore } from './components/populate-store';
16
15
  import { GLOBAL_CLASSES_PROVIDER_KEY, globalClassesStylesProvider } from './global-classes-styles-provider';
16
+ import { PrefetchCssClassUsage } from './hooks/use-prefetch-css-class-usage';
17
+ import { initMcpIntegration } from './mcp-integration';
17
18
  import { slice } from './store';
18
- import { syncWithDocumentSave } from './sync-with-document-save';
19
+ import { SyncWithDocumentSave } from './sync-with-document';
19
20
 
20
21
  export function init() {
21
22
  registerSlice( slice );
@@ -28,6 +29,16 @@ export function init() {
28
29
  component: PopulateStore,
29
30
  } );
30
31
 
32
+ injectIntoLogic( {
33
+ id: 'global-classes-sync-with-document',
34
+ component: SyncWithDocumentSave,
35
+ } );
36
+
37
+ injectIntoLogic( {
38
+ id: 'global-classes-prefetch-css-class-usage',
39
+ component: PrefetchCssClassUsage,
40
+ } );
41
+
31
42
  injectIntoCssClassConvert( {
32
43
  id: 'global-classes-convert-from-local-class',
33
44
  component: ConvertLocalClassToGlobalClass,
@@ -43,7 +54,5 @@ export function init() {
43
54
  getThemeColor: ( theme ) => theme.palette.global.dark,
44
55
  } );
45
56
 
46
- listenTo( v1ReadyEvent(), () => {
47
- syncWithDocumentSave();
48
- } );
57
+ initMcpIntegration();
49
58
  }
@@ -0,0 +1,20 @@
1
+ import { getMCPByDomain } from '@elementor/editor-mcp';
2
+
3
+ export const GLOBAL_CLASSES_URI = 'elementor://classes';
4
+
5
+ export const initClassesResource = () => {
6
+ const { mcpServer } = getMCPByDomain( 'classes' );
7
+
8
+ mcpServer.resource(
9
+ 'global-classes',
10
+ GLOBAL_CLASSES_URI,
11
+ {
12
+ description: 'Global classes list.',
13
+ },
14
+ async () => {
15
+ return {
16
+ contents: [ { uri: GLOBAL_CLASSES_URI, text: localStorage[ 'elementor-global-classes' ] ?? {} } ],
17
+ };
18
+ }
19
+ );
20
+ };
@@ -0,0 +1,15 @@
1
+ import { getMCPByDomain } from '@elementor/editor-mcp';
2
+
3
+ import { initClassesResource } from './classes-resource';
4
+ import initMcpApplyUnapplyGlobalClasses from './mcp-apply-unapply-global-classes';
5
+ import initMcpApplyGetGlobalClassUsages from './mcp-get-global-class-usages';
6
+
7
+ export const initMcpIntegration = () => {
8
+ const reg = getMCPByDomain( 'classes' );
9
+ reg.setMCPDescription(
10
+ 'Tools for managing and applying Global CSS classes to elements within the Elementor editor.'
11
+ );
12
+ initMcpApplyUnapplyGlobalClasses( reg );
13
+ initMcpApplyGetGlobalClassUsages( reg );
14
+ initClassesResource();
15
+ };
@@ -0,0 +1,117 @@
1
+ import { doApplyClasses, doGetAppliedClasses, doUnapplyClass } from '@elementor/editor-editing-panel';
2
+ import { type MCPRegistryEntry } from '@elementor/editor-mcp';
3
+ import { stylesRepository } from '@elementor/editor-styles-repository';
4
+ import { z } from '@elementor/schema';
5
+
6
+ export default function initMcpApplyUnapplyGlobalClasses( server: MCPRegistryEntry ) {
7
+ server.addTool( {
8
+ name: 'list-all-global-classes',
9
+ description: `List all classes applied to a specific element
10
+
11
+ ## When to use this tool:
12
+ - When a user requests to see which classes or global classes exists.
13
+ - When you need the list of global classes to allow the user to select from.
14
+ - At least once before applying or unapplying a class, to ensure the class ID is correct.
15
+
16
+ `,
17
+ outputSchema: {
18
+ appliedClasses: z.array(
19
+ z.object( {
20
+ id: z.string().describe( 'The ID of the class' ),
21
+ label: z.string().describe( 'The label of the class' ),
22
+ variants: z.array(
23
+ z.object( {
24
+ meta: z.object( {
25
+ breakpoint: z.string().optional(),
26
+ state: z.string().optional(),
27
+ } ),
28
+ props: z.record( z.any() ),
29
+ } )
30
+ ),
31
+ } )
32
+ ),
33
+ },
34
+ handler: async () => {
35
+ const globalClassesProvider = stylesRepository.getProviderByKey( 'global-classes' );
36
+ if ( ! globalClassesProvider ) {
37
+ throw new Error( 'Global classes provider not found' );
38
+ }
39
+ const result: {
40
+ id: string;
41
+ label: string;
42
+ variants: {
43
+ meta: { breakpoint?: string | undefined; state?: string | undefined };
44
+ props: Record< string, unknown >;
45
+ }[];
46
+ }[] = [];
47
+ globalClassesProvider.actions.all().forEach( ( style ) => {
48
+ const { id, label, variants } = style;
49
+ result.push( {
50
+ id,
51
+ label,
52
+ variants: variants.map( ( variant ) => ( {
53
+ meta: {
54
+ breakpoint: variant.meta.breakpoint as string | undefined,
55
+ state: variant.meta.state as string | undefined,
56
+ },
57
+ props: variant.props as Record< string, unknown >,
58
+ } ) ),
59
+ } );
60
+ } );
61
+ return { appliedClasses: result };
62
+ },
63
+ } );
64
+
65
+ server.addTool( {
66
+ schema: {
67
+ classId: z.string().describe( 'The ID of the class to apply' ),
68
+ elementId: z.string().describe( 'The ID of the element to which the class will be applied' ),
69
+ },
70
+ name: 'apply-global-class',
71
+ description: `Apply a global class to the current element
72
+
73
+ ## When to use this tool:
74
+ - When a user requests to apply a global class or a class to an element in the Elementor editor.
75
+ - When you need to add a specific class to an element's applied classes.
76
+
77
+ ## Prerequisites:
78
+ - Ensure you have the most up-to-date list of classes applied to the element to avoid duplicates. You can use the "list-applied-classes" tool to fetch the current classes.
79
+ - Make sure you have the correct class ID that you want to apply.`,
80
+ handler: async ( params ) => {
81
+ const { classId, elementId } = params;
82
+ const appliedClasses = doGetAppliedClasses( elementId );
83
+ doApplyClasses( elementId, [ ...appliedClasses, classId ] );
84
+ return `Class ${ classId } applied to element ${ elementId } successfully.`;
85
+ },
86
+ } );
87
+
88
+ server.addTool( {
89
+ name: 'unapply-global-class',
90
+ schema: {
91
+ classId: z.string().describe( 'The ID of the class to unapply' ),
92
+ elementId: z.string().describe( 'The ID of the element from which the class will be unapplied' ),
93
+ },
94
+ description: `Unapply a (global) class from the current element
95
+
96
+ ## When to use this tool:
97
+ - When a user requests to unapply a global class or a class from an element in the Elementor editor.
98
+ - When you need to remove a specific class from an element's applied classes.
99
+
100
+ ## Prerequisites:
101
+ - Ensure you have the most up-to-date list of classes applied to the element to avoid errors. You can use the "list-global-classes" tool to fetch the all classes applied to all elements.
102
+ - Make sure you have the correct class ID that you want to unapply.
103
+
104
+ <note>
105
+ If the user want to unapply a class by it's name and not ID, please use the "list-global-classes" tool to get the class ID from the name first.
106
+ </note>
107
+ `,
108
+ handler: async ( params ) => {
109
+ const { classId, elementId } = params;
110
+ const ok = doUnapplyClass( elementId, classId );
111
+ if ( ! ok ) {
112
+ throw new Error( `Class ${ classId } is not applied to element ${ elementId }, cannot unapply it.` );
113
+ }
114
+ return `Class ${ classId } unapplied from element ${ elementId } successfully.`;
115
+ },
116
+ } );
117
+ }
@@ -0,0 +1,72 @@
1
+ import { type MCPRegistryEntry } from '@elementor/editor-mcp';
2
+ import { z } from '@elementor/schema';
3
+
4
+ import { fetchCssClassUsage } from '../../service/css-class-usage-service';
5
+ import { type CssClassUsageContent, type EnhancedCssClassUsageContent } from '../components/css-class-usage/types';
6
+
7
+ export default function initMcpApplyGetGlobalClassUsages( reg: MCPRegistryEntry ) {
8
+ const { addTool } = reg;
9
+ const globalClassesUsageSchema = {
10
+ usages: z.array(
11
+ z.object( {
12
+ classId: z
13
+ .string()
14
+ .describe(
15
+ 'The ID of the class, not visible to the user. To retreive the name of the class, use the "list-global-classes" tool'
16
+ ),
17
+ usages: z.array(
18
+ z.object( {
19
+ pageId: z.string().describe( 'The ID of the page where the class is used' ),
20
+ title: z.string().describe( 'The title of the page where the class is used' ),
21
+ total: z.number().describe( 'The number of times the class is used on this page' ),
22
+ elements: z.array( z.string() ).describe( 'List of element IDs using this class on the page' ),
23
+ } )
24
+ ),
25
+ } )
26
+ ),
27
+ };
28
+
29
+ addTool( {
30
+ name: 'get-global-class-usages',
31
+ description: `Retreive the usage details of global classes within the Elementor editor, accross all pages.
32
+
33
+ ## Prequisites:
34
+ - Use "list-global-classes" tool to be able to match class IDs to class names/labels.
35
+
36
+ ## When to use this tool:
37
+ - When a user requests to see where a specific global class is being used accross the site.
38
+ - When you need to manage or clean up unused global classes.
39
+ - Before deleting a global class, to ensure it is not in use in any other pages.
40
+ `,
41
+ outputSchema: globalClassesUsageSchema,
42
+ handler: async () => {
43
+ const data = await fetchCssClassUsage();
44
+ const result = {
45
+ usages: [] as z.infer< ( typeof globalClassesUsageSchema )[ 'usages' ] >,
46
+ };
47
+
48
+ Object.entries( data as unknown as EnhancedCssClassUsageContent ).forEach(
49
+ ( [ classId, usageDetails ] ) => {
50
+ const newEntry: ( typeof result )[ 'usages' ][ 0 ] = {
51
+ classId,
52
+ usages: [],
53
+ };
54
+ if ( typeof usageDetails !== 'number' ) {
55
+ const { content } = usageDetails as unknown as { content: CssClassUsageContent[] };
56
+ content.forEach( ( detail ) => {
57
+ newEntry.usages.push( {
58
+ pageId: String( detail.pageId ),
59
+ title: detail.title,
60
+ total: detail.total,
61
+ elements: detail.elements,
62
+ } );
63
+ } );
64
+ result.usages.push( newEntry );
65
+ }
66
+ }
67
+ );
68
+
69
+ return result;
70
+ },
71
+ } );
72
+ }
@@ -0,0 +1,55 @@
1
+ import * as React from 'react';
2
+ import { openDialog } from '@elementor/editor-ui';
3
+ import { __dispatch as dispatch, __getState as getState } from '@elementor/store';
4
+ import { hash } from '@elementor/utils';
5
+
6
+ import { API_ERROR_CODES, apiClient, type ApiContext } from './api';
7
+ import { DuplicateLabelDialog } from './components/class-manager/duplicate-label-dialog';
8
+ import { type GlobalClasses, selectData, selectFrontendInitialData, selectPreviewInitialData, slice } from './store';
9
+ import { trackGlobalClasses } from './utils/tracking';
10
+
11
+ type Options = {
12
+ context: ApiContext;
13
+ onApprove?: () => void;
14
+ };
15
+
16
+ export async function saveGlobalClasses( { context, onApprove }: Options ) {
17
+ const state = selectData( getState() );
18
+ const apiAction = context === 'preview' ? apiClient.saveDraft : apiClient.publish;
19
+ const currentContext = context === 'preview' ? selectPreviewInitialData : selectFrontendInitialData;
20
+ const response = await apiAction( {
21
+ items: state.items,
22
+ order: state.order,
23
+ changes: calculateChanges( state, currentContext( getState() ) ),
24
+ } );
25
+
26
+ dispatch( slice.actions.reset( { context } ) );
27
+ if ( response?.data?.data?.code === API_ERROR_CODES.DUPLICATED_LABEL ) {
28
+ dispatch( slice.actions.updateMultiple( response.data.data.modifiedLabels ) );
29
+ trackGlobalClasses( {
30
+ event: 'classPublishConflict',
31
+ numOfConflicts: Object.keys( response.data.data.modifiedLabels ).length,
32
+ } );
33
+ openDialog( {
34
+ component: (
35
+ <DuplicateLabelDialog
36
+ modifiedLabels={ response.data.data.modifiedLabels || [] }
37
+ onApprove={ onApprove }
38
+ />
39
+ ),
40
+ } );
41
+ }
42
+ }
43
+
44
+ function calculateChanges( state: GlobalClasses, initialData: GlobalClasses ) {
45
+ const stateIds = Object.keys( state.items );
46
+ const initialDataIds = Object.keys( initialData.items );
47
+
48
+ return {
49
+ added: stateIds.filter( ( id ) => ! initialDataIds.includes( id ) ),
50
+ deleted: initialDataIds.filter( ( id ) => ! stateIds.includes( id ) ),
51
+ modified: stateIds.filter( ( id ) => {
52
+ return id in initialData.items && hash( state.items[ id ] ) !== hash( initialData.items[ id ] );
53
+ } ),
54
+ };
55
+ }
package/src/store.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { mergeProps, type Props } from '@elementor/editor-props';
1
+ import { type Props } from '@elementor/editor-props';
2
2
  import {
3
3
  type CustomCss,
4
4
  getVariantByMeta,
@@ -32,6 +32,13 @@ type GlobalClassesState = {
32
32
  isDirty: boolean;
33
33
  };
34
34
 
35
+ export type ModifiedLabels = {
36
+ [ id: string ]: {
37
+ original: string;
38
+ modified: string;
39
+ };
40
+ };
41
+
35
42
  const localHistory = SnapshotHistory.get< GlobalClasses >( 'global-classes' );
36
43
 
37
44
  const initialState: GlobalClassesState = {
@@ -108,6 +115,14 @@ export const slice = createSlice( {
108
115
  state.isDirty = true;
109
116
  },
110
117
 
118
+ updateMultiple( state, { payload }: PayloadAction< ModifiedLabels > ) {
119
+ localHistory.next( state.data );
120
+ Object.entries( payload ).forEach( ( [ id, { modified } ] ) => {
121
+ state.data.items[ id ].label = modified;
122
+ } );
123
+
124
+ state.isDirty = false;
125
+ },
111
126
  updateProps(
112
127
  state,
113
128
  {
@@ -186,6 +201,22 @@ export const slice = createSlice( {
186
201
  },
187
202
  } );
188
203
 
204
+ const mergeProps = ( current: Props, updates: Props ): Props => {
205
+ // edge case, the server returns an array instead of an object when empty props because of PHP array / object conversion
206
+ const props = Array.isArray( current ) ? {} : current;
207
+
208
+ Object.entries( updates ).forEach( ( [ key, value ] ) => {
209
+ if ( value === null || value === undefined ) {
210
+ // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
211
+ delete props[ key ];
212
+ } else {
213
+ props[ key ] = value;
214
+ }
215
+ } );
216
+
217
+ return props;
218
+ };
219
+
189
220
  const getNonEmptyVariants = ( style: StyleDefinition ) => {
190
221
  return style.variants.filter(
191
222
  ( { props, custom_css: customCss }: StyleDefinitionVariant ) => Object.keys( props ).length || customCss?.raw
@@ -7,10 +7,10 @@ import { UPDATE_CLASS_CAPABILITY_KEY } from './capabilities';
7
7
  import { saveGlobalClasses } from './save-global-classes';
8
8
  import { selectIsDirty } from './store';
9
9
 
10
- export function syncWithDocumentSave() {
10
+ export function syncWithDocumentSave( panelActions?: { open: () => void } ) {
11
11
  const unsubscribe = syncDirtyState();
12
12
 
13
- bindSaveAction();
13
+ bindSaveAction( panelActions );
14
14
 
15
15
  return unsubscribe;
16
16
  }
@@ -25,19 +25,22 @@ function syncDirtyState() {
25
25
  } );
26
26
  }
27
27
 
28
- function bindSaveAction() {
29
- registerDataHook( 'after', 'document/save/save', ( args ) => {
28
+ function bindSaveAction( panelActions?: { open: () => void } ) {
29
+ registerDataHook( 'dependency', 'document/save/save', ( args ) => {
30
30
  const user = getCurrentUser();
31
31
 
32
32
  const canEdit = user?.capabilities.includes( UPDATE_CLASS_CAPABILITY_KEY );
33
33
 
34
34
  if ( ! canEdit ) {
35
- return;
35
+ return true;
36
36
  }
37
37
 
38
- return saveGlobalClasses( {
38
+ saveGlobalClasses( {
39
39
  context: args.status === 'publish' ? 'frontend' : 'preview',
40
+ onApprove: panelActions?.open,
40
41
  } );
42
+
43
+ return true;
41
44
  } );
42
45
  }
43
46
 
@@ -0,0 +1,19 @@
1
+ import { useEffect } from 'react';
2
+ import { __privateListenTo as listenTo, v1ReadyEvent } from '@elementor/editor-v1-adapters';
3
+
4
+ import { usePanelActions } from './components/class-manager/class-manager-panel';
5
+ import { syncWithDocumentSave } from './sync-with-document-save';
6
+
7
+ export function SyncWithDocumentSave() {
8
+ const panelActions = usePanelActions();
9
+
10
+ useEffect( () => {
11
+ listenTo( v1ReadyEvent(), () => {
12
+ syncWithDocumentSave( panelActions );
13
+ } );
14
+
15
+ // eslint-disable-next-line react-hooks/exhaustive-deps
16
+ }, [] );
17
+
18
+ return null;
19
+ }