@elementor/editor-interactions 4.0.0-552 → 4.0.0-573

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.
@@ -1,7 +1,7 @@
1
1
  import * as React from 'react';
2
- import { type PropsWithChildren, useMemo } from 'react';
3
- import { ControlFormLabel, PopoverContent, PopoverGridContainer } from '@elementor/editor-controls';
4
- import { Divider, Grid } from '@elementor/ui';
2
+ import { useMemo, useRef } from 'react';
3
+ import { PopoverContent } from '@elementor/editor-controls';
4
+ import { Box, Divider, Grid } from '@elementor/ui';
5
5
  import { __ } from '@wordpress/i18n';
6
6
 
7
7
  import { getInteractionsControl } from '../interactions-controls-registry';
@@ -13,11 +13,12 @@ import {
13
13
  extractSize,
14
14
  extractString,
15
15
  } from '../utils/prop-value-utils';
16
+ import { resolveDirection } from '../utils/resolve-direction';
16
17
  import { parseSizeValue } from '../utils/size-transform-utils';
17
18
  import { Direction } from './controls/direction';
18
- import { Effect } from './controls/effect';
19
19
  import { EffectType } from './controls/effect-type';
20
20
  import { TimeFrameIndicator } from './controls/time-frame-indicator';
21
+ import { Field } from './field';
21
22
 
22
23
  type InteractionDetailsProps = {
23
24
  interaction: InteractionItemValue;
@@ -39,7 +40,7 @@ const DEFAULT_VALUES = {
39
40
  offsetBottom: 85,
40
41
  };
41
42
 
42
- const TRIGGERS_WITHOUT_REPLAY = [ 'load', 'scrollOn' ];
43
+ const TRIGGERS_WITHOUT_REPLAY = [ 'load', 'scrollOn', 'hover', 'click' ];
43
44
 
44
45
  type InteractionsControlType =
45
46
  | 'trigger'
@@ -130,6 +131,7 @@ export const InteractionDetails = ( { interaction, onChange, onPlayInteraction }
130
131
  };
131
132
 
132
133
  const TriggerControl = useControlComponent( 'trigger', true );
134
+ const EffectControl = useControlComponent( 'effect' );
133
135
  const ReplayControl = useControlComponent( 'replay', controlVisibilityConfig.replay( interactionValues ) );
134
136
  const RelativeToControl = useControlComponent(
135
137
  'relativeTo',
@@ -143,16 +145,7 @@ export const InteractionDetails = ( { interaction, onChange, onPlayInteraction }
143
145
 
144
146
  const EasingControl = useControlComponent( 'easing' );
145
147
 
146
- const resolveDirection = ( hasDirection: boolean, newEffect?: string, newDirection?: string ) => {
147
- if ( newEffect === 'slide' && ! newDirection ) {
148
- return 'top';
149
- }
150
- // Why? - New direction can be undefined when the effect is not slide, so if the updates object includes direction, we take it always!
151
- if ( hasDirection ) {
152
- return newDirection;
153
- }
154
- return direction;
155
- };
148
+ const containerRef = useRef< HTMLDivElement >( null );
156
149
 
157
150
  const updateInteraction = (
158
151
  updates: Partial< {
@@ -169,7 +162,13 @@ export const InteractionDetails = ( { interaction, onChange, onPlayInteraction }
169
162
  offsetBottom: SizeStringValue;
170
163
  } >
171
164
  ): void => {
172
- const resolvedDirectionValue = resolveDirection( 'direction' in updates, updates.effect, updates.direction );
165
+ const resolvedDirectionValue = resolveDirection(
166
+ 'direction' in updates,
167
+ updates.effect,
168
+ updates.direction,
169
+ direction,
170
+ effect
171
+ );
173
172
 
174
173
  const updatedInteraction = {
175
174
  ...interaction,
@@ -199,131 +198,120 @@ export const InteractionDetails = ( { interaction, onChange, onPlayInteraction }
199
198
  };
200
199
 
201
200
  return (
202
- <PopoverContent p={ 1.5 }>
203
- <Grid container spacing={ 1.5 }>
204
- { TriggerControl && (
205
- <Field label={ __( 'Trigger', 'elementor' ) }>
206
- <TriggerControl value={ trigger } onChange={ ( v ) => updateInteraction( { trigger: v } ) } />
207
- </Field>
208
- ) }
201
+ <Box ref={ containerRef }>
202
+ <PopoverContent p={ 1.5 }>
203
+ <Grid container spacing={ 1.5 }>
204
+ { TriggerControl && (
205
+ <Field label={ __( 'Trigger', 'elementor' ) }>
206
+ <TriggerControl
207
+ value={ trigger }
208
+ onChange={ ( v ) => updateInteraction( { trigger: v } ) }
209
+ />
210
+ </Field>
211
+ ) }
212
+
213
+ { ReplayControl && (
214
+ <Field label={ __( 'Replay', 'elementor' ) }>
215
+ <ReplayControl
216
+ value={ replay }
217
+ onChange={ ( v ) => updateInteraction( { replay: v } ) }
218
+ disabled={ true }
219
+ anchorRef={ containerRef }
220
+ />
221
+ </Field>
222
+ ) }
223
+ </Grid>
209
224
 
210
- { ReplayControl && (
211
- <Field label={ __( 'Replay', 'elementor' ) }>
212
- <ReplayControl
213
- value={ replay }
214
- onChange={ ( v ) => updateInteraction( { replay: v } ) }
215
- disabled={ true }
216
- />
217
- </Field>
218
- ) }
219
- </Grid>
220
-
221
- <Divider />
222
-
223
- <Grid container spacing={ 1.5 }>
224
- <Field label={ __( 'Effect', 'elementor' ) }>
225
- <Effect value={ effect } onChange={ ( v ) => updateInteraction( { effect: v } ) } />
226
- </Field>
227
-
228
- <Field label={ __( 'Type', 'elementor' ) }>
229
- <EffectType value={ type } onChange={ ( v ) => updateInteraction( { type: v } ) } />
230
- </Field>
231
-
232
- <Field label={ __( 'Direction', 'elementor' ) }>
233
- <Direction
234
- value={ direction }
235
- onChange={ ( v ) => updateInteraction( { direction: v } ) }
236
- interactionType={ type }
237
- />
238
- </Field>
239
-
240
- { controlVisibilityConfig.duration( interactionValues ) && (
241
- <Field label={ __( 'Duration', 'elementor' ) }>
242
- <TimeFrameIndicator
243
- value={ String( duration ) }
244
- onChange={ ( v ) => updateInteraction( { duration: v as SizeStringValue } ) }
245
- defaultValue={ DEFAULT_VALUES.duration as SizeStringValue }
246
- />
225
+ <Divider />
226
+
227
+ <Grid container spacing={ 1.5 }>
228
+ { EffectControl && (
229
+ <Field label={ __( 'Effect', 'elementor' ) }>
230
+ <EffectControl value={ effect } onChange={ ( v ) => updateInteraction( { effect: v } ) } />
231
+ </Field>
232
+ ) }
233
+
234
+ <Field label={ __( 'Type', 'elementor' ) }>
235
+ <EffectType value={ type } onChange={ ( v ) => updateInteraction( { type: v } ) } />
247
236
  </Field>
248
- ) }
249
237
 
250
- { controlVisibilityConfig.delay( interactionValues ) && (
251
- <Field label={ __( 'Delay', 'elementor' ) }>
252
- <TimeFrameIndicator
253
- value={ String( delay ) }
254
- onChange={ ( v ) => updateInteraction( { delay: v as SizeStringValue } ) }
255
- defaultValue={ DEFAULT_VALUES.delay as SizeStringValue }
238
+ <Field label={ __( 'Direction', 'elementor' ) }>
239
+ <Direction
240
+ value={ direction }
241
+ onChange={ ( v ) => updateInteraction( { direction: v } ) }
242
+ interactionType={ type }
256
243
  />
257
244
  </Field>
258
- ) }
259
- </Grid>
260
245
 
261
- { controlVisibilityConfig.relativeTo( interactionValues ) && RelativeToControl && (
262
- <>
263
- <Divider />
264
- <Grid container spacing={ 1.5 }>
265
- <Field label={ __( 'Relative To', 'elementor' ) }>
266
- <RelativeToControl
267
- value={ relativeTo }
268
- onChange={ ( v ) => updateInteraction( { relativeTo: v } ) }
246
+ { controlVisibilityConfig.duration( interactionValues ) && (
247
+ <Field label={ __( 'Duration', 'elementor' ) }>
248
+ <TimeFrameIndicator
249
+ value={ String( duration ) }
250
+ onChange={ ( v ) => updateInteraction( { duration: v as SizeStringValue } ) }
251
+ defaultValue={ DEFAULT_VALUES.duration as SizeStringValue }
269
252
  />
270
253
  </Field>
271
- { OffsetTopControl && (
272
- <Field label={ __( 'Offset Top', 'elementor' ) }>
273
- <OffsetTopControl
274
- value={ String( parseSizeValue( offsetTop, [ '%' ] ).size ) }
275
- onChange={ ( v: string ) =>
276
- updateInteraction( { offsetTop: v as SizeStringValue } )
277
- }
278
- />
279
- </Field>
280
- ) }
281
- { OffsetBottomControl && (
282
- <Field label={ __( 'Offset Bottom', 'elementor' ) }>
283
- <OffsetBottomControl
284
- value={ String( parseSizeValue( offsetBottom, [ '%' ] ).size ) }
285
- onChange={ ( v: string ) =>
286
- updateInteraction( { offsetBottom: v as SizeStringValue } )
287
- }
254
+ ) }
255
+
256
+ { controlVisibilityConfig.delay( interactionValues ) && (
257
+ <Field label={ __( 'Delay', 'elementor' ) }>
258
+ <TimeFrameIndicator
259
+ value={ String( delay ) }
260
+ onChange={ ( v ) => updateInteraction( { delay: v as SizeStringValue } ) }
261
+ defaultValue={ DEFAULT_VALUES.delay as SizeStringValue }
262
+ />
263
+ </Field>
264
+ ) }
265
+ </Grid>
266
+
267
+ { controlVisibilityConfig.relativeTo( interactionValues ) && RelativeToControl && (
268
+ <>
269
+ <Divider />
270
+ <Grid container spacing={ 1.5 }>
271
+ <Field label={ __( 'Relative To', 'elementor' ) }>
272
+ <RelativeToControl
273
+ value={ relativeTo }
274
+ onChange={ ( v ) => updateInteraction( { relativeTo: v } ) }
288
275
  />
289
276
  </Field>
290
- ) }
291
- </Grid>
292
- <Divider />
293
- </>
294
- ) }
277
+ { OffsetTopControl && (
278
+ <Field label={ __( 'Offset Top', 'elementor' ) }>
279
+ <OffsetTopControl
280
+ value={ String( parseSizeValue( offsetTop, [ '%' ] ).size ) }
281
+ onChange={ ( v: string ) =>
282
+ updateInteraction( { offsetTop: v as SizeStringValue } )
283
+ }
284
+ />
285
+ </Field>
286
+ ) }
287
+ { OffsetBottomControl && (
288
+ <Field label={ __( 'Offset Bottom', 'elementor' ) }>
289
+ <OffsetBottomControl
290
+ value={ String( parseSizeValue( offsetBottom, [ '%' ] ).size ) }
291
+ onChange={ ( v: string ) =>
292
+ updateInteraction( { offsetBottom: v as SizeStringValue } )
293
+ }
294
+ />
295
+ </Field>
296
+ ) }
297
+ </Grid>
298
+ <Divider />
299
+ </>
300
+ ) }
295
301
 
296
- { EasingControl && (
297
- <Grid container spacing={ 1.5 }>
298
- <Field label={ __( 'Easing', 'elementor' ) }>
299
- <EasingControl
300
- value={ easing }
301
- onChange={ ( v ) => {
302
- updateInteraction( { easing: v } );
303
- } }
304
- />
305
- </Field>
306
- </Grid>
307
- ) }
308
- </PopoverContent>
302
+ { EasingControl && (
303
+ <Grid container spacing={ 1.5 }>
304
+ <Field label={ __( 'Easing', 'elementor' ) }>
305
+ <EasingControl
306
+ value={ easing }
307
+ onChange={ ( v ) => {
308
+ updateInteraction( { easing: v } );
309
+ } }
310
+ />
311
+ </Field>
312
+ </Grid>
313
+ ) }
314
+ </PopoverContent>
315
+ </Box>
309
316
  );
310
317
  };
311
-
312
- type FieldProps = {
313
- label: string;
314
- } & PropsWithChildren;
315
-
316
- function Field( { label, children }: FieldProps ) {
317
- return (
318
- <Grid item xs={ 12 }>
319
- <PopoverGridContainer>
320
- <Grid item xs={ 6 }>
321
- <ControlFormLabel>{ label }</ControlFormLabel>
322
- </Grid>
323
- <Grid item xs={ 6 }>
324
- { children }
325
- </Grid>
326
- </PopoverGridContainer>
327
- </Grid>
328
- );
329
- }
package/src/index.ts CHANGED
@@ -12,3 +12,4 @@ export {
12
12
  export { ELEMENTS_INTERACTIONS_PROVIDER_KEY_PREFIX } from './providers/document-elements-interactions-provider';
13
13
  export { init } from './init';
14
14
  export { registerInteractionsControl } from './interactions-controls-registry';
15
+ export type { InteractionItemPropValue, FieldProps } from './types';
package/src/init.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { Easing } from './components/controls/easing';
2
+ import { Effect } from './components/controls/effect';
2
3
  import { Replay } from './components/controls/replay';
3
4
  import { Trigger } from './components/controls/trigger';
4
5
  import { initCleanInteractionIdsOnDuplicate } from './hooks/on-duplicate';
@@ -9,7 +10,9 @@ import { documentElementsInteractionsProvider } from './providers/document-eleme
9
10
  export function init() {
10
11
  try {
11
12
  interactionsRepository.register( documentElementsInteractionsProvider );
13
+
12
14
  initCleanInteractionIdsOnDuplicate();
15
+
13
16
  registerInteractionsControl( {
14
17
  type: 'trigger',
15
18
  component: Trigger,
@@ -21,11 +24,17 @@ export function init() {
21
24
  component: Easing,
22
25
  options: [ 'easeIn' ],
23
26
  } );
27
+
24
28
  registerInteractionsControl( {
25
29
  type: 'replay',
26
30
  component: Replay,
27
31
  options: [ 'true', 'false' ],
28
32
  } );
33
+ registerInteractionsControl( {
34
+ type: 'effect',
35
+ component: Effect,
36
+ options: [ 'fade', 'slide', 'scale' ],
37
+ } );
29
38
  } catch ( error ) {
30
39
  throw error;
31
40
  }
package/src/types.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { type RefObject } from 'react';
1
2
  import { type Unit } from '@elementor/editor-controls';
2
3
  import type {
3
4
  AnimationPresetPropValue,
@@ -53,6 +54,7 @@ export type ReplayFieldProps = {
53
54
  value: boolean;
54
55
  onChange: ( value: boolean ) => void;
55
56
  disabled?: boolean;
57
+ anchorRef?: RefObject< HTMLElement | null >;
56
58
  };
57
59
  export type DirectionFieldProps = FieldProps & {
58
60
  interactionType: string;
@@ -0,0 +1,39 @@
1
+ import * as React from 'react';
2
+ import { type MouseEvent, type RefObject, useState } from 'react';
3
+ import { PromotionChip, PromotionPopover, useCanvasClickHandler } from '@elementor/editor-ui';
4
+ import { Box } from '@elementor/ui';
5
+ import { __ } from '@wordpress/i18n';
6
+
7
+ export type InteractionsPromotionChipProps = {
8
+ content: string;
9
+ upgradeUrl: string;
10
+ anchorRef?: RefObject< HTMLElement | null >;
11
+ };
12
+
13
+ export function InteractionsPromotionChip( { content, upgradeUrl, anchorRef }: InteractionsPromotionChipProps ) {
14
+ const [ isOpen, setIsOpen ] = useState( false );
15
+
16
+ useCanvasClickHandler( isOpen, () => setIsOpen( false ) );
17
+
18
+ const handleToggle = ( e: MouseEvent ) => {
19
+ e.stopPropagation();
20
+ setIsOpen( ( prev ) => ! prev );
21
+ };
22
+
23
+ return (
24
+ <PromotionPopover
25
+ open={ isOpen }
26
+ title={ __( 'Interactions', 'elementor' ) }
27
+ content={ content }
28
+ ctaText={ __( 'Upgrade now', 'elementor' ) }
29
+ ctaUrl={ upgradeUrl }
30
+ anchorRef={ anchorRef }
31
+ placement={ anchorRef ? 'right-start' : undefined }
32
+ onClose={ handleToggle }
33
+ >
34
+ <Box onClick={ handleToggle } sx={ { cursor: 'pointer', display: 'inline-flex', mr: 1 } }>
35
+ <PromotionChip />
36
+ </Box>
37
+ </PromotionPopover>
38
+ );
39
+ }
@@ -0,0 +1,20 @@
1
+ export const resolveDirection = (
2
+ hasDirection: boolean,
3
+ newEffect?: string,
4
+ newDirection?: string,
5
+ currentDirection?: string,
6
+ currentEffect?: string
7
+ ) => {
8
+ if ( newEffect === 'slide' && ! newDirection ) {
9
+ return 'top';
10
+ }
11
+
12
+ if ( currentEffect === 'slide' && hasDirection ) {
13
+ return newDirection ?? 'top';
14
+ }
15
+ // Why? - New direction can be undefined when the effect is not slide, so if the updates object includes direction, we take it always!
16
+ if ( hasDirection ) {
17
+ return newDirection;
18
+ }
19
+ return currentDirection;
20
+ };