@elementor/editor-interactions 3.33.0-224
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/dist/index.d.mts +47 -0
- package/dist/index.d.ts +47 -0
- package/dist/index.js +417 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +376 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +54 -0
- package/src/components/controls/direction.tsx +43 -0
- package/src/components/controls/effect-type.tsx +40 -0
- package/src/components/controls/effect.tsx +40 -0
- package/src/components/controls/time-frame-indicator.tsx +43 -0
- package/src/components/controls/trigger.tsx +44 -0
- package/src/components/empty-state.tsx +34 -0
- package/src/components/header.tsx +22 -0
- package/src/components/interaction-details.tsx +71 -0
- package/src/components/interactions-list.tsx +113 -0
- package/src/contexts/popup-state-contex.tsx +36 -0
- package/src/index.ts +5 -0
- package/src/types.ts +23 -0
- package/src/utils/format-interaction-label.ts +32 -0
- package/src/utils/get-interactions-config.ts +16 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { MenuListItem } from '@elementor/editor-ui';
|
|
3
|
+
import { Grid, Select, type SelectChangeEvent, Typography } from '@elementor/ui';
|
|
4
|
+
import { __ } from '@wordpress/i18n';
|
|
5
|
+
|
|
6
|
+
import { type FieldProps } from '../../types';
|
|
7
|
+
|
|
8
|
+
export function TimeFrameIndicator( { value, onChange, label }: FieldProps ) {
|
|
9
|
+
const availableTimeFrames = [ '0', '100', '200', '300', '400', '500', '750', '1000', '1250', '1500' ].map(
|
|
10
|
+
( key ) => ( {
|
|
11
|
+
key,
|
|
12
|
+
// translators: %s: time in milliseconds
|
|
13
|
+
label: __( '%s MS', 'elementor' ).replace( '%s', key ),
|
|
14
|
+
} )
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<>
|
|
19
|
+
<Grid item xs={ 12 } md={ 6 }>
|
|
20
|
+
<Typography variant="caption" color="text.secondary">
|
|
21
|
+
{ label }
|
|
22
|
+
</Typography>
|
|
23
|
+
</Grid>
|
|
24
|
+
<Grid item xs={ 12 } md={ 6 }>
|
|
25
|
+
<Select
|
|
26
|
+
fullWidth
|
|
27
|
+
displayEmpty
|
|
28
|
+
size="tiny"
|
|
29
|
+
value={ value }
|
|
30
|
+
onChange={ ( event: SelectChangeEvent< string > ) => onChange( event.target.value ) }
|
|
31
|
+
>
|
|
32
|
+
{ availableTimeFrames.map( ( timeFrame ) => {
|
|
33
|
+
return (
|
|
34
|
+
<MenuListItem key={ timeFrame.key } value={ timeFrame.key }>
|
|
35
|
+
{ timeFrame.label }
|
|
36
|
+
</MenuListItem>
|
|
37
|
+
);
|
|
38
|
+
} ) }
|
|
39
|
+
</Select>
|
|
40
|
+
</Grid>
|
|
41
|
+
</>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { MenuListItem } from '@elementor/editor-ui';
|
|
3
|
+
import { Grid, Select, type SelectChangeEvent, Typography } from '@elementor/ui';
|
|
4
|
+
import { __ } from '@wordpress/i18n';
|
|
5
|
+
|
|
6
|
+
import { type FieldProps } from '../../types';
|
|
7
|
+
|
|
8
|
+
export function Trigger( { value, onChange }: FieldProps ) {
|
|
9
|
+
const availableTriggers = Object.entries( {
|
|
10
|
+
load: __( 'Page load', 'elementor' ),
|
|
11
|
+
scrollIn: __( 'Scroll into view', 'elementor' ),
|
|
12
|
+
scrollOut: __( 'Scroll out of view', 'elementor' ),
|
|
13
|
+
} ).map( ( [ key, label ] ) => ( {
|
|
14
|
+
key,
|
|
15
|
+
label,
|
|
16
|
+
} ) );
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<>
|
|
20
|
+
<Grid item xs={ 12 } md={ 6 }>
|
|
21
|
+
<Typography variant="caption" color="text.secondary">
|
|
22
|
+
{ __( 'Trigger', 'elementor' ) }
|
|
23
|
+
</Typography>
|
|
24
|
+
</Grid>
|
|
25
|
+
<Grid item xs={ 12 } md={ 6 }>
|
|
26
|
+
<Select
|
|
27
|
+
fullWidth
|
|
28
|
+
displayEmpty
|
|
29
|
+
size="tiny"
|
|
30
|
+
onChange={ ( event: SelectChangeEvent< string > ) => onChange( event.target.value ) }
|
|
31
|
+
value={ value }
|
|
32
|
+
>
|
|
33
|
+
{ availableTriggers.map( ( trigger ) => {
|
|
34
|
+
return (
|
|
35
|
+
<MenuListItem key={ trigger.key } value={ trigger.key }>
|
|
36
|
+
{ trigger.label }
|
|
37
|
+
</MenuListItem>
|
|
38
|
+
);
|
|
39
|
+
} ) }
|
|
40
|
+
</Select>
|
|
41
|
+
</Grid>
|
|
42
|
+
</>
|
|
43
|
+
);
|
|
44
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { SwipeIcon } from '@elementor/icons';
|
|
3
|
+
import { Button, Stack, Typography } from '@elementor/ui';
|
|
4
|
+
import { __ } from '@wordpress/i18n';
|
|
5
|
+
|
|
6
|
+
export const EmptyState = ( { onCreateInteraction }: { onCreateInteraction: () => void } ) => {
|
|
7
|
+
return (
|
|
8
|
+
<Stack
|
|
9
|
+
alignItems="center"
|
|
10
|
+
justifyContent="center"
|
|
11
|
+
height="100%"
|
|
12
|
+
color="text.secondary"
|
|
13
|
+
sx={ { p: 2.5, pt: 8, pb: 5.5 } }
|
|
14
|
+
gap={ 1.5 }
|
|
15
|
+
>
|
|
16
|
+
<SwipeIcon fontSize="large" />
|
|
17
|
+
|
|
18
|
+
<Typography align="center" variant="subtitle2">
|
|
19
|
+
{ __( 'Animate elements with Interactions', 'elementor' ) }
|
|
20
|
+
</Typography>
|
|
21
|
+
|
|
22
|
+
<Typography align="center" variant="caption" maxWidth="170px">
|
|
23
|
+
{ __(
|
|
24
|
+
'Add entrance animations and effects triggered by user interactions such as click, hover, or scroll.',
|
|
25
|
+
'elementor'
|
|
26
|
+
) }
|
|
27
|
+
</Typography>
|
|
28
|
+
|
|
29
|
+
<Button variant="outlined" color="secondary" size="small" sx={ { mt: 1 } } onClick={ onCreateInteraction }>
|
|
30
|
+
{ __( 'Create an interaction', 'elementor' ) }
|
|
31
|
+
</Button>
|
|
32
|
+
</Stack>
|
|
33
|
+
);
|
|
34
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { PlusIcon } from '@elementor/icons';
|
|
3
|
+
import { IconButton, Stack, Typography } from '@elementor/ui';
|
|
4
|
+
|
|
5
|
+
export const Header = ( { label }: { label: string } ) => {
|
|
6
|
+
return (
|
|
7
|
+
<Stack
|
|
8
|
+
direction="row"
|
|
9
|
+
alignItems="center"
|
|
10
|
+
justifyContent="space-between"
|
|
11
|
+
gap={ 1 }
|
|
12
|
+
sx={ { marginInlineEnd: -0.75, py: 0.25 } }
|
|
13
|
+
>
|
|
14
|
+
<Typography component="label" variant="caption" color="text.secondary" sx={ { lineHeight: 1 } }>
|
|
15
|
+
{ label }
|
|
16
|
+
</Typography>
|
|
17
|
+
<IconButton size="tiny" disabled>
|
|
18
|
+
<PlusIcon fontSize="tiny" />
|
|
19
|
+
</IconButton>
|
|
20
|
+
</Stack>
|
|
21
|
+
);
|
|
22
|
+
};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { useEffect, useState } from 'react';
|
|
3
|
+
import { Divider, Grid } from '@elementor/ui';
|
|
4
|
+
import { __ } from '@wordpress/i18n';
|
|
5
|
+
|
|
6
|
+
import { Direction } from './controls/direction';
|
|
7
|
+
import { Effect } from './controls/effect';
|
|
8
|
+
import { EffectType } from './controls/effect-type';
|
|
9
|
+
import { TimeFrameIndicator } from './controls/time-frame-indicator';
|
|
10
|
+
import { Trigger } from './controls/trigger';
|
|
11
|
+
|
|
12
|
+
const DELIMITER = '-';
|
|
13
|
+
|
|
14
|
+
type InteractionDetailsProps = {
|
|
15
|
+
interaction: string;
|
|
16
|
+
onChange: ( interaction: string ) => void;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export const InteractionDetails = ( { interaction, onChange }: InteractionDetailsProps ) => {
|
|
20
|
+
const [ interactionDetails, setInteractionDetails ] = useState( () => {
|
|
21
|
+
const [ trigger, effect, type, direction, duration, delay ] = interaction.split( DELIMITER );
|
|
22
|
+
|
|
23
|
+
return {
|
|
24
|
+
trigger: trigger || 'load',
|
|
25
|
+
effect: effect || 'fade',
|
|
26
|
+
type: type || 'in',
|
|
27
|
+
direction: direction || '',
|
|
28
|
+
duration: duration || '300',
|
|
29
|
+
delay: delay || '0',
|
|
30
|
+
};
|
|
31
|
+
} );
|
|
32
|
+
|
|
33
|
+
useEffect( () => {
|
|
34
|
+
const newValue = Object.values( interactionDetails ).join( DELIMITER );
|
|
35
|
+
onChange( newValue );
|
|
36
|
+
}, [ interactionDetails, onChange ] );
|
|
37
|
+
|
|
38
|
+
const handleChange = < K extends keyof typeof interactionDetails >(
|
|
39
|
+
key: K,
|
|
40
|
+
value: ( typeof interactionDetails )[ K ]
|
|
41
|
+
) => {
|
|
42
|
+
setInteractionDetails( ( prev ) => ( { ...prev, [ key ]: value } ) );
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<>
|
|
47
|
+
<Grid container spacing={ 2 } sx={ { width: '300px', p: 1 } }>
|
|
48
|
+
<Trigger value={ interactionDetails.trigger } onChange={ ( v ) => handleChange( 'trigger', v ) } />
|
|
49
|
+
</Grid>
|
|
50
|
+
<Divider />
|
|
51
|
+
<Grid container spacing={ 2 } sx={ { width: '300px', p: 1 } }>
|
|
52
|
+
<Effect value={ interactionDetails.effect } onChange={ ( v ) => handleChange( 'effect', v ) } />
|
|
53
|
+
<EffectType value={ interactionDetails.type } onChange={ ( v ) => handleChange( 'type', v ) } />
|
|
54
|
+
<Direction
|
|
55
|
+
value={ interactionDetails.direction ?? '' }
|
|
56
|
+
onChange={ ( v ) => handleChange( 'direction', v ) }
|
|
57
|
+
/>
|
|
58
|
+
<TimeFrameIndicator
|
|
59
|
+
value={ interactionDetails.duration }
|
|
60
|
+
onChange={ ( v ) => handleChange( 'duration', v ) }
|
|
61
|
+
label={ __( 'Duration', 'elementor' ) }
|
|
62
|
+
/>
|
|
63
|
+
<TimeFrameIndicator
|
|
64
|
+
value={ interactionDetails.delay }
|
|
65
|
+
onChange={ ( v ) => handleChange( 'delay', v ) }
|
|
66
|
+
label={ __( 'Delay', 'elementor' ) }
|
|
67
|
+
/>
|
|
68
|
+
</Grid>
|
|
69
|
+
</>
|
|
70
|
+
);
|
|
71
|
+
};
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { useEffect, useId, useMemo, useRef, useState } from 'react';
|
|
3
|
+
import { EyeIcon, XIcon } from '@elementor/icons';
|
|
4
|
+
import { bindPopover, bindTrigger, IconButton, Popover, Stack, UnstableTag, usePopupState } from '@elementor/ui';
|
|
5
|
+
import { __ } from '@wordpress/i18n';
|
|
6
|
+
|
|
7
|
+
import { usePopupStateContext } from '../contexts/popup-state-contex';
|
|
8
|
+
import { formatInteractionLabel } from '../utils/format-interaction-label';
|
|
9
|
+
import { Header } from './header';
|
|
10
|
+
import { InteractionDetails } from './interaction-details';
|
|
11
|
+
|
|
12
|
+
type PredefinedInteractionsListProps = {
|
|
13
|
+
onSelectInteraction: ( interaction: string ) => void;
|
|
14
|
+
selectedInteraction: string;
|
|
15
|
+
onDelete?: () => void;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export const PredefinedInteractionsList = ( {
|
|
19
|
+
onSelectInteraction,
|
|
20
|
+
selectedInteraction,
|
|
21
|
+
onDelete,
|
|
22
|
+
}: PredefinedInteractionsListProps ) => {
|
|
23
|
+
return (
|
|
24
|
+
<Stack sx={ { m: 1, p: 1.5 } } gap={ 2 }>
|
|
25
|
+
<Header label={ __( 'Interactions', 'elementor' ) } />
|
|
26
|
+
<InteractionsList
|
|
27
|
+
onDelete={ () => onDelete?.() }
|
|
28
|
+
selectedInteraction={ selectedInteraction }
|
|
29
|
+
onSelectInteraction={ onSelectInteraction }
|
|
30
|
+
/>
|
|
31
|
+
</Stack>
|
|
32
|
+
);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
type InteractionListProps = {
|
|
36
|
+
onDelete: () => void;
|
|
37
|
+
onSelectInteraction: ( interaction: string ) => void;
|
|
38
|
+
selectedInteraction: string;
|
|
39
|
+
defaultStateRef?: React.MutableRefObject< boolean | undefined >;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
function InteractionsList( { onSelectInteraction, selectedInteraction, defaultStateRef }: InteractionListProps ) {
|
|
43
|
+
const [ interactionId, setInteractionId ] = useState( selectedInteraction );
|
|
44
|
+
|
|
45
|
+
const anchorEl = useRef< HTMLDivElement | null >( null );
|
|
46
|
+
|
|
47
|
+
const popupId = useId();
|
|
48
|
+
const popupState = usePopupState( {
|
|
49
|
+
variant: 'popover',
|
|
50
|
+
popupId: `elementor-interactions-list-${ popupId }`,
|
|
51
|
+
} );
|
|
52
|
+
|
|
53
|
+
const { openByDefault, resetDefaultOpen } = usePopupStateContext();
|
|
54
|
+
|
|
55
|
+
useEffect( () => {
|
|
56
|
+
if ( interactionId ) {
|
|
57
|
+
onSelectInteraction( interactionId );
|
|
58
|
+
}
|
|
59
|
+
}, [ interactionId, onSelectInteraction ] );
|
|
60
|
+
|
|
61
|
+
useEffect( () => {
|
|
62
|
+
if ( openByDefault && anchorEl.current ) {
|
|
63
|
+
popupState.open();
|
|
64
|
+
resetDefaultOpen();
|
|
65
|
+
}
|
|
66
|
+
}, [ defaultStateRef, openByDefault, popupState, resetDefaultOpen ] );
|
|
67
|
+
|
|
68
|
+
const displayLabel = useMemo( () => {
|
|
69
|
+
return formatInteractionLabel( interactionId );
|
|
70
|
+
}, [ interactionId ] );
|
|
71
|
+
|
|
72
|
+
return (
|
|
73
|
+
<Stack gap={ 1.5 } ref={ anchorEl }>
|
|
74
|
+
<UnstableTag
|
|
75
|
+
{ ...bindTrigger( popupState ) }
|
|
76
|
+
fullWidth
|
|
77
|
+
variant="outlined"
|
|
78
|
+
label={ displayLabel }
|
|
79
|
+
showActionsOnHover
|
|
80
|
+
actions={
|
|
81
|
+
<>
|
|
82
|
+
<IconButton size="tiny" disabled>
|
|
83
|
+
<EyeIcon fontSize="tiny" />
|
|
84
|
+
</IconButton>
|
|
85
|
+
<IconButton size="tiny">
|
|
86
|
+
<XIcon fontSize="tiny" />
|
|
87
|
+
</IconButton>
|
|
88
|
+
</>
|
|
89
|
+
}
|
|
90
|
+
/>
|
|
91
|
+
<Popover
|
|
92
|
+
{ ...bindPopover( popupState ) }
|
|
93
|
+
disableScrollLock
|
|
94
|
+
anchorEl={ anchorEl.current }
|
|
95
|
+
anchorOrigin={ { vertical: 'bottom', horizontal: 'left' } }
|
|
96
|
+
transformOrigin={ { vertical: 'top', horizontal: 'left' } }
|
|
97
|
+
PaperProps={ {
|
|
98
|
+
sx: { my: 1 },
|
|
99
|
+
} }
|
|
100
|
+
onClose={ () => {
|
|
101
|
+
popupState.close();
|
|
102
|
+
} }
|
|
103
|
+
>
|
|
104
|
+
<InteractionDetails
|
|
105
|
+
interaction={ selectedInteraction }
|
|
106
|
+
onChange={ ( newValue: string ) => {
|
|
107
|
+
setInteractionId( newValue );
|
|
108
|
+
} }
|
|
109
|
+
/>
|
|
110
|
+
</Popover>
|
|
111
|
+
</Stack>
|
|
112
|
+
);
|
|
113
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { createContext, useCallback, useContext, useState } from 'react';
|
|
3
|
+
|
|
4
|
+
type PopupStateContextType = {
|
|
5
|
+
openByDefault: boolean;
|
|
6
|
+
triggerDefaultOpen: () => void;
|
|
7
|
+
resetDefaultOpen: () => void;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const PopupStateContext = createContext< PopupStateContextType | undefined >( undefined );
|
|
11
|
+
|
|
12
|
+
export const PopupStateProvider = ( { children }: { children: React.ReactNode } ) => {
|
|
13
|
+
const [ openByDefault, setOpenByDefault ] = useState( false );
|
|
14
|
+
|
|
15
|
+
const triggerDefaultOpen = useCallback( () => {
|
|
16
|
+
setOpenByDefault( true );
|
|
17
|
+
}, [] );
|
|
18
|
+
|
|
19
|
+
const resetDefaultOpen = useCallback( () => {
|
|
20
|
+
setOpenByDefault( false );
|
|
21
|
+
}, [] );
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<PopupStateContext.Provider value={ { openByDefault, triggerDefaultOpen, resetDefaultOpen } }>
|
|
25
|
+
{ children }
|
|
26
|
+
</PopupStateContext.Provider>
|
|
27
|
+
);
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export const usePopupStateContext = () => {
|
|
31
|
+
const context = useContext( PopupStateContext );
|
|
32
|
+
if ( ! context ) {
|
|
33
|
+
throw new Error( 'usePopupStateContext must be used within PopupStateProvider' );
|
|
34
|
+
}
|
|
35
|
+
return context;
|
|
36
|
+
};
|
package/src/index.ts
ADDED
package/src/types.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export type AnimationOption = {
|
|
2
|
+
value: string;
|
|
3
|
+
label: string;
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
export type InteractionConstants = {
|
|
7
|
+
defaultDuration: number;
|
|
8
|
+
defaultDelay: number;
|
|
9
|
+
slideDistance: number;
|
|
10
|
+
scaleStart: number;
|
|
11
|
+
easing: string;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export type InteractionsConfig = {
|
|
15
|
+
constants: InteractionConstants;
|
|
16
|
+
animationOptions: AnimationOption[];
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export type FieldProps = {
|
|
20
|
+
value: string;
|
|
21
|
+
onChange: ( value: string ) => void;
|
|
22
|
+
label?: string;
|
|
23
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { getInteractionsConfig } from './get-interactions-config';
|
|
2
|
+
|
|
3
|
+
export function formatInteractionLabel( animationId: string ): string {
|
|
4
|
+
if ( ! animationId ) {
|
|
5
|
+
return '';
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const [ trigger, effect, type, direction, duration, delay ] = animationId.split( '-' );
|
|
9
|
+
|
|
10
|
+
const baseValue = `${ trigger }-${ effect }-${ type }-${ direction || '' }`;
|
|
11
|
+
|
|
12
|
+
const animationOptions = getInteractionsConfig()?.animationOptions;
|
|
13
|
+
const option = animationOptions.find( ( opt ) => opt.value === baseValue );
|
|
14
|
+
|
|
15
|
+
let label = option?.label || animationId;
|
|
16
|
+
|
|
17
|
+
if ( duration || delay ) {
|
|
18
|
+
const constants = getInteractionsConfig()?.constants;
|
|
19
|
+
const durationValue = duration || String( constants.defaultDuration );
|
|
20
|
+
const delayValue = delay || String( constants.defaultDelay );
|
|
21
|
+
|
|
22
|
+
label += ` (${ durationValue }ms`;
|
|
23
|
+
|
|
24
|
+
if ( delayValue !== '0' ) {
|
|
25
|
+
label += `, ${ delayValue }ms delay`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
label += ')';
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return label;
|
|
32
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type InteractionsConfig } from '../types';
|
|
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
|
+
animationOptions: [],
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export function getInteractionsConfig(): InteractionsConfig {
|
|
15
|
+
return window.ElementorInteractionsConfig || DEFAULT_CONFIG;
|
|
16
|
+
}
|