@elementor/editor-editing-panel 4.2.0-871 → 4.2.0-872

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-editing-panel",
3
- "version": "4.2.0-871",
3
+ "version": "4.2.0-872",
4
4
  "private": false,
5
5
  "author": "Elementor Team",
6
6
  "homepage": "https://elementor.com/",
@@ -39,31 +39,31 @@
39
39
  "dev": "tsup --config=../../tsup.dev.ts"
40
40
  },
41
41
  "dependencies": {
42
- "@elementor/editor": "4.2.0-871",
43
- "@elementor/editor-canvas": "4.2.0-871",
44
- "@elementor/editor-controls": "4.2.0-871",
45
- "@elementor/editor-documents": "4.2.0-871",
46
- "@elementor/editor-elements": "4.2.0-871",
47
- "@elementor/editor-interactions": "4.2.0-871",
48
- "@elementor/editor-notifications": "4.2.0-871",
49
- "@elementor/editor-panels": "4.2.0-871",
50
- "@elementor/editor-props": "4.2.0-871",
51
- "@elementor/editor-responsive": "4.2.0-871",
52
- "@elementor/editor-styles": "4.2.0-871",
53
- "@elementor/editor-styles-repository": "4.2.0-871",
54
- "@elementor/editor-ui": "4.2.0-871",
55
- "@elementor/editor-v1-adapters": "4.2.0-871",
56
- "@elementor/http-client": "4.2.0-871",
42
+ "@elementor/editor": "4.2.0-872",
43
+ "@elementor/editor-canvas": "4.2.0-872",
44
+ "@elementor/editor-controls": "4.2.0-872",
45
+ "@elementor/editor-documents": "4.2.0-872",
46
+ "@elementor/editor-elements": "4.2.0-872",
47
+ "@elementor/editor-interactions": "4.2.0-872",
48
+ "@elementor/editor-notifications": "4.2.0-872",
49
+ "@elementor/editor-panels": "4.2.0-872",
50
+ "@elementor/editor-props": "4.2.0-872",
51
+ "@elementor/editor-responsive": "4.2.0-872",
52
+ "@elementor/editor-styles": "4.2.0-872",
53
+ "@elementor/editor-styles-repository": "4.2.0-872",
54
+ "@elementor/editor-ui": "4.2.0-872",
55
+ "@elementor/editor-v1-adapters": "4.2.0-872",
56
+ "@elementor/http-client": "4.2.0-872",
57
57
  "@elementor/icons": "~1.75.1",
58
- "@elementor/editor-variables": "4.2.0-871",
59
- "@elementor/locations": "4.2.0-871",
60
- "@elementor/menus": "4.2.0-871",
61
- "@elementor/query": "4.2.0-871",
62
- "@elementor/schema": "4.2.0-871",
63
- "@elementor/session": "4.2.0-871",
58
+ "@elementor/editor-variables": "4.2.0-872",
59
+ "@elementor/locations": "4.2.0-872",
60
+ "@elementor/menus": "4.2.0-872",
61
+ "@elementor/query": "4.2.0-872",
62
+ "@elementor/schema": "4.2.0-872",
63
+ "@elementor/session": "4.2.0-872",
64
64
  "@elementor/ui": "1.37.5",
65
- "@elementor/utils": "4.2.0-871",
66
- "@elementor/wp-media": "4.2.0-871",
65
+ "@elementor/utils": "4.2.0-872",
66
+ "@elementor/wp-media": "4.2.0-872",
67
67
  "@wordpress/i18n": "^5.13.0"
68
68
  },
69
69
  "peerDependencies": {
@@ -2,6 +2,12 @@ import { createFilterOptions } from '@elementor/ui';
2
2
 
3
3
  import { type InternalOption, type Option } from './types';
4
4
 
5
+ const STRIP_NON_CLASS_CHARS = /[^a-zA-Z0-9_-]/g;
6
+
7
+ function normalizeClassSearch( value: string ) {
8
+ return value.replace( STRIP_NON_CLASS_CHARS, '' ).toLowerCase();
9
+ }
10
+
5
11
  export function useFilterOptions< TOption extends Option >( parameters: {
6
12
  options: TOption[];
7
13
  selected: TOption[];
@@ -10,7 +16,9 @@ export function useFilterOptions< TOption extends Option >( parameters: {
10
16
  } ) {
11
17
  const { options, selected, onCreate, entityName } = parameters;
12
18
 
13
- const filter = createFilterOptions< InternalOption< TOption > >();
19
+ const filter = createFilterOptions< InternalOption< TOption > >( {
20
+ matchFrom: 'any',
21
+ } );
14
22
 
15
23
  const filterOptions = (
16
24
  optionList: InternalOption< TOption >[],
@@ -23,7 +31,7 @@ export function useFilterOptions< TOption extends Option >( parameters: {
23
31
 
24
32
  const filteredOptions = filter(
25
33
  optionList.filter( ( option ) => ! selectedValues.includes( option.value ) ),
26
- params
34
+ { ...params, inputValue: normalizeClassSearch( params.inputValue ) }
27
35
  );
28
36
 
29
37
  const isExisting = options.some( ( option ) => params.inputValue === option.label );
@@ -11,11 +11,13 @@ import {
11
11
  validateStyleLabel,
12
12
  } from '@elementor/editor-styles-repository';
13
13
  import { InfoAlert, WarningInfotip } from '@elementor/editor-ui';
14
+ import { isExperimentActive } from '@elementor/editor-v1-adapters';
14
15
  import { ColorSwatchIcon, MapPinIcon } from '@elementor/icons';
15
16
  import { createLocation } from '@elementor/locations';
16
17
  import {
17
18
  type AutocompleteChangeReason,
18
19
  Box,
20
+ Button,
19
21
  Chip,
20
22
  type ChipOwnProps,
21
23
  FormLabel,
@@ -43,6 +45,22 @@ import { useApplyClass, useCreateAndApplyClass, useUnapplyClass } from './use-ap
43
45
  const ID = 'elementor-css-class-selector';
44
46
  const TAGS_LIMIT = 50;
45
47
 
48
+ const EVENT_OPEN_GLOBAL_CLASSES_MANAGER = 'elementor/open-global-classes-manager';
49
+ const EVENT_TOGGLE_DESIGN_SYSTEM = 'elementor/toggle-design-system';
50
+
51
+ function openClassManagerPanel() {
52
+ if ( isExperimentActive( 'e_editor_design_system_panel' ) ) {
53
+ window.dispatchEvent(
54
+ new CustomEvent( EVENT_TOGGLE_DESIGN_SYSTEM, {
55
+ detail: { tab: 'classes' as const },
56
+ } )
57
+ );
58
+ return;
59
+ }
60
+
61
+ window.dispatchEvent( new CustomEvent( EVENT_OPEN_GLOBAL_CLASSES_MANAGER ) );
62
+ }
63
+
46
64
  type StyleDefOption = Option & {
47
65
  color: ChipOwnProps[ 'color' ];
48
66
  icon: ReactElement | null;
@@ -74,7 +92,7 @@ export function CssClassSelector() {
74
92
  const [ renameError, setRenameError ] = useState< string | null >( null );
75
93
 
76
94
  const handleSelect = useHandleSelect();
77
- const { create, validate, entityName } = useCreateAction();
95
+ const { create, validate, entityName, isAtLimit, limitCount } = useCreateAction();
78
96
 
79
97
  const appliedOptions = useAppliedOptions( options );
80
98
  const active = appliedOptions.find( ( option ) => option.value === activeId ) ?? EMPTY_OPTION;
@@ -114,7 +132,13 @@ export function CssClassSelector() {
114
132
  onCreate={ create ?? undefined }
115
133
  validate={ validate ?? undefined }
116
134
  limitTags={ TAGS_LIMIT }
117
- renderEmptyState={ EmptyState }
135
+ renderEmptyState={
136
+ isAtLimit && typeof limitCount === 'number'
137
+ ? ( props ) => (
138
+ <LimitReachedEmptyState limitCount={ limitCount } onClear={ props.onClear } />
139
+ )
140
+ : EmptyState
141
+ }
118
142
  getLimitTagsText={ ( more ) => (
119
143
  <Chip size="tiny" variant="standard" label={ `+${ more }` } clickable />
120
144
  ) }
@@ -166,7 +190,9 @@ export function CssClassSelector() {
166
190
  );
167
191
  }
168
192
 
169
- const EmptyState = ( { searchValue, onClear }: { searchValue: string; onClear: () => void } ) => (
193
+ type EmptyStateProps = { searchValue: string; onClear: () => void };
194
+
195
+ const EmptyStateLayout = ( { searchValue, onClear, children }: EmptyStateProps & { children: React.ReactNode } ) => (
170
196
  <Box sx={ { py: 4 } }>
171
197
  <Stack
172
198
  gap={ 1 }
@@ -181,11 +207,7 @@ const EmptyState = ( { searchValue, onClear }: { searchValue: string; onClear: (
181
207
  <br />
182
208
  &ldquo;{ searchValue }&rdquo;.
183
209
  </Typography>
184
- <Typography align="center" variant="caption" sx={ { mb: 2 } }>
185
- { __( 'With your current role,', 'elementor' ) }
186
- <br />
187
- { __( 'you can only use existing classes.', 'elementor' ) }
188
- </Typography>
210
+ { children }
189
211
  <Link color="text.secondary" variant="caption" component="button" onClick={ onClear }>
190
212
  { __( 'Clear & try again', 'elementor' ) }
191
213
  </Link>
@@ -193,6 +215,62 @@ const EmptyState = ( { searchValue, onClear }: { searchValue: string; onClear: (
193
215
  </Box>
194
216
  );
195
217
 
218
+ const EmptyState = ( props: EmptyStateProps ) => (
219
+ <EmptyStateLayout { ...props }>
220
+ <Typography align="center" variant="caption" sx={ { mb: 2 } }>
221
+ { __( 'With your current role,', 'elementor' ) }
222
+ <br />
223
+ { __( 'you can only use existing classes.', 'elementor' ) }
224
+ </Typography>
225
+ </EmptyStateLayout>
226
+ );
227
+
228
+ const LimitReachedEmptyState = ( {
229
+ limitCount,
230
+ onClear,
231
+ }: Pick< EmptyStateProps, 'onClear' > & { limitCount: number } ) => (
232
+ <Box sx={ { py: 4 } }>
233
+ <Stack
234
+ gap={ 1 }
235
+ alignItems="center"
236
+ color="text.secondary"
237
+ justifyContent="center"
238
+ sx={ { px: 1, m: 'auto', maxWidth: '260px' } }
239
+ >
240
+ <ColorSwatchIcon sx={ { transform: 'rotate(90deg)' } } fontSize="large" />
241
+ <Typography align="center" variant="subtitle2">
242
+ {
243
+ /* translators: %s is the maximum number of classes */
244
+ __( 'Limit of %s classes reached', 'elementor' ).replace( '%s', String( limitCount ) )
245
+ }
246
+ </Typography>
247
+ <Typography align="center" variant="caption" component="div">
248
+ { __( 'Remove a class to create a new one.', 'elementor' ) }{ ' ' }
249
+ <Link
250
+ color="inherit"
251
+ variant="caption"
252
+ component="button"
253
+ onClick={ onClear }
254
+ sx={ { verticalAlign: 'baseline' } }
255
+ >
256
+ { __( 'Clear', 'elementor' ) }
257
+ </Link>
258
+ </Typography>
259
+ <Button
260
+ variant="outlined"
261
+ color="secondary"
262
+ size="small"
263
+ onClick={ () => {
264
+ openClassManagerPanel();
265
+ onClear();
266
+ } }
267
+ >
268
+ { __( 'Class Manager', 'elementor' ) }
269
+ </Button>
270
+ </Stack>
271
+ </Box>
272
+ );
273
+
196
274
  const updateClassByProvider = ( provider: string | null, data: UpdateActionPayload ) => {
197
275
  if ( ! provider ) {
198
276
  return;
@@ -250,6 +328,18 @@ function useCreateAction() {
250
328
  return {};
251
329
  }
252
330
 
331
+ const entityName =
332
+ provider.labels.singular && provider.labels.plural
333
+ ? ( provider.labels as CreatableAutocompleteProps< StyleDefOption >[ 'entityName' ] )
334
+ : undefined;
335
+
336
+ const validate = ( newClassLabel: string, event: ValidationEvent ): ValidationResult =>
337
+ validateStyleLabel( newClassLabel, event );
338
+
339
+ if ( hasReachedLimit( provider ) ) {
340
+ return { entityName, isAtLimit: true as const, limitCount: provider.limit, validate };
341
+ }
342
+
253
343
  const create = ( classLabel: string ) => {
254
344
  const { createdId } = createAction( { classLabel } );
255
345
  trackStyles( provider.getKey() ?? '', 'classCreated', {
@@ -259,26 +349,7 @@ function useCreateAction() {
259
349
  } );
260
350
  };
261
351
 
262
- const validate = ( newClassLabel: string, event: ValidationEvent ): ValidationResult => {
263
- if ( hasReachedLimit( provider ) ) {
264
- return {
265
- isValid: false,
266
- /* translators: %s is the maximum number of classes */
267
- errorMessage: __(
268
- 'You’ve reached the limit of %s classes. Please remove an existing one to create a new class.',
269
- 'elementor'
270
- ).replace( '%s', provider.limit.toString() ),
271
- };
272
- }
273
- return validateStyleLabel( newClassLabel, event );
274
- };
275
-
276
- const entityName =
277
- provider.labels.singular && provider.labels.plural
278
- ? ( provider.labels as CreatableAutocompleteProps< StyleDefOption >[ 'entityName' ] )
279
- : undefined;
280
-
281
- return { create, validate, entityName };
352
+ return { create, validate, entityName, isAtLimit: false as const };
282
353
  }
283
354
 
284
355
  function hasReachedLimit( provider: StylesProvider ) {