@elementor/editor-global-classes 0.11.0 → 0.12.0

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.
@@ -9,8 +9,8 @@ import {
9
9
  Box,
10
10
  IconButton,
11
11
  List,
12
- ListItem,
13
12
  ListItemButton,
13
+ type ListItemButtonProps,
14
14
  ListItemText,
15
15
  Menu,
16
16
  MenuItem,
@@ -29,9 +29,9 @@ import { useClassesOrder } from '../../hooks/use-classes-order';
29
29
  import { useOrderedClasses } from '../../hooks/use-ordered-classes';
30
30
  import { DeleteConfirmationProvider, useDeleteConfirmation } from './delete-confirmation-dialog';
31
31
  import { FlippedColorSwatchIcon } from './flipped-color-swatch-icon';
32
- import { SortableItem, SortableItemIndicator, SortableProvider } from './sortable';
32
+ import { SortableItem, SortableProvider, SortableTrigger, type SortableTriggerProps } from './sortable';
33
33
 
34
- export const GlobalClassesList = () => {
34
+ export const GlobalClassesList = ( { disabled }: { disabled?: boolean } ) => {
35
35
  const cssClasses = useOrderedClasses();
36
36
 
37
37
  const [ classesOrder, reorderClasses ] = useReorder();
@@ -51,18 +51,15 @@ export const GlobalClassesList = () => {
51
51
 
52
52
  return (
53
53
  <SortableItem key={ id } id={ id }>
54
- { ( { isDragged, showDropIndication, dropIndicationStyle, isDragPlaceholder } ) => (
54
+ { ( { isDragged, isDragPlaceholder, triggerProps, triggerStyle } ) => (
55
55
  <ClassItem
56
56
  id={ id }
57
57
  label={ label }
58
58
  renameClass={ renameClass }
59
59
  selected={ isDragged }
60
- disabled={ isDragPlaceholder }
61
- >
62
- { showDropIndication && (
63
- <SortableItemIndicator style={ dropIndicationStyle } />
64
- ) }
65
- </ClassItem>
60
+ disabled={ disabled || isDragPlaceholder }
61
+ sortableTriggerProps={ { ...triggerProps, style: triggerStyle } }
62
+ />
66
63
  ) }
67
64
  </SortableItem>
68
65
  );
@@ -83,20 +80,16 @@ const useReorder = () => {
83
80
  return [ order, reorder ] as const;
84
81
  };
85
82
 
86
- const ClassItem = ( {
87
- id,
88
- label,
89
- renameClass,
90
- selected,
91
- children,
92
- disabled,
93
- }: React.PropsWithChildren< {
83
+ type ClassItemProps = React.PropsWithChildren< {
94
84
  id: string;
95
85
  label: string;
96
86
  renameClass: ( newLabel: string ) => void;
97
- selected: boolean;
98
- disabled: boolean;
99
- } > ) => {
87
+ selected?: boolean;
88
+ disabled?: boolean;
89
+ sortableTriggerProps: SortableTriggerProps;
90
+ } >;
91
+
92
+ const ClassItem = ( { id, label, renameClass, selected, disabled, sortableTriggerProps }: ClassItemProps ) => {
100
93
  const {
101
94
  ref: editableRef,
102
95
  openEditMode,
@@ -116,99 +109,103 @@ const ClassItem = ( {
116
109
  disableAutoFocus: true,
117
110
  } );
118
111
 
112
+ const isSelected = ( selected || popupState.isOpen ) && ! disabled;
113
+
119
114
  return (
120
- <Stack direction="row" alignItems="center" gap={ 1 } flexGrow={ 1 } flexShrink={ 0 }>
121
- <StyledListItem
122
- component={ 'div' }
123
- disablePadding
115
+ <>
116
+ <StyledListItemButton
117
+ dense
124
118
  disableGutters
125
- secondaryAction={
126
- <Tooltip
127
- placement="top"
128
- className="class-item-more-actions"
129
- title={ __( 'More actions', 'elementor' ) }
130
- >
131
- <IconButton size="tiny" { ...bindTrigger( popupState ) } aria-label="More actions">
132
- <DotsVerticalIcon fontSize="tiny" />
133
- </IconButton>
134
- </Tooltip>
135
- }
119
+ showActions={ isSelected || isEditing }
120
+ shape="rounded"
121
+ onDoubleClick={ openEditMode }
122
+ selected={ isSelected }
123
+ disabled={ disabled }
124
+ focusVisibleClassName="visible-class-item"
136
125
  >
137
- <ListItemButton
138
- dense
139
- disableGutters
140
- shape="rounded"
141
- onDoubleClick={ openEditMode }
142
- selected={ selected || popupState.isOpen }
143
- disabled={ disabled }
144
- focusVisibleClassName="visible-class-item"
145
- sx={ {
146
- minHeight: '36px',
147
- display: 'flex',
148
- '&.visible-class-item': {
149
- boxShadow: 'none !important',
150
- },
151
- } }
126
+ <SortableTrigger { ...sortableTriggerProps } />
127
+ <Indicator isActive={ isEditing } isError={ !! error }>
128
+ { isEditing ? (
129
+ <EditableField
130
+ ref={ editableRef }
131
+ error={ error }
132
+ as={ Typography }
133
+ variant="caption"
134
+ { ...getEditableProps() }
135
+ />
136
+ ) : (
137
+ <EllipsisWithTooltip title={ label } as={ Typography } variant="caption" />
138
+ ) }
139
+ </Indicator>
140
+ <Tooltip
141
+ placement="top"
142
+ className={ 'class-item-more-actions' }
143
+ title={ __( 'More actions', 'elementor' ) }
152
144
  >
153
- <Indicator isActive={ isEditing } isError={ !! error }>
154
- { isEditing ? (
155
- <EditableField
156
- ref={ editableRef }
157
- error={ error }
158
- as={ Typography }
159
- variant="caption"
160
- { ...getEditableProps() }
161
- />
162
- ) : (
163
- <EllipsisWithTooltip title={ label } as={ Typography } variant="caption" />
164
- ) }
165
- </Indicator>
166
- </ListItemButton>
167
- { children }
168
- <Menu
169
- { ...bindMenu( popupState ) }
170
- anchorOrigin={ {
171
- vertical: 'bottom',
172
- horizontal: 'right',
145
+ <IconButton size="tiny" { ...bindTrigger( popupState ) } aria-label="More actions">
146
+ <DotsVerticalIcon fontSize="tiny" />
147
+ </IconButton>
148
+ </Tooltip>
149
+ </StyledListItemButton>
150
+ <Menu
151
+ { ...bindMenu( popupState ) }
152
+ anchorOrigin={ {
153
+ vertical: 'bottom',
154
+ horizontal: 'right',
155
+ } }
156
+ transformOrigin={ {
157
+ vertical: 'top',
158
+ horizontal: 'right',
159
+ } }
160
+ >
161
+ <MenuItem
162
+ sx={ { minWidth: '160px' } }
163
+ onClick={ () => {
164
+ popupState.close();
165
+ openEditMode();
173
166
  } }
174
- transformOrigin={ {
175
- vertical: 'top',
176
- horizontal: 'right',
167
+ >
168
+ <ListItemText primary={ __( 'Rename', 'elementor' ) } />
169
+ </MenuItem>
170
+ <MenuItem
171
+ onClick={ () => {
172
+ popupState.close();
173
+ openDialog( { id, label } );
177
174
  } }
178
175
  >
179
- <MenuItem
180
- sx={ { minWidth: '160px' } }
181
- onClick={ () => {
182
- popupState.close();
183
- openEditMode();
184
- } }
185
- >
186
- <ListItemText primary={ __( 'Rename', 'elementor' ) } />
187
- </MenuItem>
188
- <MenuItem
189
- onClick={ () => {
190
- popupState.close();
191
- openDialog( { id, label } );
192
- } }
193
- >
194
- <ListItemText primary={ __( 'Delete', 'elementor' ) } sx={ { color: 'error.light' } } />
195
- </MenuItem>
196
- </Menu>
197
- </StyledListItem>
198
- </Stack>
176
+ <ListItemText primary={ __( 'Delete', 'elementor' ) } sx={ { color: 'error.light' } } />
177
+ </MenuItem>
178
+ </Menu>
179
+ </>
199
180
  );
200
181
  };
201
182
 
202
- const StyledListItem = styled( ListItem )`
203
- .class-item-more-actions {
204
- visibility: hidden;
183
+ // Custom styles for sortable list item, until the component is available in the UI package.
184
+ const StyledListItemButton = styled( ListItemButton, {
185
+ shouldForwardProp: ( prop: string ) => ! [ 'showActions' ].includes( prop ),
186
+ } )< ListItemButtonProps & { showActions: boolean } >(
187
+ ( { showActions } ) => `
188
+ min-height: 36px;
189
+
190
+ &.visible-class-item {
191
+ box-shadow: none !important;
205
192
  }
206
- &:hover {
207
- .class-item-more-actions {
193
+
194
+ .class-item-more-actions, .class-item-sortable-trigger {
195
+ visibility: ${ showActions ? 'visible' : 'hidden' };
196
+ }
197
+
198
+ .class-item-sortable-trigger {
199
+ visibility: ${ showActions ? 'visible' : 'hidden' };
200
+ }
201
+
202
+ &:hover&:not(:disabled) {
203
+ .class-item-more-actions, .class-item-sortable-trigger {
208
204
  visibility: visible;
209
205
  }
210
206
  }
211
- `;
207
+ `
208
+ );
212
209
 
213
210
  const EmptyState = () => (
214
211
  <Stack alignItems="center" gap={ 1.5 } pt={ 10 } px={ 0.5 } maxWidth="260px" margin="auto">
@@ -242,6 +239,7 @@ const Indicator = styled( Box, {
242
239
  border: getIndicatorBorder( { isActive, isError, theme } ),
243
240
  padding: `0 ${ theme.spacing( 1 ) }`,
244
241
  marginLeft: isActive ? theme.spacing( 1 ) : 0,
242
+ minWidth: 0,
245
243
  } ) );
246
244
 
247
245
  const getIndicatorBorder = ( { isActive, isError, theme }: { isActive: boolean; isError: boolean; theme: Theme } ) => {
@@ -2,7 +2,6 @@ import * as React from 'react';
2
2
  import { GripVerticalIcon } from '@elementor/icons';
3
3
  import {
4
4
  Box,
5
- Paper,
6
5
  styled,
7
6
  UnstableSortableItem,
8
7
  type UnstableSortableItemProps,
@@ -15,22 +14,17 @@ export const SortableProvider = < T extends string >( props: UnstableSortablePro
15
14
  <UnstableSortableProvider restrictAxis variant="static" dragPlaceholderStyle={ { opacity: '1' } } { ...props } />
16
15
  );
17
16
 
18
- const SortableTrigger = ( props: React.HTMLAttributes< HTMLDivElement > ) => (
19
- <div { ...props } role="button" className="class-item-sortable-trigger">
17
+ export type SortableTriggerProps = React.HTMLAttributes< HTMLDivElement >;
18
+
19
+ export const SortableTrigger = ( props: SortableTriggerProps ) => (
20
+ <StyledSortableTrigger { ...props } role="button" className="class-item-sortable-trigger">
20
21
  <GripVerticalIcon fontSize="tiny" />
21
- </div>
22
+ </StyledSortableTrigger>
22
23
  );
23
24
 
24
- type ItemRenderProps = Record< string, unknown > & {
25
- isDragged: boolean;
26
- showDropIndication: boolean;
27
- dropIndicationStyle: React.CSSProperties;
28
- isDragPlaceholder: boolean;
29
- };
30
-
31
25
  type SortableItemProps = {
32
26
  id: UnstableSortableItemProps[ 'id' ];
33
- children: ( props: ItemRenderProps ) => React.ReactNode;
27
+ children: ( props: Partial< UnstableSortableItemRenderProps > ) => React.ReactNode;
34
28
  };
35
29
 
36
30
  export const SortableItem = ( { children, id, ...props }: SortableItemProps ) => {
@@ -50,51 +44,41 @@ export const SortableItem = ( { children, id, ...props }: SortableItemProps ) =>
50
44
  isDragPlaceholder,
51
45
  }: UnstableSortableItemRenderProps ) => {
52
46
  return (
53
- <StyledSortableItem
47
+ <Box
54
48
  { ...itemProps }
55
- sx={ itemStyle }
49
+ style={ itemStyle }
50
+ component={ 'li' }
56
51
  role="listitem"
57
- { ...( isDragOverlay ? { component: Paper, elevation: 0 } : {} ) }
52
+ sx={ {
53
+ backgroundColor: isDragOverlay ? 'background.paper' : undefined,
54
+ } }
58
55
  >
59
- <SortableTrigger { ...triggerProps } style={ triggerStyle } />
60
56
  { children( {
61
57
  itemProps,
62
58
  isDragged,
63
59
  triggerProps,
64
60
  itemStyle,
65
61
  triggerStyle,
66
- dropIndicationStyle,
67
- showDropIndication,
68
62
  isDragPlaceholder,
69
63
  } ) }
70
- </StyledSortableItem>
64
+ { showDropIndication && <SortableItemIndicator style={ dropIndicationStyle } /> }
65
+ </Box>
71
66
  );
72
67
  } }
73
68
  />
74
69
  );
75
70
  };
76
71
 
77
- const StyledSortableItem = styled( Box )`
78
- position: relative;
79
-
80
- &:hover {
81
- & .class-item-sortable-trigger {
82
- visibility: visible;
83
- }
84
- }
85
-
86
- & .class-item-sortable-trigger {
87
- visibility: hidden;
88
- position: absolute;
89
- left: 0;
90
- top: 50%;
91
- transform: translate( -75%, -50% );
92
- }
93
- `;
72
+ const StyledSortableTrigger = styled( 'div' )( ( { theme } ) => ( {
73
+ position: 'absolute',
74
+ left: 0,
75
+ top: '50%',
76
+ transform: `translate( -${ theme.spacing( 1.5 ) }, -50% )`,
77
+ color: theme.palette.action.active,
78
+ } ) );
94
79
 
95
- export const SortableItemIndicator = styled( Box )`
80
+ const SortableItemIndicator = styled( Box )`
96
81
  width: 100%;
97
- height: 3px;
98
- border-radius: ${ ( { theme } ) => theme.spacing( 0.5 ) };
82
+ height: 1px;
99
83
  background-color: ${ ( { theme } ) => theme.palette.text.primary };
100
84
  `;
@@ -0,0 +1,27 @@
1
+ import { __dispatch as dispatch, __getState as getState } from '@elementor/store';
2
+
3
+ import { apiClient, type UpdateContext } from './api';
4
+ import { type GlobalClassesState, slice } from './store';
5
+
6
+ type Options = {
7
+ context: UpdateContext;
8
+ };
9
+
10
+ export async function saveGlobalClasses( { context }: Options ) {
11
+ const state: GlobalClassesState = getState().globalClasses;
12
+
13
+ const data = {
14
+ items: state.items,
15
+ order: state.order,
16
+ };
17
+
18
+ if ( context === 'preview' ) {
19
+ await apiClient.saveDraft( data );
20
+
21
+ return;
22
+ }
23
+
24
+ await apiClient.publish( data );
25
+
26
+ dispatch( slice.actions.setPristine() );
27
+ }
@@ -1,7 +1,8 @@
1
- import { __privateRunCommandSync as runCommandSync, registerDataHook } from '@elementor/editor-v1-adapters';
1
+ import { setDocumentModifiedStatus } from '@elementor/editor-documents';
2
+ import { registerDataHook } from '@elementor/editor-v1-adapters';
2
3
  import { __getState as getState, __subscribeWithSelector as subscribeWithSelector } from '@elementor/store';
3
4
 
4
- import { publishGlobalClasses } from './publish-global-classes';
5
+ import { saveGlobalClasses } from './save-global-classes';
5
6
  import { selectIsDirty } from './store';
6
7
 
7
8
  export function syncWithDocumentSave() {
@@ -18,12 +19,16 @@ function syncDirtyState() {
18
19
  return;
19
20
  }
20
21
 
21
- runCommandSync( 'document/save/set-is-modified', { status: true }, { internal: true } );
22
+ setDocumentModifiedStatus( true );
22
23
  } );
23
24
  }
24
25
 
25
26
  function bindSaveAction() {
26
- registerDataHook( 'after', 'document/save/save', publishGlobalClasses );
27
+ registerDataHook( 'after', 'document/save/save', ( args ) => {
28
+ return saveGlobalClasses( {
29
+ context: args.status === 'publish' ? 'frontend' : 'preview',
30
+ } );
31
+ } );
27
32
  }
28
33
 
29
34
  function isDirty() {
@@ -1,23 +0,0 @@
1
- import { __dispatch as dispatch, __getState as getState } from '@elementor/store';
2
-
3
- import { apiClient } from './api';
4
- import { type GlobalClassesState, selectIsDirty, slice } from './store';
5
-
6
- export async function publishGlobalClasses() {
7
- if ( ! isDirty() ) {
8
- return;
9
- }
10
-
11
- const state: GlobalClassesState = getState().globalClasses;
12
-
13
- await apiClient.update( {
14
- items: state.items,
15
- order: state.order,
16
- } );
17
-
18
- dispatch( slice.actions.setPristine() );
19
- }
20
-
21
- function isDirty() {
22
- return selectIsDirty( getState() );
23
- }