@elementor/editor-global-classes 3.33.0-99 → 3.34.2

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.
Files changed (37) hide show
  1. package/dist/index.js +1000 -430
  2. package/dist/index.js.map +1 -1
  3. package/dist/index.mjs +940 -366
  4. package/dist/index.mjs.map +1 -1
  5. package/package.json +21 -18
  6. package/src/api.ts +4 -0
  7. package/src/components/class-manager/class-manager-button.tsx +15 -1
  8. package/src/components/class-manager/class-manager-panel.tsx +2 -2
  9. package/src/components/class-manager/delete-class.ts +9 -3
  10. package/src/components/class-manager/delete-confirmation-dialog.tsx +2 -2
  11. package/src/components/class-manager/duplicate-label-dialog.tsx +159 -0
  12. package/src/components/class-manager/global-classes-list.tsx +53 -22
  13. package/src/components/convert-local-class-to-global-class.tsx +7 -0
  14. package/src/components/css-class-usage/components/css-class-usage-popover.tsx +10 -1
  15. package/src/components/css-class-usage/components/css-class-usage-trigger.tsx +22 -7
  16. package/src/components/search-and-filter/components/filter/active-filters.tsx +8 -0
  17. package/src/components/search-and-filter/components/filter/clear-icon-button.tsx +12 -3
  18. package/src/components/search-and-filter/components/filter/css-class-filter.tsx +10 -0
  19. package/src/components/search-and-filter/components/filter/filter-list.tsx +7 -0
  20. package/src/components/search-and-filter/components/search/class-manager-search.tsx +6 -0
  21. package/src/components/search-and-filter/context.tsx +12 -2
  22. package/src/errors.ts +5 -0
  23. package/src/global-classes-styles-provider.ts +13 -3
  24. package/src/hooks/use-css-class-by-id.ts +8 -0
  25. package/src/hooks/use-prefetch-css-class-usage.ts +6 -0
  26. package/src/init.ts +14 -5
  27. package/src/mcp-integration/classes-resource.ts +20 -0
  28. package/src/mcp-integration/index.ts +15 -0
  29. package/src/mcp-integration/mcp-apply-unapply-global-classes.ts +117 -0
  30. package/src/mcp-integration/mcp-get-global-class-usages.ts +72 -0
  31. package/src/save-global-classes.tsx +55 -0
  32. package/src/store.ts +32 -1
  33. package/src/sync-with-document-save.ts +9 -6
  34. package/src/sync-with-document.tsx +19 -0
  35. package/src/utils/tracking.ts +204 -0
  36. package/src/components/class-manager/save-changes-dialog.tsx +0 -92
  37. package/src/save-global-classes.ts +0 -42
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elementor/editor-global-classes",
3
- "version": "3.33.0-99",
3
+ "version": "3.34.2",
4
4
  "private": false,
5
5
  "author": "Elementor Team",
6
6
  "homepage": "https://elementor.com/",
@@ -39,23 +39,26 @@
39
39
  "dev": "tsup --config=../../tsup.dev.ts"
40
40
  },
41
41
  "dependencies": {
42
- "@elementor/editor": "3.33.0-99",
43
- "@elementor/editor-current-user": "3.33.0-99",
44
- "@elementor/editor-documents": "3.33.0-99",
45
- "@elementor/editor-editing-panel": "3.33.0-99",
46
- "@elementor/editor-panels": "3.33.0-99",
47
- "@elementor/editor-props": "3.33.0-99",
48
- "@elementor/editor-styles": "3.33.0-99",
49
- "@elementor/editor-styles-repository": "3.33.0-99",
50
- "@elementor/editor-ui": "3.33.0-99",
51
- "@elementor/editor-v1-adapters": "3.33.0-99",
52
- "@elementor/http-client": "3.33.0-99",
53
- "@elementor/icons": "1.46.0",
54
- "@elementor/query": "3.33.0-99",
55
- "@elementor/store": "3.33.0-99",
56
- "@elementor/ui": "1.36.12",
57
- "@elementor/utils": "3.33.0-99",
58
- "@wordpress/i18n": "^5.13.0"
42
+ "@elementor/editor": "3.34.2",
43
+ "@elementor/editor-current-user": "3.34.2",
44
+ "@elementor/editor-documents": "3.34.2",
45
+ "@elementor/editor-editing-panel": "3.34.2",
46
+ "@elementor/editor-mcp": "3.34.2",
47
+ "@elementor/editor-panels": "3.34.2",
48
+ "@elementor/editor-props": "3.34.2",
49
+ "@elementor/editor-styles": "3.34.2",
50
+ "@elementor/editor-styles-repository": "3.34.2",
51
+ "@elementor/editor-ui": "3.34.2",
52
+ "@elementor/editor-v1-adapters": "3.34.2",
53
+ "@elementor/http-client": "3.34.2",
54
+ "@elementor/icons": "^1.61.0",
55
+ "@elementor/query": "3.34.2",
56
+ "@elementor/schema": "3.34.2",
57
+ "@elementor/store": "3.34.2",
58
+ "@elementor/ui": "1.36.17",
59
+ "@elementor/utils": "3.34.2",
60
+ "@wordpress/i18n": "^5.13.0",
61
+ "@elementor/mixpanel": "3.34.2"
59
62
  },
60
63
  "peerDependencies": {
61
64
  "react": "^18.3.1",
package/src/api.ts CHANGED
@@ -49,3 +49,7 @@ export const apiClient = {
49
49
  },
50
50
  } ),
51
51
  };
52
+
53
+ export const API_ERROR_CODES = {
54
+ DUPLICATED_LABEL: 'DUPLICATED_LABEL',
55
+ };
@@ -4,14 +4,22 @@ import {
4
4
  __useActiveDocumentActions as useActiveDocumentActions,
5
5
  } from '@elementor/editor-documents';
6
6
  import { useUserStylesCapability } from '@elementor/editor-styles-repository';
7
+ import { SaveChangesDialog, useDialog } from '@elementor/editor-ui';
7
8
  import { IconButton, Tooltip } from '@elementor/ui';
8
9
  import { __ } from '@wordpress/i18n';
9
10
 
10
11
  import { globalClassesStylesProvider } from '../../global-classes-styles-provider';
11
12
  import { usePrefetchCssClassUsage } from '../../hooks/use-prefetch-css-class-usage';
13
+ import { trackGlobalClasses } from '../../utils/tracking';
12
14
  import { usePanelActions } from './class-manager-panel';
13
15
  import { FlippedColorSwatchIcon } from './flipped-color-swatch-icon';
14
- import { SaveChangesDialog, useDialog } from './save-changes-dialog';
16
+
17
+ const trackGlobalClassesButton = () => {
18
+ trackGlobalClasses( {
19
+ event: 'classManagerOpened',
20
+ source: 'style-panel',
21
+ } );
22
+ };
15
23
 
16
24
  export const ClassManagerButton = () => {
17
25
  const document = useActiveDocument();
@@ -35,6 +43,11 @@ export const ClassManagerButton = () => {
35
43
  }
36
44
 
37
45
  openPanel();
46
+ trackGlobalClassesButton();
47
+ trackGlobalClasses( {
48
+ event: 'classManagerOpened',
49
+ source: 'style-panel',
50
+ } );
38
51
  prefetchClassesUsage();
39
52
  };
40
53
 
@@ -68,6 +81,7 @@ export const ClassManagerButton = () => {
68
81
  await saveDocument();
69
82
  closeSaveChangesDialog();
70
83
  openPanel();
84
+ trackGlobalClassesButton();
71
85
  prefetchClassesUsage();
72
86
  },
73
87
  },
@@ -9,7 +9,7 @@ import {
9
9
  PanelHeader,
10
10
  PanelHeaderTitle,
11
11
  } from '@elementor/editor-panels';
12
- import { ThemeProvider } from '@elementor/editor-ui';
12
+ import { SaveChangesDialog, ThemeProvider, useDialog } from '@elementor/editor-ui';
13
13
  import { changeEditMode } from '@elementor/editor-v1-adapters';
14
14
  import { XIcon } from '@elementor/icons';
15
15
  import { useMutation } from '@elementor/query';
@@ -42,7 +42,6 @@ import { hasDeletedItems, onDelete } from './delete-class';
42
42
  import { FlippedColorSwatchIcon } from './flipped-color-swatch-icon';
43
43
  import { GlobalClassesList } from './global-classes-list';
44
44
  import { blockPanelInteractions, unblockPanelInteractions } from './panel-interactions';
45
- import { SaveChangesDialog, useDialog } from './save-changes-dialog';
46
45
 
47
46
  const id = 'global-classes-manager';
48
47
 
@@ -64,6 +63,7 @@ export const { panel, usePanelActions } = createPanel( {
64
63
 
65
64
  unblockPanelInteractions();
66
65
  },
66
+ isOpenPreviousElement: true,
67
67
  } );
68
68
 
69
69
  export function ClassManagerPanel() {
@@ -3,13 +3,19 @@ import { __privateRunCommand as runCommand } from '@elementor/editor-v1-adapters
3
3
  import { __dispatch as dispatch } from '@elementor/store';
4
4
 
5
5
  import { slice } from '../../store';
6
+ import { trackGlobalClasses } from '../../utils/tracking';
6
7
 
7
8
  let isDeleted = false;
8
9
 
9
10
  export const deleteClass = ( id: string ) => {
10
- dispatch( slice.actions.delete( id ) );
11
-
12
- isDeleted = true;
11
+ trackGlobalClasses( {
12
+ event: 'classDeleted',
13
+ classId: id,
14
+ runAction: () => {
15
+ dispatch( slice.actions.delete( id ) );
16
+ isDeleted = true;
17
+ },
18
+ } );
13
19
  };
14
20
 
15
21
  export const onDelete = async () => {
@@ -53,9 +53,9 @@ const DeleteConfirmationDialog = ( { label, id }: DeleteConfirmationDialogProps
53
53
  data: { total, content },
54
54
  } = useCssClassUsageByID( id );
55
55
  const onConfirm = () => {
56
- deleteClass( id );
57
-
56
+ //
58
57
  closeDialog();
58
+ deleteClass( id );
59
59
  };
60
60
  // translators: %1: total usage count, %2: number of pages
61
61
  const text =
@@ -0,0 +1,159 @@
1
+ import * as React from 'react';
2
+ import { closeDialog, EllipsisWithTooltip } from '@elementor/editor-ui';
3
+ import { InfoCircleFilledIcon } from '@elementor/icons';
4
+ import {
5
+ Alert,
6
+ Box,
7
+ Button,
8
+ DialogActions,
9
+ DialogContent,
10
+ DialogHeader,
11
+ Divider,
12
+ Icon,
13
+ Stack,
14
+ Typography,
15
+ } from '@elementor/ui';
16
+ import { __ } from '@wordpress/i18n';
17
+
18
+ import { type ModifiedLabels } from '../../store';
19
+
20
+ const DUP_PREFIX = 'DUP_';
21
+
22
+ export const DuplicateLabelDialog = ( {
23
+ modifiedLabels,
24
+ onApprove,
25
+ }: {
26
+ modifiedLabels: ModifiedLabels;
27
+ onApprove?: () => void;
28
+ } ) => {
29
+ const handleButtonClick = () => {
30
+ localStorage.setItem( 'elementor-global-classes-search', DUP_PREFIX );
31
+ onApprove?.();
32
+ closeDialog();
33
+ };
34
+
35
+ return (
36
+ <>
37
+ <DialogHeader logo={ false }>
38
+ <Box display="flex" alignItems="center" gap={ 1 }>
39
+ <Icon color="secondary">
40
+ <InfoCircleFilledIcon fontSize="medium" />
41
+ </Icon>
42
+ <Typography variant="subtitle1">
43
+ { __( "We've published your page and updated class names.", 'elementor' ) }
44
+ </Typography>
45
+ </Box>
46
+ </DialogHeader>
47
+ <DialogContent>
48
+ <Stack spacing={ 2 } direction="column">
49
+ <Typography variant="body2">
50
+ { __(
51
+ 'Some new classes used the same names as existing ones. To prevent conflicts, we added the prefix',
52
+ 'elementor'
53
+ ) }
54
+ <strong> { DUP_PREFIX }</strong>
55
+ </Typography>
56
+
57
+ <Box>
58
+ <Box
59
+ sx={ {
60
+ width: '100%',
61
+ display: 'flex',
62
+ gap: 2,
63
+ alignItems: 'flex-start',
64
+ } }
65
+ >
66
+ <Typography
67
+ variant="subtitle2"
68
+ sx={ {
69
+ fontWeight: 'bold',
70
+ flex: 1,
71
+ flexShrink: 1,
72
+ flexGrow: 1,
73
+ minWidth: 0,
74
+ } }
75
+ >
76
+ { __( 'Before', 'elementor' ) }
77
+ </Typography>
78
+ <Typography
79
+ variant="subtitle2"
80
+ sx={ {
81
+ minWidth: '200px',
82
+ fontWeight: 'bold',
83
+ flexShrink: 0,
84
+ flexGrow: 0,
85
+ width: '200px',
86
+ maxWidth: '200px',
87
+ } }
88
+ >
89
+ { __( 'After', 'elementor' ) }
90
+ </Typography>
91
+ </Box>
92
+ <Divider sx={ { mt: 0.5, mb: 0.5 } } />
93
+ <Stack direction="column" gap={ 0.5 } sx={ { pb: 2 } }>
94
+ { Object.values( modifiedLabels ).map( ( { original, modified }, index ) => (
95
+ <Box
96
+ key={ index }
97
+ sx={ {
98
+ width: '100%',
99
+ display: 'flex',
100
+ gap: 2,
101
+ alignItems: 'flex-start',
102
+ } }
103
+ >
104
+ <Box
105
+ sx={ {
106
+ flex: 1,
107
+ flexShrink: 1,
108
+ flexGrow: 1,
109
+ minWidth: 0,
110
+ } }
111
+ >
112
+ <EllipsisWithTooltip title={ original }>
113
+ <Typography variant="body2" sx={ { color: 'text.secondary' } }>
114
+ { original }
115
+ </Typography>
116
+ </EllipsisWithTooltip>
117
+ </Box>
118
+ <Box
119
+ sx={ {
120
+ minWidth: '200px',
121
+ flexShrink: 0,
122
+ flexGrow: 0,
123
+ width: '200px',
124
+ maxWidth: '200px',
125
+ } }
126
+ >
127
+ <EllipsisWithTooltip title={ modified }>
128
+ <Typography variant="body2" sx={ { color: 'text.primary' } }>
129
+ { modified }
130
+ </Typography>
131
+ </EllipsisWithTooltip>
132
+ </Box>
133
+ </Box>
134
+ ) ) }
135
+ </Stack>
136
+ <Box>
137
+ <Alert severity="info" size="small" color="secondary">
138
+ <strong>{ __( 'Your designs and classes are safe.', 'elementor' ) }</strong>
139
+ { __(
140
+ 'Only the prefixes were added. Find them in Class Manager by searching',
141
+ 'elementor'
142
+ ) }
143
+ <strong>{ DUP_PREFIX }</strong>
144
+ </Alert>
145
+ </Box>
146
+ </Box>
147
+ </Stack>
148
+ </DialogContent>
149
+ <DialogActions>
150
+ <Button color="secondary" variant="text" onClick={ handleButtonClick }>
151
+ { __( 'Go to Class Manager', 'elementor' ) }
152
+ </Button>
153
+ <Button color="secondary" variant="contained" onClick={ closeDialog }>
154
+ { __( 'Done', 'elementor' ) }
155
+ </Button>
156
+ </DialogActions>
157
+ </>
158
+ );
159
+ };
@@ -9,6 +9,7 @@ import { useClassesOrder } from '../../hooks/use-classes-order';
9
9
  import { useFilters } from '../../hooks/use-filters';
10
10
  import { useOrderedClasses } from '../../hooks/use-ordered-classes';
11
11
  import { slice } from '../../store';
12
+ import { trackGlobalClasses } from '../../utils/tracking';
12
13
  import { useSearchAndFilters } from '../search-and-filter/context';
13
14
  import { ClassItem } from './class-item';
14
15
  import { DeleteConfirmationProvider } from './delete-confirmation-dialog';
@@ -27,7 +28,9 @@ export const GlobalClassesList = ( { disabled }: GlobalClassesListProps ) => {
27
28
  const cssClasses = useOrderedClasses();
28
29
  const dispatch = useDispatch();
29
30
  const filters = useFilters();
30
- const [ classesOrder, reorderClasses ] = useReorder();
31
+ const [ draggedItemId, setDraggedItemId ] = React.useState< StyleDefinitionID | null >( null );
32
+ const draggedItemLabel = cssClasses.find( ( cssClass ) => cssClass.id === draggedItemId )?.label ?? '';
33
+ const [ classesOrder, reorderClasses ] = useReorder( draggedItemId, setDraggedItemId, draggedItemLabel ?? '' );
31
34
  const filteredCssClasses = useFilteredCssClasses();
32
35
 
33
36
  useEffect( () => {
@@ -72,26 +75,41 @@ export const GlobalClassesList = ( { disabled }: GlobalClassesListProps ) => {
72
75
  >
73
76
  { filteredCssClasses?.map( ( { id, label } ) => (
74
77
  <SortableItem key={ id } id={ id }>
75
- { ( { isDragged, isDragPlaceholder, triggerProps, triggerStyle } ) => (
76
- <ClassItem
77
- id={ id }
78
- label={ label }
79
- renameClass={ ( newLabel: string ) => {
80
- dispatch(
81
- slice.actions.update( {
82
- style: {
83
- id,
84
- label: newLabel,
85
- },
86
- } )
87
- );
88
- } }
89
- selected={ isDragged }
90
- disabled={ disabled || isDragPlaceholder }
91
- sortableTriggerProps={ { ...triggerProps, style: triggerStyle } }
92
- showSortIndicator={ allowSorting }
93
- />
94
- ) }
78
+ { ( { isDragged, isDragPlaceholder, triggerProps, triggerStyle } ) => {
79
+ if ( isDragged && ! draggedItemId ) {
80
+ setDraggedItemId( id );
81
+ }
82
+ return (
83
+ <ClassItem
84
+ id={ id }
85
+ label={ label }
86
+ renameClass={ ( newLabel: string ) => {
87
+ trackGlobalClasses( {
88
+ event: 'classRenamed',
89
+ classId: id,
90
+ oldValue: label,
91
+ newValue: newLabel,
92
+ source: 'class-manager',
93
+ } );
94
+ dispatch(
95
+ slice.actions.update( {
96
+ style: {
97
+ id,
98
+ label: newLabel,
99
+ },
100
+ } )
101
+ );
102
+ } }
103
+ selected={ isDragged }
104
+ disabled={ disabled || isDragPlaceholder }
105
+ sortableTriggerProps={ {
106
+ ...triggerProps,
107
+ style: triggerStyle,
108
+ } }
109
+ showSortIndicator={ allowSorting }
110
+ />
111
+ );
112
+ } }
95
113
  </SortableItem>
96
114
  ) ) }
97
115
  </SortableProvider>
@@ -122,12 +140,25 @@ const StyledHeader = styled( Typography )< TypographyProps >( ( { theme, variant
122
140
  },
123
141
  } ) );
124
142
 
125
- const useReorder = () => {
143
+ const useReorder = (
144
+ draggedItemId: StyleDefinitionID | null,
145
+ setDraggedItemId: React.Dispatch< React.SetStateAction< StyleDefinitionID | null > >,
146
+ draggedItemLabel: string
147
+ ) => {
126
148
  const dispatch = useDispatch();
127
149
  const order = useClassesOrder();
128
150
 
129
151
  const reorder = ( newIds: StyleDefinitionID[] ) => {
130
152
  dispatch( slice.actions.setOrder( newIds ) );
153
+
154
+ if ( draggedItemId ) {
155
+ trackGlobalClasses( {
156
+ event: 'classManagerReorder',
157
+ classId: draggedItemId,
158
+ classTitle: draggedItemLabel,
159
+ } );
160
+ setDraggedItemId( null ); // Reset after tracking
161
+ }
131
162
  };
132
163
 
133
164
  return [ order, reorder ] as const;
@@ -6,6 +6,7 @@ import { Divider } from '@elementor/ui';
6
6
  import { __ } from '@wordpress/i18n';
7
7
 
8
8
  import { globalClassesStylesProvider } from '../global-classes-styles-provider';
9
+ import { trackGlobalClasses } from '../utils/tracking';
9
10
 
10
11
  type OwnProps = {
11
12
  successCallback: ( _: string ) => void;
@@ -26,6 +27,12 @@ export const ConvertLocalClassToGlobalClass = ( props: OwnProps ) => {
26
27
  const newId = globalClassesStylesProvider.actions.create?.( newClassName, localStyleData.variants );
27
28
  if ( newId ) {
28
29
  props.successCallback( newId );
30
+ trackGlobalClasses( {
31
+ classId: newId,
32
+ event: 'classCreated',
33
+ source: 'converted',
34
+ classTitle: newClassName,
35
+ } );
29
36
  }
30
37
  };
31
38
 
@@ -20,6 +20,7 @@ import { Box, Chip, Divider, Icon, MenuList, Stack, styled, Tooltip, Typography
20
20
  import { __ } from '@wordpress/i18n';
21
21
 
22
22
  import { useCssClassUsageByID } from '../../../hooks/use-css-class-usage-by-id';
23
+ import { trackGlobalClasses } from '../../../utils/tracking';
23
24
  import { type ContentType } from '../types';
24
25
 
25
26
  type CssClassUsageRecord = VirtualizedItem< 'item', string > & { docType: ContentType };
@@ -69,6 +70,14 @@ export const CssClassUsagePopover = ( {
69
70
  } ) as CssClassUsageRecord
70
71
  ) ?? [];
71
72
 
73
+ const handleSelect = ( value: string ) => {
74
+ onNavigate( +value );
75
+ trackGlobalClasses( {
76
+ event: 'classUsageLocate',
77
+ classId: cssClassID,
78
+ } );
79
+ };
80
+
72
81
  return (
73
82
  <>
74
83
  <PopoverHeader
@@ -86,7 +95,7 @@ export const CssClassUsagePopover = ( {
86
95
  <Divider />
87
96
  <PopoverBody width={ 300 }>
88
97
  <PopoverMenuList
89
- onSelect={ ( value ) => onNavigate( +value ) }
98
+ onSelect={ handleSelect }
90
99
  items={ cssClassUsageRecords }
91
100
  onClose={ () => {} }
92
101
  menuListTemplate={ StyledCssClassUsageItem }
@@ -16,6 +16,7 @@ import {
16
16
  import { __ } from '@wordpress/i18n';
17
17
 
18
18
  import { useCssClassUsageByID } from '../../../hooks/use-css-class-usage-by-id';
19
+ import { trackGlobalClasses } from '../../../utils/tracking';
19
20
  import { type CssClassID } from '../types';
20
21
  import { CssClassUsagePopover } from './css-class-usage-popover';
21
22
 
@@ -32,20 +33,34 @@ export const CssClassUsageTrigger = ( { id, onClick }: { id: CssClassID; onClick
32
33
 
33
34
  const WrapperComponent = total !== 0 ? TooltipWrapper : InfoAlertMessage;
34
35
 
36
+ const handleMouseEnter = () => {
37
+ trackGlobalClasses( {
38
+ event: 'classUsageHovered',
39
+ classId: id,
40
+ usage: total,
41
+ } );
42
+ };
43
+
44
+ const handleClick = ( e: MouseEvent ) => {
45
+ if ( total !== 0 ) {
46
+ bindTrigger( cssClassUsagePopover ).onClick( e );
47
+ onClick( id );
48
+ trackGlobalClasses( {
49
+ event: 'classUsageClicked',
50
+ classId: id,
51
+ } );
52
+ }
53
+ };
54
+
35
55
  return (
36
56
  <>
37
- <Box position={ 'relative' }>
57
+ <Box position={ 'relative' } onMouseEnter={ handleMouseEnter }>
38
58
  <WrapperComponent total={ total }>
39
59
  <CustomIconButton
40
60
  disabled={ total === 0 }
41
61
  size={ 'tiny' }
42
62
  { ...bindTrigger( cssClassUsagePopover ) }
43
- onClick={ ( e: MouseEvent ) => {
44
- if ( total !== 0 ) {
45
- bindTrigger( cssClassUsagePopover ).onClick( e );
46
- onClick( id );
47
- }
48
- } }
63
+ onClick={ handleClick }
49
64
  >
50
65
  <CurrentLocationIcon fontSize={ 'tiny' } />
51
66
  </CustomIconButton>
@@ -3,6 +3,7 @@ import { Chip, Stack } from '@elementor/ui';
3
3
  import { __ } from '@wordpress/i18n';
4
4
 
5
5
  import type { FilterKey } from '../../../../hooks/use-filtered-css-class-usage';
6
+ import { trackGlobalClasses } from '../../../../utils/tracking';
6
7
  import { useSearchAndFilters } from '../../context';
7
8
  import { ClearIconButton } from './clear-icon-button';
8
9
  import { filterConfig } from './filter-list';
@@ -14,6 +15,12 @@ export const ActiveFilters = () => {
14
15
 
15
16
  const handleRemove = ( key: FilterKey ) => {
16
17
  setFilters( ( prev ) => ( { ...prev, [ key ]: false } ) );
18
+ trackGlobalClasses( {
19
+ event: 'classManagerFilterUsed',
20
+ action: 'remove',
21
+ type: key,
22
+ trigger: 'header',
23
+ } );
17
24
  };
18
25
 
19
26
  const activeKeys = Object.keys( filters ).filter( ( key ): key is FilterKey => filters[ key as FilterKey ] );
@@ -35,6 +42,7 @@ export const ActiveFilters = () => {
35
42
  </Stack>
36
43
  { showClearIcon && (
37
44
  <ClearIconButton
45
+ trigger="header"
38
46
  tooltipText={ __( 'Clear Filters', 'elementor' ) }
39
47
  sx={ { margin: '0 0 auto auto' } }
40
48
  />
@@ -2,19 +2,28 @@ import * as React from 'react';
2
2
  import { BrushBigIcon } from '@elementor/icons';
3
3
  import { Box, IconButton, styled, type SxProps, type Theme, Tooltip } from '@elementor/ui';
4
4
 
5
+ import { trackGlobalClasses } from '../../../../utils/tracking';
5
6
  import { useSearchAndFilters } from '../../context';
6
7
 
7
- type ClearIconButtonProps = { tooltipText: React.ReactNode; sx?: SxProps< Theme > };
8
+ type ClearIconButtonProps = { tooltipText: React.ReactNode; sx?: SxProps< Theme >; trigger: 'menu' | 'header' };
8
9
 
9
- export const ClearIconButton = ( { tooltipText, sx }: ClearIconButtonProps ) => {
10
+ export const ClearIconButton = ( { tooltipText, sx, trigger }: ClearIconButtonProps ) => {
10
11
  const {
11
12
  filters: { onClearFilter },
12
13
  } = useSearchAndFilters();
13
14
 
15
+ const handleClearFilters = () => {
16
+ onClearFilter( trigger );
17
+ trackGlobalClasses( {
18
+ event: 'classManagerFilterCleared',
19
+ trigger,
20
+ } );
21
+ };
22
+
14
23
  return (
15
24
  <Tooltip title={ tooltipText } placement="top" disableInteractive>
16
25
  <Box>
17
- <CustomIconButton aria-label={ tooltipText } size="tiny" onClick={ onClearFilter } sx={ sx }>
26
+ <CustomIconButton aria-label={ tooltipText } size="tiny" onClick={ handleClearFilters } sx={ sx }>
18
27
  <BrushBigIcon fontSize="tiny" />
19
28
  </CustomIconButton>
20
29
  </Box>
@@ -4,6 +4,7 @@ import { FilterIcon } from '@elementor/icons';
4
4
  import { bindPopover, bindToggle, Divider, Popover, ToggleButton, Tooltip, usePopupState } from '@elementor/ui';
5
5
  import { __ } from '@wordpress/i18n';
6
6
 
7
+ import { trackGlobalClasses, type TrackingEvent } from '../../../../utils/tracking';
7
8
  import { useSearchAndFilters } from '../../context';
8
9
  import { ClearIconButton } from './clear-icon-button';
9
10
  import { FilterList } from './filter-list';
@@ -17,6 +18,14 @@ export const CssClassFilter = () => {
17
18
  disableAutoFocus: true,
18
19
  } );
19
20
 
21
+ React.useEffect( () => {
22
+ if ( popupState.isOpen ) {
23
+ trackGlobalClasses( {
24
+ event: 'classManagerFiltersOpened',
25
+ } as TrackingEvent );
26
+ }
27
+ }, [ popupState.isOpen ] );
28
+
20
29
  const showCleanIcon = Object.values( filters ).some( ( value ) => value );
21
30
 
22
31
  return (
@@ -50,6 +59,7 @@ export const CssClassFilter = () => {
50
59
  showCleanIcon
51
60
  ? [
52
61
  <ClearIconButton
62
+ trigger="menu"
53
63
  key="clear-all-button"
54
64
  tooltipText={ __( 'Clear all', 'elementor' ) }
55
65
  />,
@@ -3,6 +3,7 @@ import { Checkbox, Chip, MenuItem, MenuList, Stack, Typography } from '@elemento
3
3
  import { __ } from '@wordpress/i18n';
4
4
 
5
5
  import { type FilterKey, useFilteredCssClassUsage } from '../../../../hooks/use-filtered-css-class-usage';
6
+ import { trackGlobalClasses } from '../../../../utils/tracking';
6
7
  import { useSearchAndFilters } from '../../context';
7
8
 
8
9
  export const filterConfig: Record< FilterKey, string > = {
@@ -19,6 +20,12 @@ export const FilterList = () => {
19
20
 
20
21
  const handleOnClick = ( value: FilterKey ) => {
21
22
  setFilters( ( prev ) => ( { ...prev, [ value ]: ! prev[ value ] } ) );
23
+ trackGlobalClasses( {
24
+ event: 'classManagerFilterUsed',
25
+ action: filters[ value ] ? 'remove' : 'apply',
26
+ type: value,
27
+ trigger: 'menu',
28
+ } );
22
29
  };
23
30
 
24
31
  return (
@@ -3,6 +3,7 @@ import { SearchIcon } from '@elementor/icons';
3
3
  import { Box, InputAdornment, Stack, TextField } from '@elementor/ui';
4
4
  import { __ } from '@wordpress/i18n';
5
5
 
6
+ import { trackGlobalClasses, type TrackingEvent } from '../../../../utils/tracking';
6
7
  import { useSearchAndFilters } from '../../context';
7
8
 
8
9
  export const ClassManagerSearch = () => {
@@ -18,6 +19,11 @@ export const ClassManagerSearch = () => {
18
19
  fullWidth
19
20
  size={ 'tiny' }
20
21
  value={ inputValue }
22
+ onFocus={ () => {
23
+ trackGlobalClasses( {
24
+ event: 'classManagerSearched',
25
+ } as TrackingEvent );
26
+ } }
21
27
  placeholder={ __( 'Search', 'elementor' ) }
22
28
  onChange={ ( e: React.ChangeEvent< HTMLInputElement > ) => handleChange( e.target.value ) }
23
29
  InputProps={ {