@elementor/editor-global-classes 4.1.0-821 → 4.1.0-823

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-821",
3
+ "version": "4.1.0-823",
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-821",
43
- "@elementor/editor-current-user": "4.1.0-821",
44
- "@elementor/editor-documents": "4.1.0-821",
45
- "@elementor/editor-editing-panel": "4.1.0-821",
46
- "@elementor/editor-mcp": "4.1.0-821",
47
- "@elementor/editor-panels": "4.1.0-821",
48
- "@elementor/editor-props": "4.1.0-821",
49
- "@elementor/editor-variables": "4.1.0-821",
50
- "@elementor/editor-styles": "4.1.0-821",
51
- "@elementor/editor-canvas": "4.1.0-821",
52
- "@elementor/editor-styles-repository": "4.1.0-821",
53
- "@elementor/editor-ui": "4.1.0-821",
54
- "@elementor/editor-v1-adapters": "4.1.0-821",
55
- "@elementor/http-client": "4.1.0-821",
42
+ "@elementor/editor": "4.1.0-823",
43
+ "@elementor/editor-current-user": "4.1.0-823",
44
+ "@elementor/editor-documents": "4.1.0-823",
45
+ "@elementor/editor-editing-panel": "4.1.0-823",
46
+ "@elementor/editor-mcp": "4.1.0-823",
47
+ "@elementor/editor-panels": "4.1.0-823",
48
+ "@elementor/editor-props": "4.1.0-823",
49
+ "@elementor/editor-variables": "4.1.0-823",
50
+ "@elementor/editor-styles": "4.1.0-823",
51
+ "@elementor/editor-canvas": "4.1.0-823",
52
+ "@elementor/editor-styles-repository": "4.1.0-823",
53
+ "@elementor/editor-ui": "4.1.0-823",
54
+ "@elementor/editor-v1-adapters": "4.1.0-823",
55
+ "@elementor/http-client": "4.1.0-823",
56
56
  "@elementor/icons": "^1.68.0",
57
- "@elementor/query": "4.1.0-821",
58
- "@elementor/schema": "4.1.0-821",
59
- "@elementor/store": "4.1.0-821",
57
+ "@elementor/query": "4.1.0-823",
58
+ "@elementor/schema": "4.1.0-823",
59
+ "@elementor/store": "4.1.0-823",
60
60
  "@elementor/ui": "1.37.5",
61
- "@elementor/utils": "4.1.0-821",
61
+ "@elementor/utils": "4.1.0-823",
62
+ "@tanstack/react-virtual": "^3.13.24",
62
63
  "@wordpress/i18n": "^5.13.0",
63
- "@elementor/events": "4.1.0-821"
64
+ "@elementor/events": "4.1.0-823"
64
65
  },
65
66
  "peerDependencies": {
66
67
  "react": "^18.3.1",
@@ -97,6 +97,7 @@ export function ClassManagerPanel() {
97
97
  const [ stopSyncConfirmation, setStopSyncConfirmation ] = useState< string | null >( null );
98
98
  const [ startSyncConfirmation, setStartSyncConfirmation ] = useState< string | null >( null );
99
99
  const [ isStopSyncSuppressed ] = useSuppressedMessage( STOP_SYNC_MESSAGE_KEY );
100
+ const [ scrollElement, setScrollElement ] = useState< HTMLElement | null >( null );
100
101
 
101
102
  const { mutateAsync: publish, isPending: isPublishing } = usePublish();
102
103
 
@@ -190,6 +191,7 @@ export function ClassManagerPanel() {
190
191
  </Box>
191
192
  <Divider />
192
193
  <Box
194
+ ref={ setScrollElement }
193
195
  px={ 2 }
194
196
  sx={ {
195
197
  flexGrow: 1,
@@ -198,6 +200,7 @@ export function ClassManagerPanel() {
198
200
  >
199
201
  <GlobalClassesList
200
202
  disabled={ isPublishing }
203
+ scrollElement={ scrollElement }
201
204
  onStopSyncRequest={ handleStopSyncRequest }
202
205
  onStartSyncRequest={ ( classId ) => setStartSyncConfirmation( classId ) }
203
206
  />
@@ -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={ {