@elementor/editor-interactions 4.0.0-manual → 4.0.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.
Files changed (36) hide show
  1. package/dist/index.d.mts +123 -5
  2. package/dist/index.d.ts +123 -5
  3. package/dist/index.js +861 -334
  4. package/dist/index.js.map +1 -1
  5. package/dist/index.mjs +823 -312
  6. package/dist/index.mjs.map +1 -1
  7. package/package.json +12 -11
  8. package/src/commands/get-clipboard-elements.ts +11 -0
  9. package/src/commands/paste-interactions.ts +136 -0
  10. package/src/components/controls/easing.tsx +3 -0
  11. package/src/components/controls/effect.tsx +3 -0
  12. package/src/components/controls/repeat.tsx +57 -0
  13. package/src/components/controls/replay.tsx +15 -13
  14. package/src/components/controls/trigger.tsx +3 -0
  15. package/src/components/interaction-details.tsx +162 -120
  16. package/src/components/interactions-list.tsx +16 -3
  17. package/src/components/interactions-tab.tsx +4 -1
  18. package/src/contexts/interactions-context.tsx +4 -1
  19. package/src/hooks/use-element-interactions.ts +26 -0
  20. package/src/index.ts +26 -0
  21. package/src/init.ts +13 -3
  22. package/src/interactions-controls-registry.ts +15 -3
  23. package/src/mcp/constants.ts +58 -0
  24. package/src/mcp/index.ts +15 -69
  25. package/src/mcp/tools/manage-element-interaction-tool.ts +40 -5
  26. package/src/mcp/tools/schema.ts +1 -1
  27. package/src/types.ts +6 -1
  28. package/src/ui/interactions-promotion-chip.tsx +14 -6
  29. package/src/ui/promotion-overlay-layout.tsx +22 -0
  30. package/src/ui/promotion-select.tsx +4 -0
  31. package/src/utils/custom-effect-to-prop-value.ts +145 -0
  32. package/src/utils/filter-interactions.ts +9 -0
  33. package/src/utils/get-interactions-config.ts +1 -11
  34. package/src/utils/is-supported-interaction-item.ts +39 -0
  35. package/src/utils/prop-value-utils.ts +59 -34
  36. package/src/utils/tracking.ts +42 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elementor/editor-interactions",
3
- "version": "4.0.0-manual",
3
+ "version": "4.0.0",
4
4
  "private": false,
5
5
  "author": "Elementor Team",
6
6
  "homepage": "https://elementor.com/",
@@ -39,18 +39,19 @@
39
39
  "dev": "tsup --config=../../tsup.dev.ts"
40
40
  },
41
41
  "dependencies": {
42
- "@elementor/editor-controls": "4.0.0-manual",
43
- "@elementor/editor-elements": "4.0.0-manual",
44
- "@elementor/editor-mcp": "4.0.0-manual",
45
- "@elementor/editor-props": "4.0.0-manual",
46
- "@elementor/editor-responsive": "4.0.0-manual",
47
- "@elementor/editor-ui": "4.0.0-manual",
48
- "@elementor/editor-v1-adapters": "4.0.0-manual",
42
+ "@elementor/editor-controls": "4.0.0",
43
+ "@elementor/editor-elements": "4.0.0",
44
+ "@elementor/editor-mcp": "4.0.0",
45
+ "@elementor/editor-props": "4.0.0",
46
+ "@elementor/editor-responsive": "4.0.0",
47
+ "@elementor/editor-ui": "4.0.0",
48
+ "@elementor/editor-v1-adapters": "4.0.0",
49
49
  "@elementor/icons": "^1.68.0",
50
- "@elementor/schema": "4.0.0-manual",
51
- "@elementor/session": "4.0.0-manual",
50
+ "@elementor/schema": "4.0.0",
51
+ "@elementor/session": "4.0.0",
52
52
  "@elementor/ui": "1.36.17",
53
- "@elementor/utils": "4.0.0-manual",
53
+ "@elementor/utils": "4.0.0",
54
+ "@elementor/events": "4.0.0",
54
55
  "@wordpress/i18n": "^5.13.0"
55
56
  },
56
57
  "peerDependencies": {
@@ -0,0 +1,11 @@
1
+ import type { V1ElementModelProps } from '@elementor/editor-elements';
2
+
3
+ export function getClipboardElements( storageKey: string = 'clipboard' ): V1ElementModelProps[] | undefined {
4
+ try {
5
+ const storedData = JSON.parse( localStorage.getItem( 'elementor' ) ?? '{}' );
6
+
7
+ return storedData[ storageKey ]?.elements as V1ElementModelProps[] | undefined;
8
+ } catch {
9
+ return undefined;
10
+ }
11
+ }
@@ -0,0 +1,136 @@
1
+ import {
2
+ getContainer,
3
+ getElementInteractions,
4
+ getElementLabel,
5
+ getWidgetsCache,
6
+ updateElementInteractions,
7
+ type V1Element,
8
+ type V1ElementModelProps,
9
+ } from '@elementor/editor-elements';
10
+ import {
11
+ __privateListenTo as listenTo,
12
+ type CommandEvent,
13
+ commandStartEvent,
14
+ undoable,
15
+ } from '@elementor/editor-v1-adapters';
16
+ import { __ } from '@wordpress/i18n';
17
+
18
+ import type { ElementInteractions } from '../types';
19
+ import { createString } from '../utils/prop-value-utils';
20
+ import { generateTempInteractionId } from '../utils/temp-id-utils';
21
+ import { getClipboardElements } from './get-clipboard-elements';
22
+
23
+ function isAtomicContainer( container: V1Element ): boolean {
24
+ const type = container?.model.get( 'widgetType' ) || container?.model.get( 'elType' );
25
+ const widgetsCache = getWidgetsCache();
26
+ const elementConfig = widgetsCache?.[ type ];
27
+
28
+ return Boolean( elementConfig?.atomic_props_schema );
29
+ }
30
+
31
+ type PasteInteractionsCommandArgs = {
32
+ container?: V1Element;
33
+ containers?: V1Element[];
34
+ storageKey?: string;
35
+ };
36
+
37
+ type PasteInteractionsPayload = {
38
+ containers: V1Element[];
39
+ newInteractions: ElementInteractions;
40
+ };
41
+
42
+ function getTitleForContainers( containers: V1Element[] ): string {
43
+ return containers.length > 1 ? __( 'Elements', 'elementor' ) : getElementLabel( containers[ 0 ].id );
44
+ }
45
+
46
+ function normalizeClipboardInteractions( raw: V1ElementModelProps[ 'interactions' ] ): ElementInteractions | null {
47
+ if ( ! raw ) {
48
+ return null;
49
+ }
50
+
51
+ const parsed: ElementInteractions = typeof raw === 'string' ? ( JSON.parse( raw ) as ElementInteractions ) : raw;
52
+
53
+ if ( ! parsed?.items?.length ) {
54
+ return null;
55
+ }
56
+
57
+ return { version: parsed.version ?? 1, items: parsed.items };
58
+ }
59
+
60
+ function regenerateInteractionIds( interactions: ElementInteractions ): ElementInteractions {
61
+ const cloned = structuredClone( interactions ) as ElementInteractions;
62
+
63
+ cloned.items?.forEach( ( item ) => {
64
+ if ( item.$$type === 'interaction-item' && item.value ) {
65
+ item.value.interaction_id = createString( generateTempInteractionId() );
66
+ }
67
+ } );
68
+
69
+ return cloned;
70
+ }
71
+
72
+ export function initPasteInteractionsCommand() {
73
+ const undoablePasteInteractions = undoable(
74
+ {
75
+ do: ( { containers, newInteractions }: PasteInteractionsPayload ) => {
76
+ const pasted = regenerateInteractionIds( newInteractions );
77
+
78
+ return containers.map( ( container ) => {
79
+ const elementId = container.id;
80
+ const previous = getElementInteractions( elementId );
81
+
82
+ updateElementInteractions( {
83
+ elementId,
84
+ interactions: pasted,
85
+ } );
86
+
87
+ return { elementId, previous: previous ?? { version: 1, items: [] } };
88
+ } );
89
+ },
90
+ undo: ( _: PasteInteractionsPayload, revertData ) => {
91
+ revertData.forEach( ( { elementId, previous } ) => {
92
+ updateElementInteractions( {
93
+ elementId,
94
+ interactions: previous.items?.length ? previous : undefined,
95
+ } );
96
+ } );
97
+ },
98
+ },
99
+ {
100
+ title: ( { containers } ) => getTitleForContainers( containers ),
101
+ subtitle: __( 'Interactions Pasted', 'elementor' ),
102
+ }
103
+ );
104
+
105
+ listenTo( commandStartEvent( 'document/elements/paste-interactions' ), ( e ) => {
106
+ const args = ( e as CommandEvent ).args as PasteInteractionsCommandArgs;
107
+ const containers = args.containers ?? ( args.container ? [ args.container ] : [] );
108
+ const storageKey = args.storageKey ?? 'clipboard';
109
+
110
+ if ( ! containers.length ) {
111
+ return;
112
+ }
113
+
114
+ const clipboardElements = getClipboardElements( storageKey );
115
+ const [ clipboardElement ] = clipboardElements ?? [];
116
+
117
+ if ( ! clipboardElement ) {
118
+ return;
119
+ }
120
+
121
+ const newInteractions = normalizeClipboardInteractions( clipboardElement.interactions );
122
+
123
+ if ( ! newInteractions ) {
124
+ return;
125
+ }
126
+
127
+ const existingContainers = containers.filter( ( c ) => getContainer( c.id ) ) as V1Element[];
128
+ const validContainers = existingContainers.filter( isAtomicContainer );
129
+
130
+ if ( ! validContainers.length ) {
131
+ return;
132
+ }
133
+
134
+ undoablePasteInteractions( { containers: validContainers, newInteractions } );
135
+ } );
136
+ }
@@ -5,6 +5,8 @@ import { type FieldProps } from '../../types';
5
5
  import { PromotionSelect } from '../../ui/promotion-select';
6
6
  import { DEFAULT_VALUES } from '../interaction-details';
7
7
 
8
+ const TRACKING_DATA = { target_name: 'interactions_easing', location_l2: 'interactions' } as const;
9
+
8
10
  export const EASING_OPTIONS = {
9
11
  easeIn: __( 'Ease In', 'elementor' ),
10
12
  easeInOut: __( 'Ease In Out', 'elementor' ),
@@ -33,6 +35,7 @@ export function Easing( {}: FieldProps ) {
33
35
  disabledOptions={ disabledOptions }
34
36
  promotionContent={ __( 'Upgrade to control the smoothness of the interaction.', 'elementor' ) }
35
37
  upgradeUrl="https://go.elementor.com/go-pro-interactions-easing-modal/"
38
+ trackingData={ TRACKING_DATA }
36
39
  />
37
40
  );
38
41
  }
@@ -5,6 +5,8 @@ import { type FieldProps } from '../../types';
5
5
  import { PromotionSelect } from '../../ui/promotion-select';
6
6
  import { DEFAULT_VALUES } from '../interaction-details';
7
7
 
8
+ const TRACKING_DATA = { target_name: 'interactions_effect', location_l2: 'interactions' } as const;
9
+
8
10
  export const EFFECT_OPTIONS = {
9
11
  fade: __( 'Fade', 'elementor' ),
10
12
  slide: __( 'Slide', 'elementor' ),
@@ -35,6 +37,7 @@ export function Effect( { value, onChange }: FieldProps ) {
35
37
  'elementor'
36
38
  ) }
37
39
  upgradeUrl="https://go.elementor.com/go-pro-interactions-custom-effect-modal/"
40
+ trackingData={ TRACKING_DATA }
38
41
  />
39
42
  );
40
43
  }
@@ -0,0 +1,57 @@
1
+ import * as React from 'react';
2
+ import { useRef } from 'react';
3
+ import { type ToggleButtonGroupItem, ToggleButtonGroupUi } from '@elementor/editor-controls';
4
+ import { Number123Icon, RepeatIcon } from '@elementor/icons';
5
+ import { __ } from '@wordpress/i18n';
6
+
7
+ import { InteractionsPromotionChip } from '../../ui/interactions-promotion-chip';
8
+ import { PromotionOverlayLayout } from '../../ui/promotion-overlay-layout';
9
+
10
+ const TRACKING_DATA = { target_name: 'interactions_repeat', location_l2: 'interactions' } as const;
11
+
12
+ export const REPEAT_OPTIONS = {
13
+ times: __( 'times', 'elementor' ),
14
+ loop: __( 'loop', 'elementor' ),
15
+ };
16
+
17
+ export const REPEAT_TOOLTIPS = {
18
+ times: __( 'Enable number', 'elementor' ),
19
+ loop: __( 'Infinite repeat', 'elementor' ),
20
+ };
21
+
22
+ export function Repeat() {
23
+ const repeatContainerRef = useRef< HTMLDivElement >( null );
24
+
25
+ const options: ToggleButtonGroupItem< string >[] = [
26
+ {
27
+ value: REPEAT_OPTIONS.times,
28
+ disabled: true,
29
+ label: REPEAT_TOOLTIPS.times,
30
+ renderContent: ( { size } ) => <Number123Icon fontSize={ size } />,
31
+ showTooltip: true,
32
+ },
33
+ {
34
+ value: REPEAT_OPTIONS.loop,
35
+ disabled: true,
36
+ label: REPEAT_TOOLTIPS.loop,
37
+ renderContent: ( { size } ) => <RepeatIcon fontSize={ size } />,
38
+ showTooltip: true,
39
+ },
40
+ ];
41
+
42
+ return (
43
+ <PromotionOverlayLayout
44
+ ref={ repeatContainerRef }
45
+ promotionChip={
46
+ <InteractionsPromotionChip
47
+ content={ __( 'Upgrade to control how many times the animation repeats.', 'elementor' ) }
48
+ upgradeUrl={ 'https://go.elementor.com/go-pro-interactions-repeat-modal/' }
49
+ anchorRef={ repeatContainerRef }
50
+ trackingData={ TRACKING_DATA }
51
+ />
52
+ }
53
+ >
54
+ <ToggleButtonGroupUi items={ options } exclusive onChange={ () => {} } value={ '' } />
55
+ </PromotionOverlayLayout>
56
+ );
57
+ }
@@ -1,11 +1,14 @@
1
1
  import * as React from 'react';
2
+ import { useRef } from 'react';
2
3
  import { type ToggleButtonGroupItem, ToggleButtonGroupUi } from '@elementor/editor-controls';
3
4
  import { CheckIcon, MinusIcon } from '@elementor/icons';
4
- import { Box } from '@elementor/ui';
5
5
  import { __ } from '@wordpress/i18n';
6
6
 
7
7
  import { type ReplayFieldProps } from '../../types';
8
8
  import { InteractionsPromotionChip } from '../../ui/interactions-promotion-chip';
9
+ import { PromotionOverlayLayout } from '../../ui/promotion-overlay-layout';
10
+
11
+ const TRACKING_DATA = { target_name: 'interactions_replay', location_l2: 'interactions' } as const;
9
12
 
10
13
  export const REPLAY_OPTIONS = {
11
14
  no: __( 'No', 'elementor' ),
@@ -14,10 +17,8 @@ export const REPLAY_OPTIONS = {
14
17
 
15
18
  export const BASE_REPLAY: string[] = [ 'no' ];
16
19
 
17
- const OVERLAY_GRID = '1 / 1';
18
- const CHIP_OFFSET = '50%';
19
-
20
- export function Replay( { onChange, anchorRef }: ReplayFieldProps ) {
20
+ export function Replay( { onChange }: ReplayFieldProps ) {
21
+ const replayContainerRef = useRef< HTMLDivElement >( null );
21
22
  const options: ToggleButtonGroupItem< boolean >[] = [
22
23
  {
23
24
  value: false,
@@ -36,17 +37,18 @@ export function Replay( { onChange, anchorRef }: ReplayFieldProps ) {
36
37
  ];
37
38
 
38
39
  return (
39
- <Box sx={ { display: 'grid', alignItems: 'center' } }>
40
- <Box sx={ { gridArea: OVERLAY_GRID } }>
41
- <ToggleButtonGroupUi items={ options } exclusive onChange={ onChange } value={ false } />
42
- </Box>
43
- <Box sx={ { gridArea: OVERLAY_GRID, marginInlineEnd: CHIP_OFFSET, justifySelf: 'end' } }>
40
+ <PromotionOverlayLayout
41
+ ref={ replayContainerRef }
42
+ promotionChip={
44
43
  <InteractionsPromotionChip
45
44
  content={ __( 'Upgrade to run the animation every time its trigger occurs.', 'elementor' ) }
46
45
  upgradeUrl={ 'https://go.elementor.com/go-pro-interactions-replay-modal/' }
47
- anchorRef={ anchorRef }
46
+ anchorRef={ replayContainerRef }
47
+ trackingData={ TRACKING_DATA }
48
48
  />
49
- </Box>
50
- </Box>
49
+ }
50
+ >
51
+ <ToggleButtonGroupUi items={ options } exclusive onChange={ onChange } value={ false } />
52
+ </PromotionOverlayLayout>
51
53
  );
52
54
  }
@@ -5,6 +5,8 @@ import { type FieldProps } from '../../types';
5
5
  import { PromotionSelect } from '../../ui/promotion-select';
6
6
  import { DEFAULT_VALUES } from '../interaction-details';
7
7
 
8
+ const TRACKING_DATA = { target_name: 'interactions_trigger', location_l2: 'interactions' } as const;
9
+
8
10
  export const TRIGGER_OPTIONS = {
9
11
  load: __( 'Page load', 'elementor' ),
10
12
  scrollIn: __( 'Scroll into view', 'elementor' ),
@@ -33,6 +35,7 @@ export function Trigger( { value, onChange }: FieldProps ) {
33
35
  promotionLabel={ __( 'PRO triggers', 'elementor' ) }
34
36
  promotionContent={ __( 'Upgrade to unlock more interactions triggers.', 'elementor' ) }
35
37
  upgradeUrl="https://go.elementor.com/go-pro-interactions-triggers-modal/"
38
+ trackingData={ TRACKING_DATA }
36
39
  />
37
40
  );
38
41
  }