@elementor/editor-interactions 4.0.0-manual → 4.1.0-685

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.
@@ -5,7 +5,12 @@ import { isProActive } from '@elementor/utils';
5
5
 
6
6
  import { interactionsRepository } from '../../interactions-repository';
7
7
  import { type ElementInteractions } from '../../types';
8
- import { createInteractionItem, extractString } from '../../utils/prop-value-utils';
8
+ import {
9
+ createInteractionItem,
10
+ extractExcludedBreakpoints,
11
+ extractSize,
12
+ extractString,
13
+ } from '../../utils/prop-value-utils';
9
14
  import { generateTempInteractionId } from '../../utils/temp-id-utils';
10
15
  import { MAX_INTERACTIONS_PER_ELEMENT } from '../constants';
11
16
  import { INTERACTIONS_SCHEMA_URI } from '../resources/interactions-schema-resource';
@@ -16,6 +21,8 @@ const EMPTY_INTERACTIONS: ElementInteractions = {
16
21
  items: [],
17
22
  };
18
23
 
24
+ const EFFECTS_WITHOUT_TYPE = [ 'custom' ];
25
+
19
26
  export const initManageElementInteractionTool = ( reg: MCPRegistryEntry ) => {
20
27
  const { addTool } = reg;
21
28
  const extendedSchema = isProActive() ? { ...baseSchema, ...proSchema } : baseSchema;
@@ -55,18 +62,44 @@ export const initManageElementInteractionTool = ( reg: MCPRegistryEntry ) => {
55
62
  [ key: string ]: unknown;
56
63
  } ) => {
57
64
  const { elementId, action, interactionId, ...animationData } = input;
65
+ const { effectType, ...restAnimationData } = animationData as {
66
+ effectType?: string;
67
+ [ key: string ]: unknown;
68
+ };
69
+ const effect = restAnimationData.effect as string | undefined;
70
+ const resolvedType =
71
+ effectType ?? ( effect && ! EFFECTS_WITHOUT_TYPE.includes( effect ) ? 'in' : undefined );
58
72
 
59
73
  const allInteractions = interactionsRepository.all();
60
74
  const elementData = allInteractions.find( ( data ) => data.elementId === elementId );
61
75
  const currentInteractions: ElementInteractions = elementData?.interactions ?? EMPTY_INTERACTIONS;
62
76
 
63
77
  if ( action === 'get' ) {
78
+ const summary = currentInteractions.items.map( ( item ) => {
79
+ const { value } = item;
80
+ const animValue = value.animation.value;
81
+ const timingValue = animValue.timing_config.value;
82
+ const configValue = animValue.config.value;
83
+
84
+ return {
85
+ id: extractString( value.interaction_id ),
86
+ trigger: extractString( value.trigger ),
87
+ effect: extractString( animValue.effect ),
88
+ effectType: extractString( animValue.type ),
89
+ direction: extractString( animValue.direction ),
90
+ duration: extractSize( timingValue.duration ),
91
+ delay: extractSize( timingValue.delay ),
92
+ easing: extractString( configValue.easing ),
93
+ excludedBreakpoints: extractExcludedBreakpoints( value.breakpoints ),
94
+ };
95
+ } );
96
+
64
97
  return {
65
98
  success: true,
66
99
  elementId,
67
100
  action,
68
- interactions: currentInteractions.items,
69
- count: currentInteractions.items.length,
101
+ interactions: summary,
102
+ count: summary.length,
70
103
  };
71
104
  }
72
105
 
@@ -82,7 +115,8 @@ export const initManageElementInteractionTool = ( reg: MCPRegistryEntry ) => {
82
115
 
83
116
  const newItem = createInteractionItem( {
84
117
  interactionId: generateTempInteractionId(),
85
- ...animationData,
118
+ ...restAnimationData,
119
+ type: resolvedType,
86
120
  } );
87
121
 
88
122
  updatedItems = [ ...updatedItems, newItem ];
@@ -106,7 +140,8 @@ export const initManageElementInteractionTool = ( reg: MCPRegistryEntry ) => {
106
140
 
107
141
  const updatedItem = createInteractionItem( {
108
142
  interactionId,
109
- ...animationData,
143
+ ...restAnimationData,
144
+ type: resolvedType,
110
145
  } );
111
146
 
112
147
  updatedItems = [
package/src/types.ts CHANGED
@@ -31,7 +31,10 @@ export type InteractionConstants = {
31
31
  defaultDelay: number;
32
32
  slideDistance: number;
33
33
  scaleStart: number;
34
- easing: string;
34
+ defaultEasing: string;
35
+ relativeTo: string;
36
+ start: number;
37
+ end: number;
35
38
  };
36
39
 
37
40
  export type InteractionsConfig = {
@@ -47,6 +50,8 @@ export type FieldProps< T = string > = {
47
50
  };
48
51
 
49
52
  export type ReplayFieldProps = FieldProps< boolean >;
53
+ export type RepeatFieldProps = FieldProps< string >;
54
+ export type TimesFieldProps = FieldProps< number >;
50
55
  export type DirectionFieldProps = FieldProps< string > & {
51
56
  interactionType: string;
52
57
  };
@@ -1,5 +1,6 @@
1
1
  import * as React from 'react';
2
- import { forwardRef, type MouseEvent, type RefObject, useImperativeHandle, useState } from 'react';
2
+ import { forwardRef, type MouseEvent, type RefObject, useCallback, useImperativeHandle, useState } from 'react';
3
+ import { type PromotionTrackingData, trackUpgradePromotionClick, trackViewPromotion } from '@elementor/editor-controls';
3
4
  import { PromotionChip, PromotionPopover, useCanvasClickHandler } from '@elementor/editor-ui';
4
5
  import { Box } from '@elementor/ui';
5
6
  import { __ } from '@wordpress/i18n';
@@ -8,6 +9,7 @@ export type InteractionsPromotionChipProps = {
8
9
  content: string;
9
10
  upgradeUrl: string;
10
11
  anchorRef?: RefObject< HTMLElement | null >;
12
+ trackingData: PromotionTrackingData;
11
13
  };
12
14
 
13
15
  export type InteractionsPromotionChipRef = {
@@ -15,14 +17,21 @@ export type InteractionsPromotionChipRef = {
15
17
  };
16
18
 
17
19
  export const InteractionsPromotionChip = forwardRef< InteractionsPromotionChipRef, InteractionsPromotionChipProps >(
18
- ( { content, upgradeUrl, anchorRef }, ref ) => {
20
+ ( { content, upgradeUrl, anchorRef, trackingData }, ref ) => {
19
21
  const [ isOpen, setIsOpen ] = useState( false );
20
22
 
21
23
  useCanvasClickHandler( isOpen, () => setIsOpen( false ) );
22
24
 
23
- const toggle = () => setIsOpen( ( prev ) => ! prev );
25
+ const toggle = useCallback( () => {
26
+ setIsOpen( ( prev ) => {
27
+ if ( ! prev ) {
28
+ trackViewPromotion( trackingData );
29
+ }
30
+ return ! prev;
31
+ } );
32
+ }, [ trackingData ] );
24
33
 
25
- useImperativeHandle( ref, () => ( { toggle } ), [] );
34
+ useImperativeHandle( ref, () => ( { toggle } ), [ toggle ] );
26
35
 
27
36
  const handleToggle = ( e: MouseEvent ) => {
28
37
  e.stopPropagation();
@@ -42,6 +51,7 @@ export const InteractionsPromotionChip = forwardRef< InteractionsPromotionChipRe
42
51
  e.stopPropagation();
43
52
  setIsOpen( false );
44
53
  } }
54
+ onCtaClick={ () => trackUpgradePromotionClick( trackingData ) }
45
55
  >
46
56
  <Box
47
57
  onMouseDown={ ( e: MouseEvent ) => e.stopPropagation() }
@@ -1,5 +1,6 @@
1
1
  import * as React from 'react';
2
2
  import { type MouseEvent, useRef } from 'react';
3
+ import { type PromotionTrackingData } from '@elementor/editor-controls';
3
4
  import { MenuListItem } from '@elementor/editor-ui';
4
5
  import { MenuSubheader, Select, type SelectChangeEvent } from '@elementor/ui';
5
6
  import { __ } from '@wordpress/i18n';
@@ -14,6 +15,7 @@ type PromotionSelectProps = {
14
15
  promotionLabel?: string;
15
16
  promotionContent: string;
16
17
  upgradeUrl: string;
18
+ trackingData: PromotionTrackingData;
17
19
  };
18
20
 
19
21
  export function PromotionSelect( {
@@ -24,6 +26,7 @@ export function PromotionSelect( {
24
26
  promotionLabel,
25
27
  promotionContent,
26
28
  upgradeUrl,
29
+ trackingData,
27
30
  }: PromotionSelectProps ) {
28
31
  const promotionRef = useRef< InteractionsPromotionChipRef >( null );
29
32
  const anchorRef = useRef< HTMLElement >( null );
@@ -63,6 +66,7 @@ export function PromotionSelect( {
63
66
  upgradeUrl={ upgradeUrl }
64
67
  ref={ promotionRef }
65
68
  anchorRef={ anchorRef }
69
+ trackingData={ trackingData }
66
70
  />
67
71
  </MenuSubheader>
68
72
 
@@ -0,0 +1,9 @@
1
+ import { type InteractionItemPropValue } from '@elementor/editor-elements';
2
+
3
+ import { isSupportedInteractionItem } from './is-supported-interaction-item';
4
+
5
+ export const filterInteractions = ( interactions: InteractionItemPropValue[] ) => {
6
+ return interactions.filter( ( interaction ) => {
7
+ return isSupportedInteractionItem( interaction );
8
+ } );
9
+ };
@@ -1,15 +1,5 @@
1
1
  import { type InteractionsConfig } from '../types';
2
2
 
3
- const DEFAULT_CONFIG: InteractionsConfig = {
4
- constants: {
5
- defaultDuration: 300,
6
- defaultDelay: 0,
7
- slideDistance: 100,
8
- scaleStart: 0.5,
9
- easing: 'linear',
10
- },
11
- };
12
-
13
3
  export function getInteractionsConfig(): InteractionsConfig {
14
- return window.ElementorInteractionsConfig || DEFAULT_CONFIG;
4
+ return window.ElementorInteractionsConfig ?? ( {} as InteractionsConfig );
15
5
  }
@@ -0,0 +1,39 @@
1
+ import { getInteractionsControlOptions, type InteractionsControlType } from '../interactions-controls-registry';
2
+ import { type InteractionItemPropValue } from '../types';
3
+ import { extractBoolean, extractString } from '../utils/prop-value-utils';
4
+
5
+ export function isSupportedInteractionItem( interaction: InteractionItemPropValue ): boolean {
6
+ const value = interaction.value;
7
+
8
+ const replay = extractBoolean( value.animation.value.config?.value.replay );
9
+ if ( true === replay ) {
10
+ return hasSupport( 'replay', 'yes' );
11
+ }
12
+
13
+ const trigger = extractString( value.trigger );
14
+ const easing = extractString( value.animation.value.config?.value.easing );
15
+ const effect = extractString( value.animation.value.effect );
16
+
17
+ const checks: Array< [ string, string ] > = [
18
+ [ 'trigger', trigger ],
19
+ [ 'easing', easing ],
20
+ [ 'effect', effect ],
21
+ ];
22
+
23
+ return checks.every( ( [ controlType, controlValue ] ) => {
24
+ if ( controlValue === '' || controlValue === null ) {
25
+ return true;
26
+ }
27
+ return hasSupport( controlType, controlValue );
28
+ } );
29
+ }
30
+
31
+ function hasSupport( controlType: string, controlValue: string ) {
32
+ const supportedOptions = getInteractionsControlOptions( controlType as InteractionsControlType );
33
+
34
+ if ( 1 > supportedOptions.length ) {
35
+ return true;
36
+ }
37
+
38
+ return supportedOptions.includes( controlValue );
39
+ }
@@ -17,6 +17,7 @@ import {
17
17
  type TimingConfigPropValue,
18
18
  } from '../types';
19
19
  import { formatSizeValue, parseSizeValue } from '../utils/size-transform-utils';
20
+ import { getInteractionsConfig } from './get-interactions-config';
20
21
  import { generateTempInteractionId } from './temp-id-utils';
21
22
 
22
23
  export const createString = ( value: string ): StringPropValue => ( {
@@ -46,24 +47,31 @@ export const createConfig = ( {
46
47
  replay,
47
48
  easing = 'easeIn',
48
49
  relativeTo = '',
50
+ repeat = '',
51
+ times = 1,
49
52
  start = 85,
50
53
  end = 15,
51
54
  }: {
52
55
  replay: boolean;
53
56
  easing?: string;
54
57
  relativeTo?: string;
58
+ repeat?: string;
59
+ times?: number;
55
60
  start?: SizeStringValue;
56
61
  end?: SizeStringValue;
57
- } ): ConfigPropValue => ( {
58
- $$type: 'config',
59
- value: {
60
- replay: createBoolean( replay ),
61
- easing: createString( easing ),
62
- relativeTo: createString( relativeTo ),
63
- start: createSize( start, '%' ),
64
- end: createSize( end, '%' ),
65
- },
66
- } );
62
+ } ): ConfigPropValue =>
63
+ ( {
64
+ $$type: 'config',
65
+ value: {
66
+ replay: createBoolean( replay ),
67
+ easing: createString( easing ),
68
+ relativeTo: createString( relativeTo ),
69
+ repeat: createString( repeat ),
70
+ times: createNumber( times ),
71
+ start: createSize( start, '%' ),
72
+ end: createSize( end, '%' ),
73
+ },
74
+ } ) as ConfigPropValue;
67
75
 
68
76
  const createSize = ( value?: SizeStringValue, defaultUnit?: Unit, defaultValue?: SizeStringValue ) => {
69
77
  if ( ! value ) {
@@ -102,6 +110,8 @@ export const createAnimationPreset = ( {
102
110
  replay = false,
103
111
  easing = 'easeIn',
104
112
  relativeTo,
113
+ repeat,
114
+ times,
105
115
  start,
106
116
  end,
107
117
  customEffects,
@@ -114,6 +124,8 @@ export const createAnimationPreset = ( {
114
124
  replay: boolean;
115
125
  easing?: string;
116
126
  relativeTo?: string;
127
+ repeat?: string;
128
+ times?: number;
117
129
  start?: SizeStringValue;
118
130
  end?: SizeStringValue;
119
131
  customEffects?: PropValue;
@@ -129,6 +141,8 @@ export const createAnimationPreset = ( {
129
141
  replay,
130
142
  easing,
131
143
  relativeTo,
144
+ repeat,
145
+ times,
132
146
  start,
133
147
  end,
134
148
  } ),
@@ -146,6 +160,8 @@ export const createInteractionItem = ( {
146
160
  replay = false,
147
161
  easing = 'easeIn',
148
162
  relativeTo,
163
+ repeat,
164
+ times,
149
165
  start,
150
166
  end,
151
167
  excludedBreakpoints,
@@ -161,6 +177,8 @@ export const createInteractionItem = ( {
161
177
  replay?: boolean;
162
178
  easing?: string;
163
179
  relativeTo?: string;
180
+ repeat?: string;
181
+ times?: number;
164
182
  start?: number;
165
183
  end?: number;
166
184
  excludedBreakpoints?: string[];
@@ -179,6 +197,8 @@ export const createInteractionItem = ( {
179
197
  replay,
180
198
  easing,
181
199
  relativeTo,
200
+ repeat,
201
+ times,
182
202
  start,
183
203
  end,
184
204
  customEffects,
@@ -191,14 +211,15 @@ export const createInteractionItem = ( {
191
211
  } );
192
212
 
193
213
  export const createDefaultInteractionItem = (): InteractionItemPropValue => {
214
+ const { constants } = getInteractionsConfig();
194
215
  return createInteractionItem( {
195
216
  trigger: 'load',
196
217
  effect: 'fade',
197
218
  type: 'in',
198
- duration: 600,
199
- delay: 0,
219
+ duration: constants.defaultDuration,
220
+ delay: constants.defaultDelay,
200
221
  replay: false,
201
- easing: 'easeIn',
222
+ easing: constants.defaultEasing,
202
223
  interactionId: generateTempInteractionId(),
203
224
  } );
204
225
  };
@@ -238,7 +259,7 @@ export const buildDisplayLabel = ( item: InteractionItemValue ): string => {
238
259
 
239
260
  const triggerLabel = TRIGGER_LABELS[ trigger ] || capitalize( trigger );
240
261
  const effectLabel = capitalize( effect );
241
- const typeLabel = capitalize( type );
262
+ const typeLabel = 'custom' === effect ? '' : capitalize( type );
242
263
 
243
264
  return `${ triggerLabel }: ${ effectLabel } ${ typeLabel }`;
244
265
  };
@@ -0,0 +1,42 @@
1
+ import { getElementLabel } from '@elementor/editor-elements';
2
+ import { getMixpanel } from '@elementor/events';
3
+
4
+ import type { InteractionItemPropValue } from '../types';
5
+ import { extractString } from './prop-value-utils';
6
+
7
+ const TRIGGER_LABELS: Record< string, string > = {
8
+ load: 'On page load',
9
+ scrollIn: 'Scroll into view',
10
+ scrollOut: 'Scroll out of view',
11
+ scrollOn: 'While scrolling',
12
+ hover: 'Hover',
13
+ click: 'Click',
14
+ };
15
+
16
+ const capitalize = ( s: string ) => s.charAt( 0 ).toUpperCase() + s.slice( 1 );
17
+
18
+ export const trackInteractionCreated = ( elementId: string, item: InteractionItemPropValue ) => {
19
+ const { dispatchEvent, config } = getMixpanel();
20
+ if ( ! config?.names?.interactions?.created ) {
21
+ return;
22
+ }
23
+
24
+ const trigger = extractString( item.value.trigger );
25
+ const effect = extractString( item.value.animation.value.effect );
26
+ const type = extractString( item.value.animation.value.type );
27
+
28
+ dispatchEvent?.( config.names.interactions.created, {
29
+ app_type: config?.appTypes?.editor,
30
+ window_name: config?.appTypes?.editor,
31
+ interaction_type: config?.triggers?.click,
32
+ target_name: getElementLabel( elementId ),
33
+ interaction_result: 'interaction_created',
34
+ target_location: config?.locations?.widgetPanel,
35
+ location_l1: getElementLabel( elementId ),
36
+ location_l2: 'interactions',
37
+ interaction_description: 'interaction_created',
38
+ interaction_trigger: TRIGGER_LABELS[ trigger ] ?? capitalize( trigger ),
39
+ interaction_effect:
40
+ effect === 'custom' ? capitalize( effect ) : `${ capitalize( effect ) } ${ capitalize( type ) }`,
41
+ } );
42
+ };