@elementor/editor-controls 3.33.0-99 → 3.35.0-325

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 (94) hide show
  1. package/dist/index.d.mts +276 -85
  2. package/dist/index.d.ts +276 -85
  3. package/dist/index.js +2491 -1783
  4. package/dist/index.js.map +1 -1
  5. package/dist/index.mjs +2304 -1592
  6. package/dist/index.mjs.map +1 -1
  7. package/package.json +31 -17
  8. package/src/bound-prop-context/prop-context.tsx +7 -1
  9. package/src/bound-prop-context/use-bound-prop.ts +19 -5
  10. package/src/components/autocomplete.tsx +34 -3
  11. package/src/components/conditional-control-infotip.tsx +64 -0
  12. package/src/components/{unstable-repeater → control-repeater}/actions/disable-item-action.tsx +2 -2
  13. package/src/components/{unstable-repeater → control-repeater}/actions/duplicate-item-action.tsx +10 -4
  14. package/src/components/{unstable-repeater → control-repeater}/actions/remove-item-action.tsx +2 -2
  15. package/src/components/control-repeater/context/item-context.tsx +8 -0
  16. package/src/components/{unstable-repeater → control-repeater}/context/repeater-context.tsx +24 -15
  17. package/src/components/control-repeater/control-repeater.tsx +29 -0
  18. package/src/components/{unstable-repeater → control-repeater}/index.ts +1 -2
  19. package/src/components/{unstable-repeater → control-repeater}/items/edit-item-popover.tsx +6 -20
  20. package/src/components/control-repeater/items/item.tsx +75 -0
  21. package/src/components/{unstable-repeater → control-repeater}/items/items-container.tsx +8 -13
  22. package/src/components/{unstable-repeater → control-repeater}/locations.ts +0 -4
  23. package/src/components/{unstable-repeater → control-repeater}/types.ts +1 -2
  24. package/src/components/control-toggle-button-group.tsx +79 -69
  25. package/src/components/enable-unfiltered-modal.tsx +1 -26
  26. package/src/components/icon-buttons/clear-icon-button.tsx +23 -0
  27. package/src/components/inline-editor-toolbar.tsx +137 -0
  28. package/src/components/inline-editor.tsx +111 -0
  29. package/src/components/item-selector.tsx +10 -4
  30. package/src/components/{unstable-repeater/header/header.tsx → repeater/repeater-header.tsx} +4 -12
  31. package/src/components/repeater/repeater-popover.tsx +19 -0
  32. package/src/components/repeater/repeater-tag.tsx +16 -0
  33. package/src/components/repeater/repeater.tsx +405 -0
  34. package/src/components/{sortable.tsx → repeater/sortable.tsx} +1 -1
  35. package/src/components/size-control/size-input.tsx +20 -14
  36. package/src/components/size-control/text-field-inner-selection.tsx +15 -2
  37. package/src/control-adornments/control-adornments-context.tsx +5 -4
  38. package/src/control-replacements.tsx +12 -47
  39. package/src/controls/background-control/background-control.tsx +43 -12
  40. package/src/controls/background-control/background-gradient-color-control.tsx +5 -8
  41. package/src/controls/background-control/background-overlay/background-image-overlay/background-image-overlay-position.tsx +18 -13
  42. package/src/controls/background-control/background-overlay/background-overlay-repeater-control.tsx +25 -16
  43. package/src/controls/box-shadow-repeater-control.tsx +38 -21
  44. package/src/controls/color-control.tsx +3 -1
  45. package/src/controls/date-time-control.tsx +108 -0
  46. package/src/controls/filter-control/drop-shadow/drop-shadow-item-content.tsx +1 -0
  47. package/src/controls/filter-control/drop-shadow/drop-shadow-item-label.tsx +10 -6
  48. package/src/controls/filter-control/filter-content.tsx +1 -1
  49. package/src/controls/filter-control/filter-repeater-control.tsx +24 -21
  50. package/src/controls/filter-control/single-size/single-size-item-content.tsx +1 -1
  51. package/src/controls/filter-control/single-size/single-size-item-label.tsx +2 -1
  52. package/src/controls/font-family-control/font-family-control.tsx +66 -55
  53. package/src/controls/html-tag-control.tsx +90 -0
  54. package/src/controls/image-media-control.tsx +2 -2
  55. package/src/controls/inline-editing-control.tsx +18 -0
  56. package/src/controls/key-value-control.tsx +8 -2
  57. package/src/controls/link-control.tsx +23 -123
  58. package/src/controls/query-control.tsx +168 -0
  59. package/src/controls/repeatable-control.tsx +62 -27
  60. package/src/controls/select-control-wrapper.tsx +57 -0
  61. package/src/controls/select-control.tsx +9 -5
  62. package/src/controls/selection-size-control.tsx +13 -2
  63. package/src/controls/size-control.tsx +43 -25
  64. package/src/controls/svg-media-control.tsx +33 -10
  65. package/src/controls/text-area-control.tsx +5 -1
  66. package/src/controls/text-control.tsx +5 -0
  67. package/src/controls/toggle-control.tsx +11 -2
  68. package/src/controls/transform-control/functions/axis-row.tsx +1 -0
  69. package/src/controls/transform-control/transform-icon.tsx +2 -2
  70. package/src/controls/transform-control/transform-label.tsx +15 -32
  71. package/src/controls/transform-control/transform-repeater-control.tsx +42 -36
  72. package/src/controls/transform-control/{transform-base-control.tsx → transform-settings-control.tsx} +2 -2
  73. package/src/controls/transform-control/use-transform-tabs-history.tsx +1 -1
  74. package/src/controls/transition-control/data.ts +16 -1
  75. package/src/controls/transition-control/trainsition-events.ts +2 -2
  76. package/src/controls/transition-control/transition-repeater-control.tsx +137 -13
  77. package/src/controls/transition-control/transition-selector.tsx +37 -14
  78. package/src/controls/url-control.tsx +21 -16
  79. package/src/create-control.tsx +3 -2
  80. package/src/hooks/use-filtered-items-list.ts +3 -2
  81. package/src/hooks/use-repeatable-control-context.ts +3 -0
  82. package/src/hooks/use-sync-external-state.tsx +0 -1
  83. package/src/index.ts +21 -5
  84. package/src/utils/convert-toggle-options-to-atomic.tsx +33 -0
  85. package/src/utils/escape-html-attr.ts +11 -0
  86. package/src/components/css-code-editor/css-editor.styles.ts +0 -52
  87. package/src/components/css-code-editor/css-editor.tsx +0 -142
  88. package/src/components/css-code-editor/css-validation.ts +0 -75
  89. package/src/components/css-code-editor/resize-handle.tsx +0 -55
  90. package/src/components/css-code-editor/visual-content-change-protection.ts +0 -69
  91. package/src/components/repeater.tsx +0 -343
  92. package/src/components/unstable-repeater/items/item.tsx +0 -77
  93. package/src/components/unstable-repeater/unstable-repeater.tsx +0 -26
  94. /package/src/components/{unstable-repeater → control-repeater}/actions/tooltip-add-item-action.tsx +0 -0
@@ -3,53 +3,36 @@ import type { TransformFunctionsItemPropValue } from '@elementor/editor-props';
3
3
  import { Box } from '@elementor/ui';
4
4
  import { __ } from '@wordpress/i18n';
5
5
 
6
+ import { CUSTOM_SIZE_LABEL } from '../size-control';
6
7
  import { defaultValues, TransformFunctionKeys } from './initial-values';
7
8
 
8
- const transformMoveValue = ( value: TransformFunctionsItemPropValue[ 'value' ] ) =>
9
- Object.values( value )
9
+ const formatLabel = ( value: TransformFunctionsItemPropValue[ 'value' ], functionType: keyof typeof defaultValues ) => {
10
+ return Object.values( value )
10
11
  .map( ( axis ) => {
11
- const size = axis?.value?.size ?? defaultValues.move.size;
12
- const unit = axis?.value?.unit ?? defaultValues.move.unit;
12
+ if ( functionType === 'scale' ) {
13
+ return axis?.value || defaultValues[ functionType ];
14
+ }
13
15
 
14
- return `${ size }${ unit }`;
15
- } )
16
- .join( ', ' );
17
-
18
- const transformScaleValue = ( value: TransformFunctionsItemPropValue[ 'value' ] ) =>
19
- Object.values( value )
20
- .map( ( axis ) => axis?.value || defaultValues.scale )
21
- .join( ', ' );
22
-
23
- const transformRotateValue = ( value: TransformFunctionsItemPropValue[ 'value' ] ) =>
24
- Object.values( value )
25
- .map( ( axis ) => {
26
- const size = axis?.value?.size ?? defaultValues.rotate.size;
27
- const unit = axis?.value?.unit ?? defaultValues.rotate.unit;
16
+ const defaults = defaultValues[ functionType ];
17
+ const size = axis?.value?.size ?? defaults.size;
18
+ const unit = axis?.value?.unit ?? defaults.unit;
28
19
 
29
- return `${ size }${ unit }`;
30
- } )
31
- .join( ', ' );
32
- const transformSkewValue = ( value: TransformFunctionsItemPropValue[ 'value' ] ) =>
33
- Object.values( value )
34
- .map( ( axis ) => {
35
- const size = axis?.value?.size ?? defaultValues.skew.size;
36
- const unit = axis?.value?.unit ?? defaultValues.skew.unit;
37
-
38
- return `${ size }${ unit }`;
20
+ return unit === 'custom' ? size || CUSTOM_SIZE_LABEL : `${ size }${ unit }`;
39
21
  } )
40
22
  .join( ', ' );
23
+ };
41
24
 
42
25
  export const TransformLabel = ( props: { value: TransformFunctionsItemPropValue } ) => {
43
26
  const { $$type, value } = props.value;
44
27
  switch ( $$type ) {
45
28
  case TransformFunctionKeys.move:
46
- return <Label label={ __( 'Move', 'elementor' ) } value={ transformMoveValue( value ) } />;
29
+ return <Label label={ __( 'Move', 'elementor' ) } value={ formatLabel( value, 'move' ) } />;
47
30
  case TransformFunctionKeys.scale:
48
- return <Label label={ __( 'Scale', 'elementor' ) } value={ transformScaleValue( value ) } />;
31
+ return <Label label={ __( 'Scale', 'elementor' ) } value={ formatLabel( value, 'scale' ) } />;
49
32
  case TransformFunctionKeys.rotate:
50
- return <Label label={ __( 'Rotate', 'elementor' ) } value={ transformRotateValue( value ) } />;
33
+ return <Label label={ __( 'Rotate', 'elementor' ) } value={ formatLabel( value, 'rotate' ) } />;
51
34
  case TransformFunctionKeys.skew:
52
- return <Label label={ __( 'Skew', 'elementor' ) } value={ transformSkewValue( value ) } />;
35
+ return <Label label={ __( 'Skew', 'elementor' ) } value={ formatLabel( value, 'skew' ) } />;
53
36
  default:
54
37
  return '';
55
38
  }
@@ -2,28 +2,22 @@ import * as React from 'react';
2
2
  import { useRef } from 'react';
3
3
  import { type PropType, transformFunctionsPropTypeUtil, transformPropTypeUtil } from '@elementor/editor-props';
4
4
  import { AdjustmentsIcon, InfoCircleFilledIcon } from '@elementor/icons';
5
- import { bindTrigger, Box, IconButton, type PopupState, Typography, usePopupState } from '@elementor/ui';
5
+ import { bindTrigger, Box, IconButton, type PopupState, Tooltip, Typography, usePopupState } from '@elementor/ui';
6
6
  import { __ } from '@wordpress/i18n';
7
7
 
8
8
  import { PropKeyProvider, PropProvider, useBoundProp } from '../../bound-prop-context';
9
- import {
10
- Header,
11
- Item,
12
- ItemsContainer,
13
- TooltipAddItemAction,
14
- UnstableRepeater,
15
- } from '../../components/unstable-repeater';
16
- import { DisableItemAction } from '../../components/unstable-repeater/actions/disable-item-action';
17
- import { RemoveItemAction } from '../../components/unstable-repeater/actions/remove-item-action';
18
- import { EditItemPopover } from '../../components/unstable-repeater/items/edit-item-popover';
19
- import { injectIntoRepeaterHeaderActions } from '../../components/unstable-repeater/locations';
9
+ import { ControlRepeater, Item, ItemsContainer, TooltipAddItemAction } from '../../components/control-repeater';
10
+ import { DisableItemAction } from '../../components/control-repeater/actions/disable-item-action';
11
+ import { RemoveItemAction } from '../../components/control-repeater/actions/remove-item-action';
12
+ import { EditItemPopover } from '../../components/control-repeater/items/edit-item-popover';
13
+ import { RepeaterHeader } from '../../components/repeater/repeater-header';
20
14
  import { ControlAdornments } from '../../control-adornments/control-adornments';
21
15
  import { createControl } from '../../create-control';
22
16
  import { initialRotateValue, initialScaleValue, initialSkewValue, initialTransformValue } from './initial-values';
23
- import { TransformBaseControl } from './transform-base-control';
24
17
  import { TransformContent } from './transform-content';
25
18
  import { TransformIcon } from './transform-icon';
26
19
  import { TransformLabel } from './transform-label';
20
+ import { TransformSettingsControl } from './transform-settings-control';
27
21
 
28
22
  const SIZE = 'tiny';
29
23
 
@@ -32,19 +26,11 @@ export const TransformRepeaterControl = createControl( () => {
32
26
  const headerRef = useRef< HTMLDivElement >( null );
33
27
  const popupState = usePopupState( { variant: 'popover' } );
34
28
 
35
- const repeaterBindKey = 'transform-functions';
36
-
37
- injectIntoRepeaterHeaderActions( {
38
- id: 'transform-base-control',
39
- component: () => <TransformBasePopoverTrigger popupState={ popupState } repeaterBindKey={ repeaterBindKey } />,
40
- options: { overwrite: true },
41
- } );
42
-
43
29
  return (
44
30
  <PropProvider { ...context }>
45
- <TransformBaseControl popupState={ popupState } anchorRef={ headerRef } />
46
- <PropKeyProvider bind={ repeaterBindKey }>
47
- <Repeater headerRef={ headerRef } propType={ context.propType } />
31
+ <TransformSettingsControl popupState={ popupState } anchorRef={ headerRef } />
32
+ <PropKeyProvider bind={ 'transform-functions' }>
33
+ <Repeater headerRef={ headerRef } propType={ context.propType } popupState={ popupState } />
48
34
  </PropKeyProvider>
49
35
  </PropProvider>
50
36
  );
@@ -63,10 +49,18 @@ const ToolTip = (
63
49
  </Box>
64
50
  );
65
51
 
66
- const Repeater = ( { headerRef, propType }: { headerRef: React.RefObject< HTMLDivElement >; propType: PropType } ) => {
52
+ const Repeater = ( {
53
+ headerRef,
54
+ propType,
55
+ popupState,
56
+ }: {
57
+ headerRef: React.RefObject< HTMLDivElement >;
58
+ propType: PropType;
59
+ popupState: PopupState;
60
+ } ) => {
67
61
  const transformFunctionsContext = useBoundProp( transformFunctionsPropTypeUtil );
68
62
  const availableValues = [ initialTransformValue, initialScaleValue, initialRotateValue, initialSkewValue ];
69
- const { value: transformValues } = transformFunctionsContext;
63
+ const { value: transformValues, bind } = transformFunctionsContext;
70
64
 
71
65
  const getInitialValue = () => {
72
66
  return availableValues.find( ( value ) => ! transformValues?.some( ( item ) => item.$$type === value.$$type ) );
@@ -76,30 +70,39 @@ const Repeater = ( { headerRef, propType }: { headerRef: React.RefObject< HTMLDi
76
70
 
77
71
  return (
78
72
  <PropProvider { ...transformFunctionsContext }>
79
- <UnstableRepeater
73
+ <ControlRepeater
80
74
  initial={ getInitialValue() ?? initialTransformValue }
81
75
  propTypeUtil={ transformFunctionsPropTypeUtil }
82
76
  >
83
- <Header
77
+ <RepeaterHeader
84
78
  label={ __( 'Transform', 'elementor' ) }
85
79
  adornment={ () => <ControlAdornments customContext={ { path: [ 'transform' ], propType } } /> }
86
80
  ref={ headerRef }
87
81
  >
82
+ <TransformBasePopoverTrigger popupState={ popupState } repeaterBindKey={ bind } />
88
83
  <TooltipAddItemAction
89
84
  disabled={ shouldDisableAddItem }
90
85
  tooltipContent={ ToolTip }
91
86
  enableTooltip={ shouldDisableAddItem }
92
87
  ariaLabel={ 'transform' }
93
88
  />
94
- </Header>
95
- <ItemsContainer itemTemplate={ <Item Icon={ TransformIcon } Label={ TransformLabel } /> }>
96
- <DisableItemAction />
97
- <RemoveItemAction />
89
+ </RepeaterHeader>
90
+ <ItemsContainer>
91
+ <Item
92
+ Icon={ TransformIcon }
93
+ Label={ TransformLabel }
94
+ actions={
95
+ <>
96
+ <DisableItemAction />
97
+ <RemoveItemAction />
98
+ </>
99
+ }
100
+ />
98
101
  </ItemsContainer>
99
102
  <EditItemPopover>
100
103
  <TransformContent />
101
104
  </EditItemPopover>
102
- </UnstableRepeater>
105
+ </ControlRepeater>
103
106
  </PropProvider>
104
107
  );
105
108
  };
@@ -112,10 +115,13 @@ const TransformBasePopoverTrigger = ( {
112
115
  repeaterBindKey: string;
113
116
  } ) => {
114
117
  const { bind } = useBoundProp();
118
+ const titleLabel = __( 'Transform settings', 'elementor' );
115
119
 
116
120
  return bind !== repeaterBindKey ? null : (
117
- <IconButton size={ SIZE } aria-label={ __( 'Base Transform', 'elementor' ) } { ...bindTrigger( popupState ) }>
118
- <AdjustmentsIcon fontSize={ SIZE } />
119
- </IconButton>
121
+ <Tooltip title={ titleLabel } placement="top">
122
+ <IconButton size={ SIZE } aria-label={ titleLabel } { ...bindTrigger( popupState ) }>
123
+ <AdjustmentsIcon fontSize={ SIZE } />
124
+ </IconButton>
125
+ </Tooltip>
120
126
  );
121
127
  };
@@ -11,7 +11,7 @@ import { TransformOriginControl } from './transform-base-controls/transform-orig
11
11
 
12
12
  const SIZE = 'tiny';
13
13
 
14
- export const TransformBaseControl = ( {
14
+ export const TransformSettingsControl = ( {
15
15
  popupState,
16
16
  anchorRef,
17
17
  }: {
@@ -38,7 +38,7 @@ export const TransformBaseControl = ( {
38
38
  { ...popupProps }
39
39
  >
40
40
  <PopoverHeader
41
- title={ __( 'Base Transform', 'elementor' ) }
41
+ title={ __( 'Transform settings', 'elementor' ) }
42
42
  onClose={ popupState.close }
43
43
  icon={ <AdjustmentsIcon fontSize={ SIZE } /> }
44
44
  />
@@ -13,7 +13,7 @@ import {
13
13
  import { useTabs } from '@elementor/ui';
14
14
 
15
15
  import { useBoundProp } from '../../bound-prop-context';
16
- import { useRepeaterContext } from '../../components/unstable-repeater/context/repeater-context';
16
+ import { useRepeaterContext } from '../../components/control-repeater/context/repeater-context';
17
17
  import { type TransformFunction, TransformFunctionKeys } from './initial-values';
18
18
 
19
19
  type InitialTransformValues = {
@@ -1,9 +1,11 @@
1
+ import { type KeyValuePropValue, type SizePropValue } from '@elementor/editor-props';
1
2
  import { __ } from '@wordpress/i18n';
2
3
 
3
4
  export type TransitionProperty = {
4
5
  label: string;
5
6
  value: string;
6
7
  unavailable?: boolean;
8
+ isDisabled?: boolean;
7
9
  };
8
10
 
9
11
  export type TransitionCategory = {
@@ -12,7 +14,20 @@ export type TransitionCategory = {
12
14
  properties: TransitionProperty[];
13
15
  };
14
16
 
15
- export const initialTransitionValue = {
17
+ export type TransitionValue = {
18
+ selection: KeyValuePropValue;
19
+ size: SizePropValue;
20
+ };
21
+
22
+ export type TransitionItem = {
23
+ $$type: 'selection-size';
24
+ value: {
25
+ $$type: 'key-value';
26
+ value: TransitionValue;
27
+ };
28
+ };
29
+
30
+ export const initialTransitionValue: TransitionValue = {
16
31
  selection: {
17
32
  $$type: 'key-value',
18
33
  value: {
@@ -1,5 +1,5 @@
1
1
  import { getSelectedElements } from '@elementor/editor-elements';
2
- import { sendMixpanelEvent } from '@elementor/utils';
2
+ import { trackEvent } from '@elementor/mixpanel';
3
3
 
4
4
  import { eventBus } from '../../services/event-bus';
5
5
  import { type initialTransitionValue } from './data';
@@ -19,7 +19,7 @@ export function subscribeToTransitionEvent() {
19
19
  const value = payload?.itemValue?.selection?.value?.value?.value;
20
20
  const selectedElements = getSelectedElements();
21
21
  const widgetType = selectedElements[ 0 ]?.type ?? null;
22
- sendMixpanelEvent( {
22
+ trackEvent( {
23
23
  transition_type: value ?? 'unknown',
24
24
  ...transitionRepeaterMixpanelEvent,
25
25
  widget_type: widgetType,
@@ -1,17 +1,30 @@
1
1
  import * as React from 'react';
2
- import { useEffect, useState } from 'react';
3
- import { selectionSizePropTypeUtil } from '@elementor/editor-props';
2
+ import { useEffect, useMemo, useState } from 'react';
3
+ import {
4
+ createArrayPropUtils,
5
+ type KeyValuePropValue,
6
+ selectionSizePropTypeUtil,
7
+ type SelectionSizePropValue,
8
+ } from '@elementor/editor-props';
4
9
  import { type StyleDefinitionState } from '@elementor/editor-styles';
5
10
  import { InfoCircleFilledIcon } from '@elementor/icons';
6
11
  import { Alert, AlertTitle, Box, Typography } from '@elementor/ui';
7
12
  import { __ } from '@wordpress/i18n';
8
13
 
14
+ import { useBoundProp } from '../../bound-prop-context';
15
+ import { type Item, type RepeatablePropValue } from '../../components/control-repeater/types';
9
16
  import { createControl } from '../../create-control';
10
17
  import { RepeatableControl } from '../repeatable-control';
11
18
  import { SelectionSizeControl } from '../selection-size-control';
12
- import { initialTransitionValue, transitionProperties } from './data';
19
+ import {
20
+ initialTransitionValue,
21
+ type TransitionItem,
22
+ transitionProperties,
23
+ type TransitionProperty,
24
+ type TransitionValue,
25
+ } from './data';
13
26
  import { subscribeToTransitionEvent } from './trainsition-events';
14
- import { TransitionSelector } from './transition-selector';
27
+ import { getTransitionPropertyByValue, TransitionSelector } from './transition-selector';
15
28
 
16
29
  const DURATION_CONFIG = {
17
30
  variant: 'time',
@@ -19,9 +32,34 @@ const DURATION_CONFIG = {
19
32
  defaultUnit: 'ms',
20
33
  };
21
34
 
35
+ const childArrayPropTypeUtil = createArrayPropUtils(
36
+ selectionSizePropTypeUtil.key,
37
+ selectionSizePropTypeUtil.schema,
38
+ 'transition'
39
+ );
40
+
41
+ subscribeToTransitionEvent();
42
+
43
+ const areAllPropertiesUsed = ( value: SelectionSizePropValue[] = [] ) => {
44
+ return value?.length
45
+ ? transitionProperties.every( ( category ) => {
46
+ return category.properties.every( ( property ) => {
47
+ return (
48
+ property.isDisabled ||
49
+ !! value?.find( ( item ) => {
50
+ return (
51
+ ( item.value?.selection?.value as KeyValuePropValue )?.value?.value === property.value
52
+ );
53
+ } )
54
+ );
55
+ } );
56
+ } )
57
+ : false;
58
+ };
59
+
22
60
  // this config needs to be loaded at runtime/render since it's the transitionProperties object will be mutated by the pro plugin.
23
61
  // See: https://elementor.atlassian.net/browse/ED-20285
24
- const getSelectionSizeProps = ( recentlyUsedList: string[] ) => {
62
+ const getSelectionSizeProps = ( recentlyUsedList: string[], disabledItems?: string[] ) => {
25
63
  return {
26
64
  selectionLabel: __( 'Type', 'elementor' ),
27
65
  sizeLabel: __( 'Duration', 'elementor' ),
@@ -29,8 +67,10 @@ const getSelectionSizeProps = ( recentlyUsedList: string[] ) => {
29
67
  component: TransitionSelector,
30
68
  props: {
31
69
  recentlyUsedList,
70
+ disabledItems,
32
71
  },
33
72
  },
73
+ isRepeaterControl: true,
34
74
  sizeConfigMap: {
35
75
  ...transitionProperties.reduce(
36
76
  ( acc, category ) => {
@@ -45,13 +85,69 @@ const getSelectionSizeProps = ( recentlyUsedList: string[] ) => {
45
85
  };
46
86
  };
47
87
 
48
- function getChildControlConfig( recentlyUsedList: string[] ) {
88
+ const isItemDisabled = ( item: TransitionItem[ 'value' ] ) => {
89
+ const property = getTransitionPropertyByValue( item.value.selection.value?.value );
90
+
91
+ return ! property ? false : !! property.isDisabled;
92
+ };
93
+
94
+ const getChildControlConfig = ( recentlyUsedList: string[], disabledItems?: string[] ) => {
49
95
  return {
50
96
  propTypeUtil: selectionSizePropTypeUtil,
51
97
  component: SelectionSizeControl as unknown as React.ComponentType< Record< string, unknown > >,
52
- props: getSelectionSizeProps( recentlyUsedList ),
98
+ props: getSelectionSizeProps( recentlyUsedList, disabledItems ),
99
+ isItemDisabled: isItemDisabled as ( item: Item< RepeatablePropValue > ) => boolean,
53
100
  };
54
- }
101
+ };
102
+
103
+ const isPropertyUsed = ( value: SelectionSizePropValue[], property: TransitionProperty ) => {
104
+ return ( value ?? [] ).some( ( item ) => {
105
+ return ( item?.value?.selection?.value as KeyValuePropValue )?.value?.value === property.value;
106
+ } );
107
+ };
108
+
109
+ const getDisabledItemLabels = ( values: SelectionSizePropValue[] = [] ) => {
110
+ const disabledLabels: string[] = ( values || [] ).map(
111
+ ( item ) => ( item.value?.selection as KeyValuePropValue )?.value?.key?.value
112
+ );
113
+
114
+ transitionProperties.forEach( ( category ) => {
115
+ const disabledProperties = category.properties
116
+ .filter( ( property ) => property.isDisabled && ! disabledLabels.includes( property.label ) )
117
+ .map( ( property ) => property.label );
118
+
119
+ disabledLabels.push( ...disabledProperties );
120
+ } );
121
+
122
+ return disabledLabels;
123
+ };
124
+
125
+ const getInitialValue = ( values: SelectionSizePropValue[] = [] ): TransitionValue => {
126
+ if ( ! values?.length ) {
127
+ return initialTransitionValue;
128
+ }
129
+
130
+ for ( const category of transitionProperties ) {
131
+ for ( const property of category.properties ) {
132
+ if ( isPropertyUsed( values, property ) ) {
133
+ continue;
134
+ }
135
+
136
+ return {
137
+ ...initialTransitionValue,
138
+ selection: {
139
+ $$type: 'key-value',
140
+ value: {
141
+ key: { value: property.label, $$type: 'string' },
142
+ value: { value: property.value, $$type: 'string' },
143
+ },
144
+ },
145
+ };
146
+ }
147
+ }
148
+
149
+ return initialTransitionValue;
150
+ };
55
151
 
56
152
  const disableAddItemTooltipContent = (
57
153
  <Alert
@@ -71,8 +167,6 @@ const disableAddItemTooltipContent = (
71
167
  </Alert>
72
168
  );
73
169
 
74
- subscribeToTransitionEvent();
75
-
76
170
  export const TransitionRepeaterControl = createControl(
77
171
  ( {
78
172
  recentlyUsedListGetter,
@@ -84,10 +178,40 @@ export const TransitionRepeaterControl = createControl(
84
178
  const currentStyleIsNormal = currentStyleState === null;
85
179
  const [ recentlyUsedList, setRecentlyUsedList ] = useState< string[] >( [] );
86
180
 
181
+ const { value, setValue } = useBoundProp( childArrayPropTypeUtil );
182
+ const disabledItems = useMemo( () => getDisabledItemLabels( value ), [ value ] );
183
+
184
+ const allowedTransitionSet = useMemo( () => {
185
+ const set = new Set< string >();
186
+ transitionProperties.forEach( ( category ) => {
187
+ category.properties.forEach( ( prop ) => set.add( prop.value ) );
188
+ } );
189
+ return set;
190
+ }, [] );
191
+
192
+ useEffect( () => {
193
+ if ( ! value || value.length === 0 ) {
194
+ return;
195
+ }
196
+
197
+ const sanitized = value.filter( ( item ) => {
198
+ const selectionValue = ( item?.value?.selection?.value as KeyValuePropValue )?.value?.value ?? '';
199
+ return allowedTransitionSet.has( selectionValue );
200
+ } );
201
+
202
+ if ( sanitized.length !== value.length ) {
203
+ setValue( sanitized );
204
+ }
205
+ // eslint-disable-next-line react-hooks/exhaustive-deps
206
+ }, [ allowedTransitionSet ] );
207
+
87
208
  useEffect( () => {
88
209
  recentlyUsedListGetter().then( setRecentlyUsedList );
89
210
  }, [ recentlyUsedListGetter ] );
90
211
 
212
+ const allPropertiesUsed = useMemo( () => areAllPropertiesUsed( value ), [ value ] );
213
+ const isAddItemDisabled = ! currentStyleIsNormal || allPropertiesUsed;
214
+
91
215
  return (
92
216
  <RepeatableControl
93
217
  label={ __( 'Transitions', 'elementor' ) }
@@ -96,11 +220,11 @@ export const TransitionRepeaterControl = createControl(
96
220
  placeholder={ __( 'Empty Transition', 'elementor' ) }
97
221
  showDuplicate={ false }
98
222
  showToggle={ true }
99
- initialValues={ initialTransitionValue }
100
- childControlConfig={ getChildControlConfig( recentlyUsedList ) }
223
+ initialValues={ getInitialValue( value ) }
224
+ childControlConfig={ getChildControlConfig( recentlyUsedList, disabledItems ) }
101
225
  propKey="transition"
102
226
  addItemTooltipProps={ {
103
- disabled: ! currentStyleIsNormal,
227
+ disabled: isAddItemDisabled,
104
228
  enableTooltip: ! currentStyleIsNormal,
105
229
  tooltipContent: disableAddItemTooltipContent,
106
230
  } }
@@ -1,12 +1,13 @@
1
1
  import * as React from 'react';
2
2
  import { useRef } from 'react';
3
- import { keyValuePropTypeUtil } from '@elementor/editor-props';
3
+ import { keyValuePropTypeUtil, type KeyValuePropValue, type StringPropValue } from '@elementor/editor-props';
4
4
  import { ChevronDownIcon, VariationsIcon } from '@elementor/icons';
5
5
  import { bindPopover, bindTrigger, Box, Popover, UnstableTag, usePopupState } from '@elementor/ui';
6
6
  import { __ } from '@wordpress/i18n';
7
7
 
8
8
  import { useBoundProp } from '../../bound-prop-context';
9
9
  import { ItemSelector } from '../../components/item-selector';
10
+ import ControlActions from '../../control-actions/control-actions';
10
11
  import { transitionProperties, transitionsItemsList } from './data';
11
12
 
12
13
  const toTransitionSelectorValue = ( label: string ) => {
@@ -23,16 +24,35 @@ const toTransitionSelectorValue = ( label: string ) => {
23
24
  return null;
24
25
  };
25
26
 
26
- const findByValue = ( value: string ) => {
27
+ export function getTransitionPropertyByValue( item?: StringPropValue | null ) {
28
+ if ( ! item?.value ) {
29
+ return null;
30
+ }
31
+
27
32
  for ( const category of transitionProperties ) {
28
- const property = category.properties.find( ( prop ) => prop.value === value );
29
- if ( property ) {
30
- return property.label;
33
+ for ( const property of category.properties ) {
34
+ if ( property.value === item.value ) {
35
+ return property;
36
+ }
31
37
  }
32
38
  }
39
+
40
+ return null;
41
+ }
42
+
43
+ const includeCurrentValueInOptions = ( value: KeyValuePropValue[ 'value' ], disabledItems: string[] ) => {
44
+ return disabledItems.filter( ( item ) => {
45
+ return item !== value.key.value;
46
+ } );
33
47
  };
34
48
 
35
- export const TransitionSelector = ( { recentlyUsedList = [] }: { recentlyUsedList: string[] } ) => {
49
+ export const TransitionSelector = ( {
50
+ recentlyUsedList = [],
51
+ disabledItems = [],
52
+ }: {
53
+ recentlyUsedList: string[];
54
+ disabledItems?: string[];
55
+ } ) => {
36
56
  const { value, setValue } = useBoundProp( keyValuePropTypeUtil );
37
57
  const {
38
58
  key: { value: transitionLabel },
@@ -42,7 +62,7 @@ export const TransitionSelector = ( { recentlyUsedList = [] }: { recentlyUsedLis
42
62
 
43
63
  const getItemList = () => {
44
64
  const recentItems = recentlyUsedList
45
- .map( ( item ) => findByValue( item ) )
65
+ .map( ( item ) => getTransitionPropertyByValue( { value: item, $$type: 'string' } )?.label )
46
66
  .filter( ( item ) => !! item ) as string[];
47
67
  const filteredItems = transitionsItemsList.map( ( category ) => {
48
68
  return {
@@ -89,13 +109,15 @@ export const TransitionSelector = ( { recentlyUsedList = [] }: { recentlyUsedLis
89
109
 
90
110
  return (
91
111
  <Box ref={ defaultRef }>
92
- <UnstableTag
93
- variant="outlined"
94
- label={ transitionLabel }
95
- endIcon={ <ChevronDownIcon fontSize="tiny" /> }
96
- { ...bindTrigger( popoverState ) }
97
- fullWidth
98
- />
112
+ <ControlActions>
113
+ <UnstableTag
114
+ variant="outlined"
115
+ label={ transitionLabel }
116
+ endIcon={ <ChevronDownIcon fontSize="tiny" /> }
117
+ { ...bindTrigger( popoverState ) }
118
+ fullWidth
119
+ />
120
+ </ControlActions>
99
121
  <Popover
100
122
  disablePortal
101
123
  disableScrollLock
@@ -113,6 +135,7 @@ export const TransitionSelector = ( { recentlyUsedList = [] }: { recentlyUsedLis
113
135
  sectionWidth={ 268 }
114
136
  title={ __( 'Transition Property', 'elementor' ) }
115
137
  icon={ VariationsIcon as React.ElementType< { fontSize: string } > }
138
+ disabledItems={ includeCurrentValueInOptions( value, disabledItems ) }
116
139
  />
117
140
  </Popover>
118
141
  </Box>
@@ -6,20 +6,25 @@ import { useBoundProp } from '../bound-prop-context';
6
6
  import ControlActions from '../control-actions/control-actions';
7
7
  import { createControl } from '../create-control';
8
8
 
9
- export const UrlControl = createControl( ( { placeholder }: { placeholder?: string } ) => {
10
- const { value, setValue, disabled } = useBoundProp( urlPropTypeUtil );
11
- const handleChange = ( event: React.ChangeEvent< HTMLInputElement > ) => setValue( event.target.value );
9
+ export const UrlControl = createControl(
10
+ ( { placeholder, ariaLabel }: { placeholder?: string; ariaLabel?: string } ) => {
11
+ const { value, setValue, disabled } = useBoundProp( urlPropTypeUtil );
12
+ const handleChange = ( event: React.ChangeEvent< HTMLInputElement > ) => setValue( event.target.value );
12
13
 
13
- return (
14
- <ControlActions>
15
- <TextField
16
- size="tiny"
17
- fullWidth
18
- value={ value ?? '' }
19
- disabled={ disabled }
20
- onChange={ handleChange }
21
- placeholder={ placeholder }
22
- />
23
- </ControlActions>
24
- );
25
- } );
14
+ return (
15
+ <ControlActions>
16
+ <TextField
17
+ size="tiny"
18
+ fullWidth
19
+ value={ value ?? '' }
20
+ disabled={ disabled }
21
+ onChange={ handleChange }
22
+ placeholder={ placeholder }
23
+ inputProps={ {
24
+ ...( ariaLabel ? { 'aria-label': ariaLabel } : {} ),
25
+ } }
26
+ />
27
+ </ControlActions>
28
+ );
29
+ }
30
+ );
@@ -15,11 +15,12 @@ export type ControlComponent< TComponent extends AnyComponentType = AnyComponent
15
15
 
16
16
  export function createControl< T extends AnyComponentType >( Control: T ) {
17
17
  return ( ( props: ComponentProps< T > ) => {
18
- const Component = useControlReplacement( Control );
18
+ const { ControlToRender, OriginalControl, isReplaced } = useControlReplacement( Control );
19
+ const controlProps = isReplaced ? { ...props, OriginalControl } : props;
19
20
 
20
21
  return (
21
22
  <ErrorBoundary fallback={ null }>
22
- <Component { ...props } />
23
+ <ControlToRender { ...controlProps } />
23
24
  </ErrorBoundary>
24
25
  );
25
26
  } ) as ControlComponent< T >;