@elementor/editor-global-classes 4.1.0-822 → 4.1.0-824

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.1.0-822",
3
+ "version": "4.1.0-824",
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.1.0-822",
43
- "@elementor/editor-current-user": "4.1.0-822",
44
- "@elementor/editor-documents": "4.1.0-822",
45
- "@elementor/editor-editing-panel": "4.1.0-822",
46
- "@elementor/editor-mcp": "4.1.0-822",
47
- "@elementor/editor-panels": "4.1.0-822",
48
- "@elementor/editor-props": "4.1.0-822",
49
- "@elementor/editor-variables": "4.1.0-822",
50
- "@elementor/editor-styles": "4.1.0-822",
51
- "@elementor/editor-canvas": "4.1.0-822",
52
- "@elementor/editor-styles-repository": "4.1.0-822",
53
- "@elementor/editor-ui": "4.1.0-822",
54
- "@elementor/editor-v1-adapters": "4.1.0-822",
55
- "@elementor/http-client": "4.1.0-822",
42
+ "@elementor/editor": "4.1.0-824",
43
+ "@elementor/editor-current-user": "4.1.0-824",
44
+ "@elementor/editor-documents": "4.1.0-824",
45
+ "@elementor/editor-editing-panel": "4.1.0-824",
46
+ "@elementor/editor-mcp": "4.1.0-824",
47
+ "@elementor/editor-panels": "4.1.0-824",
48
+ "@elementor/editor-props": "4.1.0-824",
49
+ "@elementor/editor-variables": "4.1.0-824",
50
+ "@elementor/editor-styles": "4.1.0-824",
51
+ "@elementor/editor-canvas": "4.1.0-824",
52
+ "@elementor/editor-styles-repository": "4.1.0-824",
53
+ "@elementor/editor-ui": "4.1.0-824",
54
+ "@elementor/editor-v1-adapters": "4.1.0-824",
55
+ "@elementor/http-client": "4.1.0-824",
56
56
  "@elementor/icons": "^1.68.0",
57
- "@elementor/query": "4.1.0-822",
58
- "@elementor/schema": "4.1.0-822",
59
- "@elementor/store": "4.1.0-822",
57
+ "@elementor/query": "4.1.0-824",
58
+ "@elementor/schema": "4.1.0-824",
59
+ "@elementor/store": "4.1.0-824",
60
60
  "@elementor/ui": "1.37.5",
61
- "@elementor/utils": "4.1.0-822",
61
+ "@elementor/utils": "4.1.0-824",
62
+ "@tanstack/react-virtual": "^3.13.24",
62
63
  "@wordpress/i18n": "^5.13.0",
63
- "@elementor/events": "4.1.0-822"
64
+ "@elementor/events": "4.1.0-824"
64
65
  },
65
66
  "peerDependencies": {
66
67
  "react": "^18.3.1",
@@ -1,7 +1,7 @@
1
1
  import * as React from 'react';
2
2
  import { useCallback, useEffect, useState } from 'react';
3
3
  import { useSuppressedMessage } from '@elementor/editor-current-user';
4
- import { getCurrentDocument, getV1DocumentsManager, setDocumentModifiedStatus } from '@elementor/editor-documents';
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 { __privateRunCommand as runCommand, changeEditMode } from '@elementor/editor-v1-adapters';
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,19 +56,6 @@ type StopSyncConfirmationDialogProps = {
56
56
 
57
57
  const id = 'global-classes-manager';
58
58
 
59
- const reloadDocument = () => {
60
- const currentDocument = getCurrentDocument();
61
- const documentsManager = getV1DocumentsManager();
62
-
63
- documentsManager.invalidateCache();
64
-
65
- return runCommand( 'editor/documents/switch', {
66
- id: currentDocument?.id,
67
- shouldScroll: false,
68
- shouldNavigateToDefaultRoute: false,
69
- } );
70
- };
71
-
72
59
  // We need to disable the app-bar buttons, and the elements overlays when opening the classes manager panel.
73
60
  // The buttons and overlays are enabled only in edit mode, so we're creating a custom new edit mode that
74
61
  // will force them to be disabled. We can't use the `preview` edit mode in this case since it'll force
@@ -84,7 +71,7 @@ export const { panel, usePanelActions } = createPanel( {
84
71
  },
85
72
  onClose: async () => {
86
73
  changeEditMode( 'edit' );
87
- await reloadDocument();
74
+ await reloadCurrentDocument();
88
75
  unblockPanelInteractions();
89
76
  },
90
77
  isOpenPreviousElement: true,
@@ -97,6 +84,7 @@ export function ClassManagerPanel() {
97
84
  const [ stopSyncConfirmation, setStopSyncConfirmation ] = useState< string | null >( null );
98
85
  const [ startSyncConfirmation, setStartSyncConfirmation ] = useState< string | null >( null );
99
86
  const [ isStopSyncSuppressed ] = useSuppressedMessage( STOP_SYNC_MESSAGE_KEY );
87
+ const [ scrollElement, setScrollElement ] = useState< HTMLElement | null >( null );
100
88
 
101
89
  const { mutateAsync: publish, isPending: isPublishing } = usePublish();
102
90
 
@@ -190,6 +178,7 @@ export function ClassManagerPanel() {
190
178
  </Box>
191
179
  <Divider />
192
180
  <Box
181
+ ref={ setScrollElement }
193
182
  px={ 2 }
194
183
  sx={ {
195
184
  flexGrow: 1,
@@ -198,6 +187,7 @@ export function ClassManagerPanel() {
198
187
  >
199
188
  <GlobalClassesList
200
189
  disabled={ isPublishing }
190
+ scrollElement={ scrollElement }
201
191
  onStopSyncRequest={ handleStopSyncRequest }
202
192
  onStartSyncRequest={ ( classId ) => setStartSyncConfirmation( classId ) }
203
193
  />
@@ -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 = ( { disabled, onStopSyncRequest, onStartSyncRequest }: GlobalClassesListProps ) => {
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 sx={ { display: 'flex', flexDirection: 'column', gap: 0.5 } }>
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
- { filteredCssClasses?.map( ( cssClass ) => (
79
- <SortableItem key={ cssClass.id } id={ cssClass.id }>
80
- { ( { isDragged, isDragPlaceholder, triggerProps, triggerStyle } ) => {
81
- if ( isDragged && ! draggedItemId ) {
82
- setDraggedItemId( cssClass.id );
83
- }
84
- return (
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
- </SortableItem>
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 ); // Reset after tracking
227
+ setDraggedItemId( null );
180
228
  }
181
229
  };
182
230
 
@@ -11,7 +11,12 @@ import {
11
11
  } from '@elementor/ui';
12
12
 
13
13
  export const SortableProvider = < T extends string >( props: UnstableSortableProviderProps< T > ) => (
14
- <UnstableSortableProvider restrictAxis variant="static" dragPlaceholderStyle={ { opacity: '1' } } { ...props } />
14
+ <UnstableSortableProvider
15
+ restrictAxis
16
+ variant="static"
17
+ dragPlaceholderStyle={ { visibility: 'hidden' } }
18
+ { ...props }
19
+ />
15
20
  );
16
21
 
17
22
  export type SortableTriggerProps = React.HTMLAttributes< HTMLDivElement >;
@@ -24,10 +29,11 @@ export const SortableTrigger = ( props: SortableTriggerProps ) => (
24
29
 
25
30
  type SortableItemProps = {
26
31
  id: UnstableSortableItemProps[ 'id' ];
32
+ style?: React.CSSProperties;
27
33
  children: ( props: Partial< UnstableSortableItemRenderProps > ) => React.ReactNode;
28
34
  };
29
35
 
30
- export const SortableItem = ( { children, id, ...props }: SortableItemProps ) => {
36
+ export const SortableItem = ( { children, id, style, ...props }: SortableItemProps ) => {
31
37
  return (
32
38
  <UnstableSortableItem
33
39
  { ...props }
@@ -46,7 +52,7 @@ export const SortableItem = ( { children, id, ...props }: SortableItemProps ) =>
46
52
  return (
47
53
  <Box
48
54
  { ...itemProps }
49
- style={ itemStyle }
55
+ style={ { ...itemStyle, ...( ! isDragOverlay ? style : null ) } }
50
56
  component={ 'li' }
51
57
  role="listitem"
52
58
  sx={ {
@@ -1,4 +1,5 @@
1
1
  import { useEffect } from 'react';
2
+ import { GLOBAL_STYLES_IMPORTED_EVENT } from '@elementor/editor-canvas';
2
3
  import { __useDispatch as useDispatch } from '@elementor/store';
3
4
 
4
5
  import { apiClient } from '../api';
@@ -47,13 +48,10 @@ export function GlobalStylesImportListener() {
47
48
  .catch( () => {} );
48
49
  };
49
50
 
50
- window.addEventListener( 'elementor/global-styles/imported', handleGlobalStylesImported as EventListener );
51
+ window.addEventListener( GLOBAL_STYLES_IMPORTED_EVENT, handleGlobalStylesImported as EventListener );
51
52
 
52
53
  return () => {
53
- window.removeEventListener(
54
- 'elementor/global-styles/imported',
55
- handleGlobalStylesImported as EventListener
56
- );
54
+ window.removeEventListener( GLOBAL_STYLES_IMPORTED_EVENT, handleGlobalStylesImported as EventListener );
57
55
  };
58
56
  }, [ dispatch ] );
59
57