@elementor/editor-global-classes 4.1.0-manual → 4.2.0-840
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/dist/index.d.mts +21 -1
- package/dist/index.d.ts +21 -1
- package/dist/index.js +1008 -700
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1011 -705
- package/dist/index.mjs.map +1 -1
- package/package.json +22 -21
- package/src/api.ts +32 -15
- package/src/components/class-manager/class-manager-button.tsx +16 -6
- package/src/components/class-manager/class-manager-panel.tsx +197 -101
- package/src/components/class-manager/global-classes-list.tsx +62 -14
- package/src/components/class-manager/sortable.tsx +9 -3
- package/src/components/global-styles-import-listener.tsx +10 -37
- package/src/components/populate-store.tsx +10 -24
- package/src/global-classes-styles-provider.ts +48 -15
- package/src/index.ts +9 -0
- package/src/init.ts +11 -5
- package/src/load-document-classes.ts +76 -0
- package/src/load-existing-classes.ts +49 -0
- package/src/mcp-integration/classes-resource.ts +3 -1
- package/src/mcp-integration/mcp-apply-unapply-global-classes.ts +1 -1
- package/src/mcp-integration/mcp-get-global-class-usages.ts +4 -4
- package/src/save-global-classes.tsx +17 -2
- package/src/store.ts +73 -7
- package/src/sync-with-document.tsx +13 -5
- package/src/utils/create-labels-for-classes.ts +7 -0
- package/src/utils/tracking.ts +14 -6
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elementor/editor-global-classes",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.2.0-840",
|
|
4
4
|
"private": false,
|
|
5
5
|
"author": "Elementor Team",
|
|
6
6
|
"homepage": "https://elementor.com/",
|
|
@@ -39,28 +39,29 @@
|
|
|
39
39
|
"dev": "tsup --config=../../tsup.dev.ts"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@elementor/editor": "4.
|
|
43
|
-
"@elementor/editor-current-user": "4.
|
|
44
|
-
"@elementor/editor-documents": "4.
|
|
45
|
-
"@elementor/editor-editing-panel": "4.
|
|
46
|
-
"@elementor/editor-mcp": "4.
|
|
47
|
-
"@elementor/editor-panels": "4.
|
|
48
|
-
"@elementor/editor-props": "4.
|
|
49
|
-
"@elementor/editor-variables": "4.
|
|
50
|
-
"@elementor/editor-styles": "4.
|
|
51
|
-
"@elementor/editor-canvas": "4.
|
|
52
|
-
"@elementor/editor-styles-repository": "4.
|
|
53
|
-
"@elementor/editor-ui": "4.
|
|
54
|
-
"@elementor/editor-v1-adapters": "4.
|
|
55
|
-
"@elementor/http-client": "4.
|
|
56
|
-
"@elementor/icons": "
|
|
57
|
-
"@elementor/query": "4.
|
|
58
|
-
"@elementor/schema": "4.
|
|
59
|
-
"@elementor/store": "4.
|
|
42
|
+
"@elementor/editor": "4.2.0-840",
|
|
43
|
+
"@elementor/editor-current-user": "4.2.0-840",
|
|
44
|
+
"@elementor/editor-documents": "4.2.0-840",
|
|
45
|
+
"@elementor/editor-editing-panel": "4.2.0-840",
|
|
46
|
+
"@elementor/editor-mcp": "4.2.0-840",
|
|
47
|
+
"@elementor/editor-panels": "4.2.0-840",
|
|
48
|
+
"@elementor/editor-props": "4.2.0-840",
|
|
49
|
+
"@elementor/editor-variables": "4.2.0-840",
|
|
50
|
+
"@elementor/editor-styles": "4.2.0-840",
|
|
51
|
+
"@elementor/editor-canvas": "4.2.0-840",
|
|
52
|
+
"@elementor/editor-styles-repository": "4.2.0-840",
|
|
53
|
+
"@elementor/editor-ui": "4.2.0-840",
|
|
54
|
+
"@elementor/editor-v1-adapters": "4.2.0-840",
|
|
55
|
+
"@elementor/http-client": "4.2.0-840",
|
|
56
|
+
"@elementor/icons": "~1.75.1",
|
|
57
|
+
"@elementor/query": "4.2.0-840",
|
|
58
|
+
"@elementor/schema": "4.2.0-840",
|
|
59
|
+
"@elementor/store": "4.2.0-840",
|
|
60
60
|
"@elementor/ui": "1.37.5",
|
|
61
|
-
"@elementor/utils": "4.
|
|
61
|
+
"@elementor/utils": "4.2.0-840",
|
|
62
|
+
"@tanstack/react-virtual": "^3.13.24",
|
|
62
63
|
"@wordpress/i18n": "^5.13.0",
|
|
63
|
-
"@elementor/events": "4.
|
|
64
|
+
"@elementor/events": "4.2.0-840"
|
|
64
65
|
},
|
|
65
66
|
"peerDependencies": {
|
|
66
67
|
"react": "^18.3.1",
|
package/src/api.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type StyleDefinition, type StyleDefinitionID
|
|
1
|
+
import { type StyleDefinition, type StyleDefinitionID } from '@elementor/editor-styles';
|
|
2
2
|
import { type HttpResponse, httpService } from '@elementor/http-client';
|
|
3
3
|
|
|
4
4
|
import { type CssClassUsage } from './components/css-class-usage/types';
|
|
@@ -7,13 +7,24 @@ import { type GlobalClasses } from './store';
|
|
|
7
7
|
const RESOURCE_URL = '/global-classes';
|
|
8
8
|
const BASE_URL = 'elementor/v1';
|
|
9
9
|
const RESOURCE_USAGE_URL = `${ RESOURCE_URL }/usage`;
|
|
10
|
+
const RESOURCE_POST_URL = `${ RESOURCE_URL }/post`;
|
|
11
|
+
const RESOURCE_STYLES_URL = `${ RESOURCE_URL }/styles`;
|
|
10
12
|
|
|
11
13
|
type GlobalClassesUsageResponse = HttpResponse< CssClassUsage >;
|
|
12
14
|
|
|
13
|
-
export type
|
|
14
|
-
|
|
15
|
+
export type GlobalClassIndexEntry = {
|
|
16
|
+
id: StyleDefinitionID;
|
|
17
|
+
label: string;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export type GlobalClassesIndexHttpResponse = HttpResponse< GlobalClassIndexEntry[], Record< string, never > >;
|
|
21
|
+
|
|
22
|
+
export type StyleDefinitionsNullableMap = Record< StyleDefinitionID, StyleDefinition | null >;
|
|
23
|
+
|
|
24
|
+
export type GlobalClassesStylesHttpResponse = HttpResponse<
|
|
25
|
+
StyleDefinitionsNullableMap,
|
|
15
26
|
{
|
|
16
|
-
order:
|
|
27
|
+
order: StyleDefinitionID[];
|
|
17
28
|
}
|
|
18
29
|
>;
|
|
19
30
|
|
|
@@ -27,27 +38,33 @@ type UpdatePayload = GlobalClasses & {
|
|
|
27
38
|
|
|
28
39
|
export type ApiContext = 'preview' | 'frontend';
|
|
29
40
|
|
|
41
|
+
function saveGlobalClasses( context: ApiContext, payload: UpdatePayload ) {
|
|
42
|
+
return httpService().put( `${ BASE_URL }${ RESOURCE_URL }`, payload, {
|
|
43
|
+
params: { context },
|
|
44
|
+
} );
|
|
45
|
+
}
|
|
46
|
+
|
|
30
47
|
export const apiClient = {
|
|
31
48
|
usage: () => httpService().get< GlobalClassesUsageResponse >( `${ BASE_URL }${ RESOURCE_USAGE_URL }` ),
|
|
32
49
|
|
|
33
50
|
all: ( context: ApiContext = 'preview' ) =>
|
|
34
|
-
httpService().get<
|
|
51
|
+
httpService().get< GlobalClassesIndexHttpResponse >( `${ BASE_URL }${ RESOURCE_URL }`, {
|
|
35
52
|
params: { context },
|
|
36
53
|
} ),
|
|
37
54
|
|
|
38
|
-
|
|
39
|
-
httpService().
|
|
40
|
-
params: {
|
|
41
|
-
context: 'frontend' satisfies ApiContext,
|
|
42
|
-
},
|
|
55
|
+
getStylesForPost: ( postId: number, context: ApiContext = 'preview' ) =>
|
|
56
|
+
httpService().get< GlobalClassesStylesHttpResponse >( `${ BASE_URL }${ RESOURCE_POST_URL }`, {
|
|
57
|
+
params: { context, post_id: postId },
|
|
43
58
|
} ),
|
|
44
59
|
|
|
45
|
-
|
|
46
|
-
httpService().
|
|
47
|
-
params: {
|
|
48
|
-
context: 'preview' satisfies ApiContext,
|
|
49
|
-
},
|
|
60
|
+
getStylesByIds: ( ids: StyleDefinitionID[], context: ApiContext = 'preview' ) =>
|
|
61
|
+
httpService().get< GlobalClassesStylesHttpResponse >( `${ BASE_URL }${ RESOURCE_STYLES_URL }`, {
|
|
62
|
+
params: { context, ids: ids.join( ',' ) },
|
|
50
63
|
} ),
|
|
64
|
+
|
|
65
|
+
publish: ( payload: UpdatePayload ) => saveGlobalClasses( 'frontend', payload ),
|
|
66
|
+
|
|
67
|
+
saveDraft: ( payload: UpdatePayload ) => saveGlobalClasses( 'preview', payload ),
|
|
51
68
|
};
|
|
52
69
|
|
|
53
70
|
export const API_ERROR_CODES = {
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
} from '@elementor/editor-documents';
|
|
6
6
|
import { useUserStylesCapability } from '@elementor/editor-styles-repository';
|
|
7
7
|
import { SaveChangesDialog, useDialog } from '@elementor/editor-ui';
|
|
8
|
+
import { isExperimentActive } from '@elementor/editor-v1-adapters';
|
|
8
9
|
import { IconButton, Tooltip } from '@elementor/ui';
|
|
9
10
|
import { __ } from '@wordpress/i18n';
|
|
10
11
|
|
|
@@ -36,18 +37,27 @@ export const ClassManagerButton = () => {
|
|
|
36
37
|
return null;
|
|
37
38
|
}
|
|
38
39
|
|
|
40
|
+
const toggleClassesManagerPanel = () => {
|
|
41
|
+
if ( isExperimentActive( 'e_editor_design_system_panel' ) ) {
|
|
42
|
+
window.dispatchEvent(
|
|
43
|
+
new CustomEvent( 'elementor/toggle-design-system', {
|
|
44
|
+
detail: { tab: 'classes' as const },
|
|
45
|
+
} )
|
|
46
|
+
);
|
|
47
|
+
} else {
|
|
48
|
+
openPanel();
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
|
|
39
52
|
const handleOpenPanel = () => {
|
|
40
53
|
if ( document?.isDirty ) {
|
|
41
54
|
openSaveChangesDialog();
|
|
42
55
|
return;
|
|
43
56
|
}
|
|
44
57
|
|
|
45
|
-
|
|
58
|
+
toggleClassesManagerPanel();
|
|
59
|
+
|
|
46
60
|
trackGlobalClassesButton();
|
|
47
|
-
trackGlobalClasses( {
|
|
48
|
-
event: 'classManagerOpened',
|
|
49
|
-
source: 'style-panel',
|
|
50
|
-
} );
|
|
51
61
|
prefetchClassesUsage();
|
|
52
62
|
};
|
|
53
63
|
|
|
@@ -80,7 +90,7 @@ export const ClassManagerButton = () => {
|
|
|
80
90
|
action: async () => {
|
|
81
91
|
await saveDocument();
|
|
82
92
|
closeSaveChangesDialog();
|
|
83
|
-
|
|
93
|
+
toggleClassesManagerPanel();
|
|
84
94
|
trackGlobalClassesButton();
|
|
85
95
|
prefetchClassesUsage();
|
|
86
96
|
},
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import { useCallback, useEffect, useState } from 'react';
|
|
2
|
+
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
3
3
|
import { useSuppressedMessage } from '@elementor/editor-current-user';
|
|
4
|
-
import {
|
|
4
|
+
import { reloadCurrentDocument, setDocumentModifiedStatus } from '@elementor/editor-documents';
|
|
5
5
|
import {
|
|
6
6
|
__createPanel as createPanel,
|
|
7
7
|
Panel,
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
PanelHeaderTitle,
|
|
12
12
|
} from '@elementor/editor-panels';
|
|
13
13
|
import { ConfirmationDialog, SaveChangesDialog, ThemeProvider, useDialog } from '@elementor/editor-ui';
|
|
14
|
-
import {
|
|
14
|
+
import { changeEditMode } from '@elementor/editor-v1-adapters';
|
|
15
15
|
import { XIcon } from '@elementor/icons';
|
|
16
16
|
import { useMutation } from '@elementor/query';
|
|
17
17
|
import { __dispatch as dispatch } from '@elementor/store';
|
|
@@ -56,23 +56,25 @@ type StopSyncConfirmationDialogProps = {
|
|
|
56
56
|
|
|
57
57
|
const id = 'global-classes-manager';
|
|
58
58
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
59
|
+
export type ClassManagerPanelEmbeddedProps = {
|
|
60
|
+
onRequestClose: () => void | Promise< void >;
|
|
61
|
+
onExposeCloseAttempt?: ( attemptClose: ( () => void ) | null ) => void;
|
|
62
|
+
};
|
|
62
63
|
|
|
63
|
-
|
|
64
|
+
export function ClassManagerPanelEmbedded( { onRequestClose, onExposeCloseAttempt }: ClassManagerPanelEmbeddedProps ) {
|
|
65
|
+
return (
|
|
66
|
+
<ClassManagerPanelRoot
|
|
67
|
+
embedded
|
|
68
|
+
onRequestClose={ onRequestClose }
|
|
69
|
+
onExposeCloseAttempt={ onExposeCloseAttempt }
|
|
70
|
+
/>
|
|
71
|
+
);
|
|
72
|
+
}
|
|
64
73
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
shouldNavigateToDefaultRoute: false,
|
|
69
|
-
} );
|
|
70
|
-
};
|
|
74
|
+
export function ClassManagerPanel() {
|
|
75
|
+
return <ClassManagerPanelRoot />;
|
|
76
|
+
}
|
|
71
77
|
|
|
72
|
-
// We need to disable the app-bar buttons, and the elements overlays when opening the classes manager panel.
|
|
73
|
-
// The buttons and overlays are enabled only in edit mode, so we're creating a custom new edit mode that
|
|
74
|
-
// will force them to be disabled. We can't use the `preview` edit mode in this case since it'll force
|
|
75
|
-
// the panel to be closed.
|
|
76
78
|
export const { panel, usePanelActions } = createPanel( {
|
|
77
79
|
id,
|
|
78
80
|
component: ClassManagerPanel,
|
|
@@ -84,19 +86,35 @@ export const { panel, usePanelActions } = createPanel( {
|
|
|
84
86
|
},
|
|
85
87
|
onClose: async () => {
|
|
86
88
|
changeEditMode( 'edit' );
|
|
87
|
-
await
|
|
89
|
+
await reloadCurrentDocument();
|
|
88
90
|
unblockPanelInteractions();
|
|
89
91
|
},
|
|
90
92
|
isOpenPreviousElement: true,
|
|
91
93
|
} );
|
|
92
94
|
|
|
93
|
-
|
|
95
|
+
type ClassManagerPanelRootProps = {
|
|
96
|
+
embedded?: boolean;
|
|
97
|
+
onRequestClose?: () => void | Promise< void >;
|
|
98
|
+
onExposeCloseAttempt?: ( attemptClose: ( () => void ) | null ) => void;
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
function ClassManagerPanelRoot( {
|
|
102
|
+
embedded = false,
|
|
103
|
+
onRequestClose,
|
|
104
|
+
onExposeCloseAttempt,
|
|
105
|
+
}: ClassManagerPanelRootProps = {} ) {
|
|
94
106
|
const isDirty = useDirtyState();
|
|
95
|
-
const { close:
|
|
107
|
+
const { close: closeStandalonePanel } = usePanelActions();
|
|
108
|
+
const closePanel = useMemo(
|
|
109
|
+
() => ( embedded ? onRequestClose ?? ( async () => {} ) : closeStandalonePanel ),
|
|
110
|
+
[ embedded, onRequestClose, closeStandalonePanel ]
|
|
111
|
+
);
|
|
112
|
+
|
|
96
113
|
const { open: openSaveChangesDialog, close: closeSaveChangesDialog, isOpen: isSaveChangesDialogOpen } = useDialog();
|
|
97
114
|
const [ stopSyncConfirmation, setStopSyncConfirmation ] = useState< string | null >( null );
|
|
98
115
|
const [ startSyncConfirmation, setStartSyncConfirmation ] = useState< string | null >( null );
|
|
99
116
|
const [ isStopSyncSuppressed ] = useSuppressedMessage( STOP_SYNC_MESSAGE_KEY );
|
|
117
|
+
const [ scrollElement, setScrollElement ] = useState< HTMLElement | null >( null );
|
|
100
118
|
|
|
101
119
|
const { mutateAsync: publish, isPending: isPublishing } = usePublish();
|
|
102
120
|
|
|
@@ -105,6 +123,37 @@ export function ClassManagerPanel() {
|
|
|
105
123
|
closeSaveChangesDialog();
|
|
106
124
|
};
|
|
107
125
|
|
|
126
|
+
const handleClosePanel = useCallback( () => {
|
|
127
|
+
if ( isDirty ) {
|
|
128
|
+
openSaveChangesDialog();
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
void closePanel();
|
|
133
|
+
}, [ isDirty, openSaveChangesDialog, closePanel ] );
|
|
134
|
+
|
|
135
|
+
useEffect( () => {
|
|
136
|
+
if ( ! embedded || ! onExposeCloseAttempt ) {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
onExposeCloseAttempt( () => handleClosePanel() );
|
|
141
|
+
|
|
142
|
+
return () => onExposeCloseAttempt( null );
|
|
143
|
+
}, [ embedded, onExposeCloseAttempt, handleClosePanel ] );
|
|
144
|
+
|
|
145
|
+
useEffect( () => {
|
|
146
|
+
if ( ! embedded ) {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
blockPanelInteractions();
|
|
151
|
+
|
|
152
|
+
return () => {
|
|
153
|
+
unblockPanelInteractions();
|
|
154
|
+
};
|
|
155
|
+
}, [ embedded ] );
|
|
156
|
+
|
|
108
157
|
const handleStopSync = useCallback( ( classId: string ) => {
|
|
109
158
|
dispatch(
|
|
110
159
|
slice.actions.update( {
|
|
@@ -144,83 +193,56 @@ export function ClassManagerPanel() {
|
|
|
144
193
|
|
|
145
194
|
usePreventUnload();
|
|
146
195
|
|
|
147
|
-
|
|
148
|
-
<
|
|
149
|
-
<
|
|
150
|
-
<
|
|
151
|
-
<
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
} }
|
|
198
|
-
>
|
|
199
|
-
<GlobalClassesList
|
|
200
|
-
disabled={ isPublishing }
|
|
201
|
-
onStopSyncRequest={ handleStopSyncRequest }
|
|
202
|
-
onStartSyncRequest={ ( classId ) => setStartSyncConfirmation( classId ) }
|
|
203
|
-
/>
|
|
204
|
-
</Box>
|
|
205
|
-
</PanelBody>
|
|
206
|
-
|
|
207
|
-
<PanelFooter>
|
|
208
|
-
<Button
|
|
209
|
-
fullWidth
|
|
210
|
-
size="small"
|
|
211
|
-
color="global"
|
|
212
|
-
variant="contained"
|
|
213
|
-
onClick={ publish }
|
|
214
|
-
disabled={ ! isDirty }
|
|
215
|
-
loading={ isPublishing }
|
|
216
|
-
>
|
|
217
|
-
{ __( 'Save changes', 'elementor' ) }
|
|
218
|
-
</Button>
|
|
219
|
-
</PanelFooter>
|
|
220
|
-
</SearchAndFilterProvider>
|
|
221
|
-
</Panel>
|
|
222
|
-
</ErrorBoundary>
|
|
223
|
-
<ClassManagerIntroduction />
|
|
196
|
+
const searchFiltersBlock = (
|
|
197
|
+
<Box px={ 2 } pb={ 1 }>
|
|
198
|
+
<Stack direction="row" alignItems="center" justifyContent="space-between" gap={ 0.5 } sx={ { pb: 0.5 } }>
|
|
199
|
+
<Box sx={ embedded ? { flexGrow: 1, minWidth: 0 } : { flexGrow: 1 } }>
|
|
200
|
+
<ClassManagerSearch />
|
|
201
|
+
</Box>
|
|
202
|
+
<CssClassFilter />
|
|
203
|
+
{ embedded && <TotalCssClassCounter /> }
|
|
204
|
+
</Stack>
|
|
205
|
+
<ActiveFilters />
|
|
206
|
+
</Box>
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
const listArea = (
|
|
210
|
+
<Box
|
|
211
|
+
ref={ setScrollElement }
|
|
212
|
+
px={ 2 }
|
|
213
|
+
sx={ {
|
|
214
|
+
flexGrow: 1,
|
|
215
|
+
overflowY: 'auto',
|
|
216
|
+
...( embedded ? { minHeight: 0 } : {} ),
|
|
217
|
+
} }
|
|
218
|
+
>
|
|
219
|
+
<GlobalClassesList
|
|
220
|
+
disabled={ isPublishing }
|
|
221
|
+
scrollElement={ scrollElement }
|
|
222
|
+
onStopSyncRequest={ handleStopSyncRequest }
|
|
223
|
+
onStartSyncRequest={ ( classId ) => setStartSyncConfirmation( classId ) }
|
|
224
|
+
/>
|
|
225
|
+
</Box>
|
|
226
|
+
);
|
|
227
|
+
|
|
228
|
+
const saveFooter = (
|
|
229
|
+
<PanelFooter>
|
|
230
|
+
<Button
|
|
231
|
+
fullWidth
|
|
232
|
+
size="small"
|
|
233
|
+
color="global"
|
|
234
|
+
variant="contained"
|
|
235
|
+
onClick={ publish }
|
|
236
|
+
disabled={ ! isDirty }
|
|
237
|
+
loading={ isPublishing }
|
|
238
|
+
>
|
|
239
|
+
{ __( 'Save changes', 'elementor' ) }
|
|
240
|
+
</Button>
|
|
241
|
+
</PanelFooter>
|
|
242
|
+
);
|
|
243
|
+
|
|
244
|
+
const dialogs = (
|
|
245
|
+
<>
|
|
224
246
|
{ startSyncConfirmation && (
|
|
225
247
|
<StartSyncToV3Modal
|
|
226
248
|
externalOpen
|
|
@@ -264,19 +286,93 @@ export function ClassManagerPanel() {
|
|
|
264
286
|
action: async () => {
|
|
265
287
|
await publish();
|
|
266
288
|
closeSaveChangesDialog();
|
|
267
|
-
closePanel();
|
|
289
|
+
void closePanel();
|
|
268
290
|
},
|
|
269
291
|
},
|
|
270
292
|
} }
|
|
271
293
|
/>
|
|
272
294
|
</SaveChangesDialog>
|
|
273
295
|
) }
|
|
274
|
-
|
|
296
|
+
</>
|
|
297
|
+
);
|
|
298
|
+
|
|
299
|
+
const classManagerLayout = embedded ? (
|
|
300
|
+
<Stack
|
|
301
|
+
direction="column"
|
|
302
|
+
sx={ {
|
|
303
|
+
height: '100%',
|
|
304
|
+
width: '100%',
|
|
305
|
+
flex: 1,
|
|
306
|
+
minHeight: 0,
|
|
307
|
+
overflow: 'hidden',
|
|
308
|
+
} }
|
|
309
|
+
>
|
|
310
|
+
{ searchFiltersBlock }
|
|
311
|
+
<Divider />
|
|
312
|
+
{ listArea }
|
|
313
|
+
{ saveFooter }
|
|
314
|
+
</Stack>
|
|
315
|
+
) : (
|
|
316
|
+
<Panel>
|
|
317
|
+
<PanelHeader>
|
|
318
|
+
<Stack p={ 1 } pl={ 2 } width="100%" direction="row" alignItems="center">
|
|
319
|
+
<Stack width="100%" direction="row" gap={ 1 }>
|
|
320
|
+
<PanelHeaderTitle sx={ { display: 'flex', alignItems: 'center', gap: 0.5 } }>
|
|
321
|
+
<FlippedColorSwatchIcon fontSize="inherit" />
|
|
322
|
+
{ __( 'Class Manager', 'elementor' ) }
|
|
323
|
+
</PanelHeaderTitle>
|
|
324
|
+
<TotalCssClassCounter />
|
|
325
|
+
</Stack>
|
|
326
|
+
<ClassPanelCloseButton
|
|
327
|
+
disabled={ isPublishing }
|
|
328
|
+
onClose={ () => {
|
|
329
|
+
if ( isDirty ) {
|
|
330
|
+
openSaveChangesDialog();
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
void closeStandalonePanel();
|
|
335
|
+
} }
|
|
336
|
+
/>
|
|
337
|
+
</Stack>
|
|
338
|
+
</PanelHeader>
|
|
339
|
+
<PanelBody
|
|
340
|
+
sx={ {
|
|
341
|
+
display: 'flex',
|
|
342
|
+
flexDirection: 'column',
|
|
343
|
+
height: '100%',
|
|
344
|
+
} }
|
|
345
|
+
>
|
|
346
|
+
{ searchFiltersBlock }
|
|
347
|
+
<Divider />
|
|
348
|
+
{ listArea }
|
|
349
|
+
</PanelBody>
|
|
350
|
+
{ saveFooter }
|
|
351
|
+
</Panel>
|
|
275
352
|
);
|
|
353
|
+
|
|
354
|
+
const core = (
|
|
355
|
+
<>
|
|
356
|
+
<ErrorBoundary fallback={ <ErrorBoundaryFallback /> }>
|
|
357
|
+
<SearchAndFilterProvider>{ classManagerLayout }</SearchAndFilterProvider>
|
|
358
|
+
</ErrorBoundary>
|
|
359
|
+
<ClassManagerIntroduction />
|
|
360
|
+
{ dialogs }
|
|
361
|
+
</>
|
|
362
|
+
);
|
|
363
|
+
|
|
364
|
+
return embedded ? core : <ThemeProvider>{ core }</ThemeProvider>;
|
|
276
365
|
}
|
|
277
366
|
|
|
278
|
-
const
|
|
279
|
-
<IconButton
|
|
367
|
+
const ClassPanelCloseButton = ( { onClose, sx, ...props }: IconButtonProps & { onClose: () => void } ) => (
|
|
368
|
+
<IconButton
|
|
369
|
+
size="small"
|
|
370
|
+
color="secondary"
|
|
371
|
+
onClick={ onClose }
|
|
372
|
+
aria-label="Close"
|
|
373
|
+
sx={ { marginLeft: 'auto', ...sx } }
|
|
374
|
+
{ ...props }
|
|
375
|
+
>
|
|
280
376
|
<XIcon fontSize="small" />
|
|
281
377
|
</IconButton>
|
|
282
378
|
);
|
|
@@ -3,6 +3,7 @@ import { useEffect, useMemo, useState } from 'react';
|
|
|
3
3
|
import { type StyleDefinition, type StyleDefinitionID } from '@elementor/editor-styles';
|
|
4
4
|
import { __useDispatch as useDispatch } from '@elementor/store';
|
|
5
5
|
import { List, Stack, styled, Typography, type TypographyProps } from '@elementor/ui';
|
|
6
|
+
import { defaultRangeExtractor, useVirtualizer } from '@tanstack/react-virtual';
|
|
6
7
|
import { __ } from '@wordpress/i18n';
|
|
7
8
|
|
|
8
9
|
import { useClassesOrder } from '../../hooks/use-classes-order';
|
|
@@ -17,13 +18,22 @@ import { FlippedColorSwatchIcon } from './flipped-color-swatch-icon';
|
|
|
17
18
|
import { getNotFoundType, NotFound } from './not-found';
|
|
18
19
|
import { SortableItem, SortableProvider } from './sortable';
|
|
19
20
|
|
|
21
|
+
const ROW_HEIGHT = 40;
|
|
22
|
+
const OVERSCAN = 6;
|
|
23
|
+
|
|
20
24
|
type GlobalClassesListProps = {
|
|
21
25
|
disabled?: boolean;
|
|
26
|
+
scrollElement?: HTMLElement | null;
|
|
22
27
|
onStopSyncRequest?: ( id: string ) => void;
|
|
23
28
|
onStartSyncRequest?: ( id: string ) => void;
|
|
24
29
|
};
|
|
25
30
|
|
|
26
|
-
export const GlobalClassesList = ( {
|
|
31
|
+
export const GlobalClassesList = ( {
|
|
32
|
+
disabled,
|
|
33
|
+
scrollElement,
|
|
34
|
+
onStopSyncRequest,
|
|
35
|
+
onStartSyncRequest,
|
|
36
|
+
}: GlobalClassesListProps ) => {
|
|
27
37
|
const {
|
|
28
38
|
search: { debouncedValue: searchValue },
|
|
29
39
|
} = useSearchAndFilters();
|
|
@@ -35,6 +45,27 @@ export const GlobalClassesList = ( { disabled, onStopSyncRequest, onStartSyncReq
|
|
|
35
45
|
const [ classesOrder, reorderClasses ] = useReorder( draggedItemId, setDraggedItemId, draggedItemLabel ?? '' );
|
|
36
46
|
const filteredCssClasses = useFilteredCssClasses();
|
|
37
47
|
|
|
48
|
+
const virtualizer = useVirtualizer( {
|
|
49
|
+
count: filteredCssClasses.length,
|
|
50
|
+
getScrollElement: () => scrollElement ?? null,
|
|
51
|
+
estimateSize: () => ROW_HEIGHT,
|
|
52
|
+
overscan: OVERSCAN,
|
|
53
|
+
getItemKey: ( index ) => filteredCssClasses[ index ].id,
|
|
54
|
+
// Keep the actively dragged row mounted even when scrolled out of view.
|
|
55
|
+
// SortableItem unregisters its render on unmount, which would make the
|
|
56
|
+
// DragOverlay clone disappear mid-drag.
|
|
57
|
+
rangeExtractor: ( range ) => {
|
|
58
|
+
const indices = new Set( defaultRangeExtractor( range ) );
|
|
59
|
+
if ( draggedItemId ) {
|
|
60
|
+
const draggedItemIndex = filteredCssClasses.findIndex( ( cssClass ) => cssClass.id === draggedItemId );
|
|
61
|
+
if ( draggedItemIndex >= 0 ) {
|
|
62
|
+
indices.add( draggedItemIndex );
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return [ ...indices ].sort( ( a, b ) => a - b );
|
|
66
|
+
},
|
|
67
|
+
} );
|
|
68
|
+
|
|
38
69
|
useEffect( () => {
|
|
39
70
|
const handler = ( event: KeyboardEvent ) => {
|
|
40
71
|
if ( event.key === 'z' && ( event.ctrlKey || event.metaKey ) ) {
|
|
@@ -69,19 +100,36 @@ export const GlobalClassesList = ( { disabled, onStopSyncRequest, onStartSyncReq
|
|
|
69
100
|
|
|
70
101
|
return (
|
|
71
102
|
<DeleteConfirmationProvider>
|
|
72
|
-
<List
|
|
103
|
+
<List
|
|
104
|
+
sx={ {
|
|
105
|
+
position: 'relative',
|
|
106
|
+
display: 'block',
|
|
107
|
+
height: virtualizer.getTotalSize(),
|
|
108
|
+
padding: 0,
|
|
109
|
+
} }
|
|
110
|
+
>
|
|
73
111
|
<SortableProvider
|
|
74
112
|
value={ classesOrder }
|
|
75
113
|
onChange={ reorderClasses }
|
|
114
|
+
onDragStart={ ( event ) => setDraggedItemId( event.active.id as StyleDefinitionID ) }
|
|
115
|
+
onDragEnd={ () => setDraggedItemId( null ) }
|
|
116
|
+
onDragCancel={ () => setDraggedItemId( null ) }
|
|
76
117
|
disableDragOverlay={ ! allowSorting }
|
|
77
118
|
>
|
|
78
|
-
{
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
}
|
|
84
|
-
|
|
119
|
+
{ virtualizer.getVirtualItems().map( ( virtualRow ) => {
|
|
120
|
+
const cssClass = filteredCssClasses[ virtualRow.index ];
|
|
121
|
+
return (
|
|
122
|
+
<SortableItem
|
|
123
|
+
key={ virtualRow.key }
|
|
124
|
+
id={ cssClass.id }
|
|
125
|
+
style={ {
|
|
126
|
+
position: 'absolute',
|
|
127
|
+
top: virtualRow.start,
|
|
128
|
+
left: 0,
|
|
129
|
+
width: '100%',
|
|
130
|
+
} }
|
|
131
|
+
>
|
|
132
|
+
{ ( { isDragged, isDragPlaceholder, triggerProps, triggerStyle } ) => (
|
|
85
133
|
<ClassItem
|
|
86
134
|
id={ cssClass.id }
|
|
87
135
|
label={ cssClass.label }
|
|
@@ -127,10 +175,10 @@ export const GlobalClassesList = ( { disabled, onStopSyncRequest, onStartSyncReq
|
|
|
127
175
|
}
|
|
128
176
|
} }
|
|
129
177
|
/>
|
|
130
|
-
)
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
178
|
+
) }
|
|
179
|
+
</SortableItem>
|
|
180
|
+
);
|
|
181
|
+
} ) }
|
|
134
182
|
</SortableProvider>
|
|
135
183
|
</List>
|
|
136
184
|
</DeleteConfirmationProvider>
|
|
@@ -176,7 +224,7 @@ const useReorder = (
|
|
|
176
224
|
classId: draggedItemId,
|
|
177
225
|
classTitle: draggedItemLabel,
|
|
178
226
|
} );
|
|
179
|
-
setDraggedItemId( null );
|
|
227
|
+
setDraggedItemId( null );
|
|
180
228
|
}
|
|
181
229
|
};
|
|
182
230
|
|