@elementor/editor-global-classes 0.14.1 → 0.15.4

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.
@@ -1,10 +1,6 @@
1
1
  import * as React from 'react';
2
- import { useEffect, useState } from 'react';
3
- import {
4
- __useActiveDocument as useActiveDocument,
5
- getDocumentModifiedStatus,
6
- setDocumentModifiedStatus,
7
- } from '@elementor/editor-documents';
2
+ import { useEffect } from 'react';
3
+ import { setDocumentModifiedStatus } from '@elementor/editor-documents';
8
4
  import {
9
5
  __createPanel as createPanel,
10
6
  Panel,
@@ -23,8 +19,10 @@ import { __ } from '@wordpress/i18n';
23
19
  import { useDirtyState } from '../../hooks/use-dirty-state';
24
20
  import { saveGlobalClasses } from '../../save-global-classes';
25
21
  import { ClassManagerIntroduction } from './class-manager-introduction';
22
+ import { hasDeletedItems, onDelete } from './delete-class';
26
23
  import { FlippedColorSwatchIcon } from './flipped-color-swatch-icon';
27
24
  import { GlobalClassesList } from './global-classes-list';
25
+ import { blockPanelInteractions, unblockPanelInteractions } from './panel-interactions';
28
26
  import { SaveChangesDialog, useDialog } from './save-changes-dialog';
29
27
 
30
28
  const id = 'global-classes-manager';
@@ -40,14 +38,12 @@ export const { panel, usePanelActions } = createPanel( {
40
38
  onOpen: () => {
41
39
  changeEditMode( id );
42
40
 
43
- return getDocumentModifiedStatus();
41
+ blockPanelInteractions();
44
42
  },
45
- onClose: ( documentModifiedState ) => {
43
+ onClose: () => {
46
44
  changeEditMode( 'edit' );
47
45
 
48
- if ( getDocumentModifiedStatus() !== documentModifiedState ) {
49
- setDocumentModifiedStatus( documentModifiedState );
50
- }
46
+ unblockPanelInteractions();
51
47
  },
52
48
  } );
53
49
 
@@ -62,81 +58,78 @@ export function ClassManagerPanel() {
62
58
  usePreventUnload();
63
59
 
64
60
  return (
65
- <>
66
- <ThemeProvider>
67
- <ErrorBoundary fallback={ <ErrorBoundaryFallback /> }>
68
- <Panel>
69
- <PanelHeader>
70
- <Stack p={ 1 } pl={ 2 } width="100%" direction="row" alignItems="center">
71
- <PanelHeaderTitle sx={ { display: 'flex', alignItems: 'center', gap: 0.5 } }>
72
- <FlippedColorSwatchIcon fontSize="inherit" />
73
- { __( 'CSS Class manager', 'elementor' ) }
74
- </PanelHeaderTitle>
75
- <CloseButton
76
- sx={ { marginLeft: 'auto' } }
77
- onClose={ () => {
78
- if ( isDirty ) {
79
- openSaveChangesDialog();
80
- return;
81
- }
82
-
83
- closePanel();
84
- } }
85
- />
86
- </Stack>
87
- </PanelHeader>
88
- <PanelBody px={ 2 }>
89
- <GlobalClassesList disabled={ isPublishing } />
90
- </PanelBody>
91
- <PanelFooter>
92
- <Button
93
- fullWidth
94
- size="small"
95
- color="global"
96
- variant="contained"
97
- onClick={ publish }
98
- disabled={ ! isDirty }
99
- loading={ isPublishing }
100
- >
101
- { __( 'Save changes', 'elementor' ) }
102
- </Button>
103
- </PanelFooter>
104
- </Panel>
105
- </ErrorBoundary>
106
- <ClassManagerIntroduction />
107
- { isSaveChangesDialogOpen && (
108
- <SaveChangesDialog>
109
- <SaveChangesDialog.Title>
110
- { __( 'You have unsaved changes', 'elementor' ) }
111
- </SaveChangesDialog.Title>
112
- <SaveChangesDialog.Content>
113
- <SaveChangesDialog.ContentText>
114
- { __( 'You have unsaved changes in the Class Manager.', 'elementor' ) }
115
- </SaveChangesDialog.ContentText>
116
- <SaveChangesDialog.ContentText>
117
- { __( 'To avoid losing your updates, save your changes before leaving.', 'elementor' ) }
118
- </SaveChangesDialog.ContentText>
119
- </SaveChangesDialog.Content>
120
- <SaveChangesDialog.Actions
121
- actions={ {
122
- cancel: {
123
- label: __( 'Cancel', 'elementor' ),
124
- action: closeSaveChangesDialog,
125
- },
126
- confirm: {
127
- label: __( 'Save & Continue', 'elementor' ),
128
- action: async () => {
129
- await publish();
130
- closeSaveChangesDialog();
131
- closePanel();
132
- },
61
+ <ThemeProvider>
62
+ <ErrorBoundary fallback={ <ErrorBoundaryFallback /> }>
63
+ <Panel>
64
+ <PanelHeader>
65
+ <Stack p={ 1 } pl={ 2 } width="100%" direction="row" alignItems="center">
66
+ <PanelHeaderTitle sx={ { display: 'flex', alignItems: 'center', gap: 0.5 } }>
67
+ <FlippedColorSwatchIcon fontSize="inherit" />
68
+ { __( 'Class Manager', 'elementor' ) }
69
+ </PanelHeaderTitle>
70
+ <CloseButton
71
+ sx={ { marginLeft: 'auto' } }
72
+ disabled={ isPublishing }
73
+ onClose={ () => {
74
+ if ( isDirty ) {
75
+ openSaveChangesDialog();
76
+ return;
77
+ }
78
+
79
+ closePanel();
80
+ } }
81
+ />
82
+ </Stack>
83
+ </PanelHeader>
84
+ <PanelBody px={ 2 }>
85
+ <GlobalClassesList disabled={ isPublishing } />
86
+ </PanelBody>
87
+ <PanelFooter>
88
+ <Button
89
+ fullWidth
90
+ size="small"
91
+ color="global"
92
+ variant="contained"
93
+ onClick={ publish }
94
+ disabled={ ! isDirty }
95
+ loading={ isPublishing }
96
+ >
97
+ { __( 'Save changes', 'elementor' ) }
98
+ </Button>
99
+ </PanelFooter>
100
+ </Panel>
101
+ </ErrorBoundary>
102
+ <ClassManagerIntroduction />
103
+ { isSaveChangesDialogOpen && (
104
+ <SaveChangesDialog>
105
+ <SaveChangesDialog.Title>{ __( 'You have unsaved changes', 'elementor' ) }</SaveChangesDialog.Title>
106
+ <SaveChangesDialog.Content>
107
+ <SaveChangesDialog.ContentText>
108
+ { __( 'You have unsaved changes in the Class Manager.', 'elementor' ) }
109
+ </SaveChangesDialog.ContentText>
110
+ <SaveChangesDialog.ContentText>
111
+ { __( 'To avoid losing your updates, save your changes before leaving.', 'elementor' ) }
112
+ </SaveChangesDialog.ContentText>
113
+ </SaveChangesDialog.Content>
114
+ <SaveChangesDialog.Actions
115
+ actions={ {
116
+ cancel: {
117
+ label: __( 'Cancel', 'elementor' ),
118
+ action: closeSaveChangesDialog,
119
+ },
120
+ confirm: {
121
+ label: __( 'Save & Continue', 'elementor' ),
122
+ action: async () => {
123
+ await publish();
124
+ closeSaveChangesDialog();
125
+ closePanel();
133
126
  },
134
- } }
135
- />
136
- </SaveChangesDialog>
137
- ) }
138
- </ThemeProvider>
139
- </>
127
+ },
128
+ } }
129
+ />
130
+ </SaveChangesDialog>
131
+ ) }
132
+ </ThemeProvider>
140
133
  );
141
134
  }
142
135
 
@@ -173,14 +166,13 @@ const usePreventUnload = () => {
173
166
  };
174
167
 
175
168
  const usePublish = () => {
176
- const document = useActiveDocument();
177
- const [ initialDocumentStatus ] = useState( document?.isDirty ?? false );
178
-
179
169
  return useMutation( {
180
170
  mutationFn: () => saveGlobalClasses( { context: 'frontend' } ),
181
- onSuccess: () => {
182
- if ( initialDocumentStatus !== document?.isDirty ) {
183
- setDocumentModifiedStatus( initialDocumentStatus );
171
+ onSuccess: async () => {
172
+ setDocumentModifiedStatus( false );
173
+
174
+ if ( hasDeletedItems() ) {
175
+ await onDelete();
184
176
  }
185
177
  },
186
178
  } );
@@ -0,0 +1,36 @@
1
+ import { getCurrentDocument, getV1DocumentsManager } from '@elementor/editor-documents';
2
+ import { __privateRunCommand as runCommand } from '@elementor/editor-v1-adapters';
3
+ import { __dispatch as dispatch } from '@elementor/store';
4
+
5
+ import { slice } from '../../store';
6
+
7
+ let isDeleted = false;
8
+
9
+ export const deleteClass = ( id: string ) => {
10
+ dispatch( slice.actions.delete( id ) );
11
+
12
+ isDeleted = true;
13
+ };
14
+
15
+ export const onDelete = async () => {
16
+ await reloadDocument();
17
+
18
+ isDeleted = false;
19
+ };
20
+
21
+ export const hasDeletedItems = () => isDeleted;
22
+
23
+ // When deleting a class, we remove it from all the documents that have it applied.
24
+ // In order to reflect the changes in the active document, we need to reload it.
25
+ const reloadDocument = () => {
26
+ const currentDocument = getCurrentDocument();
27
+ const documentsManager = getV1DocumentsManager();
28
+
29
+ documentsManager.invalidateCache();
30
+
31
+ return runCommand( 'editor/documents/switch', {
32
+ id: currentDocument?.id,
33
+ shouldScroll: false,
34
+ shouldNavigateToDefaultRoute: false,
35
+ } );
36
+ };
@@ -13,7 +13,7 @@ import {
13
13
  } from '@elementor/ui';
14
14
  import { __ } from '@wordpress/i18n';
15
15
 
16
- import { globalClassesStylesProvider } from '../../global-classes-styles-provider';
16
+ import { deleteClass } from './delete-class';
17
17
 
18
18
  type DeleteConfirmationDialogProps = Pick< StyleDefinition, 'id' | 'label' >;
19
19
 
@@ -50,7 +50,7 @@ const DeleteConfirmationDialog = ( { label, id }: DeleteConfirmationDialogProps
50
50
  const { closeDialog } = useDeleteConfirmation();
51
51
 
52
52
  const onConfirm = () => {
53
- globalClassesStylesProvider.actions.delete( id );
53
+ deleteClass( id );
54
54
 
55
55
  closeDialog();
56
56
  };
@@ -59,7 +59,7 @@ const DeleteConfirmationDialog = ( { label, id }: DeleteConfirmationDialogProps
59
59
  <Dialog open onClose={ closeDialog } aria-labelledby={ TITLE_ID } maxWidth="xs">
60
60
  <DialogTitle id={ TITLE_ID } display="flex" alignItems="center" gap={ 1 } sx={ { lineHeight: 1 } }>
61
61
  <AlertOctagonFilledIcon color="error" />
62
- { __( 'Delete global class', 'elementor' ) }
62
+ { __( 'Delete this class?', 'elementor' ) }
63
63
  </DialogTitle>
64
64
  <DialogContent>
65
65
  <DialogContentText variant="body2" color="textPrimary">
@@ -75,7 +75,7 @@ const DeleteConfirmationDialog = ( { label, id }: DeleteConfirmationDialogProps
75
75
  </DialogContent>
76
76
  <DialogActions>
77
77
  <Button color="secondary" onClick={ closeDialog }>
78
- { __( 'Cancel', 'elementor' ) }
78
+ { __( 'Not now', 'elementor' ) }
79
79
  </Button>
80
80
  <Button variant="contained" color="error" onClick={ onConfirm }>
81
81
  { __( 'Delete', 'elementor' ) }
@@ -1,8 +1,9 @@
1
1
  import * as React from 'react';
2
2
  import { type StyleDefinitionID } from '@elementor/editor-styles';
3
- import { stylesRepository } from '@elementor/editor-styles-repository';
3
+ import { validateStyleLabel } from '@elementor/editor-styles-repository';
4
4
  import { EditableField, EllipsisWithTooltip, MenuListItem, useEditable } from '@elementor/editor-ui';
5
5
  import { DotsVerticalIcon } from '@elementor/icons';
6
+ import { __useDispatch as useDispatch } from '@elementor/store';
6
7
  import {
7
8
  bindMenu,
8
9
  bindTrigger,
@@ -22,15 +23,16 @@ import {
22
23
  } from '@elementor/ui';
23
24
  import { __ } from '@wordpress/i18n';
24
25
 
25
- import { globalClassesStylesProvider } from '../../global-classes-styles-provider';
26
26
  import { useClassesOrder } from '../../hooks/use-classes-order';
27
27
  import { useOrderedClasses } from '../../hooks/use-ordered-classes';
28
+ import { slice } from '../../store';
28
29
  import { DeleteConfirmationProvider, useDeleteConfirmation } from './delete-confirmation-dialog';
29
30
  import { FlippedColorSwatchIcon } from './flipped-color-swatch-icon';
30
31
  import { SortableItem, SortableProvider, SortableTrigger, type SortableTriggerProps } from './sortable';
31
32
 
32
33
  export const GlobalClassesList = ( { disabled }: { disabled?: boolean } ) => {
33
34
  const cssClasses = useOrderedClasses();
35
+ const dispatch = useDispatch();
34
36
 
35
37
  const [ classesOrder, reorderClasses ] = useReorder();
36
38
 
@@ -44,7 +46,14 @@ export const GlobalClassesList = ( { disabled }: { disabled?: boolean } ) => {
44
46
  <SortableProvider value={ classesOrder } onChange={ reorderClasses }>
45
47
  { cssClasses?.map( ( { id, label } ) => {
46
48
  const renameClass = ( newLabel: string ) => {
47
- globalClassesStylesProvider.actions.update( { label: newLabel, id } );
49
+ dispatch(
50
+ slice.actions.update( {
51
+ style: {
52
+ id,
53
+ label: newLabel,
54
+ },
55
+ } )
56
+ );
48
57
  };
49
58
 
50
59
  return (
@@ -69,10 +78,11 @@ export const GlobalClassesList = ( { disabled }: { disabled?: boolean } ) => {
69
78
  };
70
79
 
71
80
  const useReorder = () => {
81
+ const dispatch = useDispatch();
72
82
  const order = useClassesOrder();
73
83
 
74
84
  const reorder = ( newIds: StyleDefinitionID[] ) => {
75
- globalClassesStylesProvider.actions.setOrder( newIds );
85
+ dispatch( slice.actions.setOrder( newIds ) );
76
86
  };
77
87
 
78
88
  return [ order, reorder ] as const;
@@ -257,13 +267,11 @@ const getIndicatorBorder = ( { isActive, isError, theme }: { isActive: boolean;
257
267
  };
258
268
 
259
269
  const validateLabel = ( newLabel: string ) => {
260
- if ( ! stylesRepository.isLabelValid( newLabel ) ) {
261
- return __( 'Format is not valid', 'elementor' );
262
- }
270
+ const result = validateStyleLabel( newLabel );
263
271
 
264
- if ( stylesRepository.isLabelExist( newLabel ) ) {
265
- return __( 'Existing name', 'elementor' );
272
+ if ( result.isValid ) {
273
+ return null;
266
274
  }
267
275
 
268
- return null;
276
+ return result.error;
269
277
  };
@@ -0,0 +1,24 @@
1
+ type ExtendedWindow = Window & {
2
+ $e?: {
3
+ components?: {
4
+ get?: ( name: 'panel' ) =>
5
+ | {
6
+ blockUserInteractions?: () => void;
7
+ unblockUserInteractions?: () => void;
8
+ }
9
+ | undefined;
10
+ };
11
+ };
12
+ };
13
+
14
+ export function blockPanelInteractions() {
15
+ const extendedWindow = window as unknown as ExtendedWindow;
16
+
17
+ extendedWindow.$e?.components?.get?.( 'panel' )?.blockUserInteractions?.();
18
+ }
19
+
20
+ export function unblockPanelInteractions() {
21
+ const extendedWindow = window as unknown as ExtendedWindow;
22
+
23
+ extendedWindow.$e?.components?.get?.( 'panel' )?.unblockUserInteractions?.();
24
+ }
@@ -1,5 +1,5 @@
1
1
  import { generateId } from '@elementor/editor-styles';
2
- import type { StylesProvider } from '@elementor/editor-styles-repository';
2
+ import { createStylesProvider } from '@elementor/editor-styles-repository';
3
3
  import {
4
4
  __dispatch as dispatch,
5
5
  __getState as getState,
@@ -12,13 +12,18 @@ import { selectClass, selectGlobalClasses, selectOrderedClasses, slice, type Sta
12
12
 
13
13
  const MAX_CLASSES = 50;
14
14
 
15
- export const globalClassesStylesProvider = {
15
+ export const globalClassesStylesProvider = createStylesProvider( {
16
16
  key: 'global-classes',
17
17
  priority: 30,
18
18
  limit: MAX_CLASSES,
19
+ labels: {
20
+ singular: __( 'class', 'elementor' ),
21
+ plural: __( 'classes', 'elementor' ),
22
+ },
23
+ subscribe: ( cb ) => subscribeWithSelector( ( state: StateWithGlobalClasses ) => state.globalClasses, cb ),
19
24
  actions: {
20
- get: () => selectOrderedClasses( getState() ),
21
- getById: ( id ) => selectClass( getState(), id ),
25
+ all: () => selectOrderedClasses( getState() ),
26
+ get: ( id ) => selectClass( getState(), id ),
22
27
  create: ( label ) => {
23
28
  const classes = selectGlobalClasses( getState() );
24
29
 
@@ -52,9 +57,6 @@ export const globalClassesStylesProvider = {
52
57
  delete: ( id ) => {
53
58
  dispatch( slice.actions.delete( id ) );
54
59
  },
55
- setOrder: ( order ) => {
56
- dispatch( slice.actions.setOrder( order ) );
57
- },
58
60
  updateProps: ( args ) => {
59
61
  dispatch(
60
62
  slice.actions.updateProps( {
@@ -65,9 +67,4 @@ export const globalClassesStylesProvider = {
65
67
  );
66
68
  },
67
69
  },
68
- subscribe: ( cb ) => subscribeWithSelector( ( state: StateWithGlobalClasses ) => state.globalClasses, cb ),
69
- labels: {
70
- singular: __( 'Global class', 'elementor' ),
71
- plural: __( 'Global CSS classes', 'elementor' ),
72
- },
73
- } satisfies StylesProvider;
70
+ } );
package/src/index.ts CHANGED
@@ -1,3 +1 @@
1
- import { init } from './init';
2
-
3
- init();
1
+ export { init } from './init';