@elementor/editor-controls 1.1.0 → 1.3.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 (43) hide show
  1. package/CHANGELOG.md +57 -0
  2. package/dist/index.d.mts +26 -13
  3. package/dist/index.d.ts +26 -13
  4. package/dist/index.js +979 -575
  5. package/dist/index.js.map +1 -1
  6. package/dist/index.mjs +823 -418
  7. package/dist/index.mjs.map +1 -1
  8. package/package.json +11 -11
  9. package/src/bound-prop-context/prop-context.tsx +3 -3
  10. package/src/bound-prop-context/prop-key-context.tsx +1 -0
  11. package/src/bound-prop-context/use-bound-prop.ts +5 -1
  12. package/src/components/font-family-selector.tsx +30 -13
  13. package/src/components/popover-content.tsx +3 -11
  14. package/src/components/repeater.tsx +3 -1
  15. package/src/components/text-field-popover.tsx +21 -20
  16. package/src/controls/aspect-ratio-control.tsx +20 -2
  17. package/src/controls/background-control/background-overlay/background-image-overlay/background-image-overlay-position.tsx +2 -2
  18. package/src/controls/background-control/background-overlay/background-image-overlay/background-image-overlay-size.tsx +2 -2
  19. package/src/controls/background-control/background-overlay/background-overlay-repeater-control.tsx +3 -10
  20. package/src/controls/box-shadow-repeater-control.tsx +3 -3
  21. package/src/controls/equal-unequal-sizes-control.tsx +4 -10
  22. package/src/controls/filter-repeater-control.tsx +186 -0
  23. package/src/controls/font-family-control/font-family-control.tsx +20 -4
  24. package/src/controls/gap-control.tsx +3 -3
  25. package/src/controls/image-control.tsx +46 -30
  26. package/src/controls/key-value-control.tsx +39 -19
  27. package/src/controls/link-control.tsx +28 -21
  28. package/src/controls/linked-dimensions-control.tsx +4 -4
  29. package/src/controls/number-control.tsx +3 -3
  30. package/src/controls/repeatable-control.tsx +98 -8
  31. package/src/controls/select-control.tsx +22 -2
  32. package/src/controls/size-control.tsx +3 -3
  33. package/src/controls/stroke-control.tsx +2 -2
  34. package/src/controls/svg-media-control.tsx +0 -2
  35. package/src/controls/switch-control.tsx +9 -1
  36. package/src/controls/transform-control/functions/axis-row.tsx +32 -0
  37. package/src/controls/transform-control/functions/move.tsx +44 -0
  38. package/src/controls/transform-control/transform-content.tsx +36 -0
  39. package/src/controls/transform-control/transform-icon.tsx +12 -0
  40. package/src/controls/transform-control/transform-label.tsx +27 -0
  41. package/src/controls/transform-control/transform-repeater-control.tsx +42 -0
  42. package/src/hooks/use-repeatable-control-context.ts +6 -1
  43. package/src/index.ts +4 -1
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@elementor/editor-controls",
3
3
  "description": "This package contains the controls model and utils for the Elementor editor",
4
- "version": "1.1.0",
4
+ "version": "1.3.0",
5
5
  "private": false,
6
6
  "author": "Elementor Team",
7
7
  "homepage": "https://elementor.com/",
@@ -40,21 +40,21 @@
40
40
  "dev": "tsup --config=../../tsup.dev.ts"
41
41
  },
42
42
  "dependencies": {
43
- "@elementor/editor-current-user": "0.5.0",
44
- "@elementor/editor-elements": "0.8.6",
45
- "@elementor/editor-props": "0.14.0",
46
- "@elementor/editor-responsive": "0.13.5",
47
- "@elementor/editor-ui": "0.12.0",
48
- "@elementor/editor-v1-adapters": "0.12.0",
43
+ "@elementor/editor-current-user": "0.6.0",
44
+ "@elementor/editor-elements": "0.9.0",
45
+ "@elementor/editor-props": "0.16.0",
46
+ "@elementor/editor-responsive": "0.13.6",
47
+ "@elementor/editor-ui": "0.14.0",
48
+ "@elementor/editor-v1-adapters": "0.12.1",
49
49
  "@elementor/env": "0.3.5",
50
50
  "@elementor/http-client": "0.3.0",
51
- "@elementor/icons": "1.44.0",
51
+ "@elementor/icons": "1.46.0",
52
52
  "@elementor/locations": "0.8.0",
53
53
  "@elementor/query": "0.2.4",
54
54
  "@elementor/session": "0.1.0",
55
- "@elementor/ui": "1.35.5",
56
- "@elementor/utils": "0.4.0",
57
- "@elementor/wp-media": "0.6.0",
55
+ "@elementor/ui": "1.36.0",
56
+ "@elementor/utils": "0.5.0",
57
+ "@elementor/wp-media": "0.6.1",
58
58
  "@wordpress/i18n": "^5.13.0"
59
59
  },
60
60
  "devDependencies": {
@@ -15,7 +15,7 @@ type PropContext< T extends PropValue, P extends PropType > = {
15
15
  value: T | null;
16
16
  propType: P;
17
17
  placeholder?: T;
18
- disabled?: boolean;
18
+ isDisabled?: ( propType: PropType ) => boolean | undefined;
19
19
  };
20
20
 
21
21
  const PropContext = createContext< PropContext< PropValue, PropType > | null >( null );
@@ -30,7 +30,7 @@ export const PropProvider = < T extends PropValue, P extends PropType >( {
30
30
  setValue,
31
31
  propType,
32
32
  placeholder,
33
- disabled,
33
+ isDisabled,
34
34
  }: PropProviderProps< T, P > ) => {
35
35
  return (
36
36
  <PropContext.Provider
@@ -39,7 +39,7 @@ export const PropProvider = < T extends PropValue, P extends PropType >( {
39
39
  propType,
40
40
  setValue: setValue as SetValue< PropValue >,
41
41
  placeholder,
42
- disabled,
42
+ isDisabled,
43
43
  } }
44
44
  >
45
45
  { children }
@@ -21,6 +21,7 @@ export type PropKeyContextValue< T, P > = {
21
21
  propType: P;
22
22
  placeholder?: T;
23
23
  path: PropKey[];
24
+ isDisabled?: ( propType: PropType ) => boolean | undefined;
24
25
  disabled?: boolean;
25
26
  };
26
27
 
@@ -19,6 +19,7 @@ type UseBoundProp< TValue extends PropValue > = {
19
19
  placeholder?: TValue;
20
20
  path: PropKey[];
21
21
  restoreValue: () => void;
22
+ isDisabled?: ( propType: PropType ) => boolean | undefined;
22
23
  disabled?: boolean;
23
24
  };
24
25
 
@@ -38,9 +39,11 @@ export function useBoundProp< TKey extends string, TValue extends PropValue >(
38
39
 
39
40
  const { isValid, validate, restoreValue } = useValidation( propKeyContext.propType );
40
41
 
42
+ const disabled = propKeyContext.isDisabled?.( propKeyContext.propType );
43
+
41
44
  // allow using the hook without a propTypeUtil, with no modifications or validations.
42
45
  if ( ! propTypeUtil ) {
43
- return propKeyContext;
46
+ return { ...propKeyContext, disabled } as PropKeyContextValue< PropValue, PropType >;
44
47
  }
45
48
 
46
49
  function setValue( value: TValue | null, options: CreateOptions, meta: { bind?: PropKey } ) {
@@ -67,6 +70,7 @@ export function useBoundProp< TKey extends string, TValue extends PropValue >(
67
70
  value: isValid ? value : null,
68
71
  restoreValue,
69
72
  placeholder,
73
+ disabled,
70
74
  };
71
75
  }
72
76
 
@@ -17,6 +17,7 @@ type FontFamilySelectorProps = {
17
17
  fontFamily: string | null;
18
18
  onFontFamilyChange: ( fontFamily: string ) => void;
19
19
  onClose: () => void;
20
+ sectionWidth: number;
20
21
  };
21
22
 
22
23
  export const FontFamilySelector = ( {
@@ -24,6 +25,7 @@ export const FontFamilySelector = ( {
24
25
  fontFamily,
25
26
  onFontFamilyChange,
26
27
  onClose,
28
+ sectionWidth,
27
29
  }: FontFamilySelectorProps ) => {
28
30
  const [ searchValue, setSearchValue ] = useState( '' );
29
31
 
@@ -45,22 +47,32 @@ export const FontFamilySelector = ( {
45
47
  onClose={ handleClose }
46
48
  icon={ <TextIcon fontSize={ SIZE } /> }
47
49
  />
50
+
48
51
  <PopoverSearch
49
52
  value={ searchValue }
50
53
  onSearch={ handleSearch }
51
54
  placeholder={ __( 'Search', 'elementor' ) }
52
55
  />
56
+
53
57
  <Divider />
54
- { filteredFontFamilies.length > 0 ? (
55
- <FontList
56
- fontListItems={ filteredFontFamilies }
57
- setFontFamily={ onFontFamilyChange }
58
- handleClose={ handleClose }
59
- fontFamily={ fontFamily }
60
- />
61
- ) : (
62
- <PopoverScrollableContent>
63
- <Stack alignItems="center" p={ 2.5 } gap={ 1.5 } overflow={ 'hidden' }>
58
+
59
+ <PopoverScrollableContent width={ sectionWidth }>
60
+ { filteredFontFamilies.length > 0 ? (
61
+ <FontList
62
+ fontListItems={ filteredFontFamilies }
63
+ setFontFamily={ onFontFamilyChange }
64
+ handleClose={ handleClose }
65
+ fontFamily={ fontFamily }
66
+ />
67
+ ) : (
68
+ <Stack
69
+ alignItems="center"
70
+ justifyContent="center"
71
+ height="100%"
72
+ p={ 2.5 }
73
+ gap={ 1.5 }
74
+ overflow={ 'hidden' }
75
+ >
64
76
  <TextIcon fontSize="large" />
65
77
  <Box sx={ { maxWidth: 160, overflow: 'hidden' } }>
66
78
  <Typography align="center" variant="subtitle2" color="text.secondary">
@@ -82,7 +94,12 @@ export const FontFamilySelector = ( {
82
94
  <span>&rdquo;.</span>
83
95
  </Typography>
84
96
  </Box>
85
- <Typography align="center" variant="caption" color="text.secondary">
97
+ <Typography
98
+ align="center"
99
+ variant="caption"
100
+ color="text.secondary"
101
+ sx={ { display: 'flex', flexDirection: 'column' } }
102
+ >
86
103
  { __( 'Try something else.', 'elementor' ) }
87
104
  <Link
88
105
  color="secondary"
@@ -94,8 +111,8 @@ export const FontFamilySelector = ( {
94
111
  </Link>
95
112
  </Typography>
96
113
  </Stack>
97
- </PopoverScrollableContent>
98
- ) }
114
+ ) }
115
+ </PopoverScrollableContent>
99
116
  </Stack>
100
117
  );
101
118
  };
@@ -1,17 +1,9 @@
1
1
  import { type FC, type PropsWithChildren } from 'react';
2
2
  import * as React from 'react';
3
- import { Stack } from '@elementor/ui';
3
+ import { Stack, type StackProps } from '@elementor/ui';
4
4
 
5
- type PopoverContentProps = PropsWithChildren< {
6
- alignItems?: 'center';
7
- gap?: number;
8
- p?: 1.5 | 2 | 2.5;
9
- pt?: 2.5;
10
- pb?: 3;
11
- } >;
12
-
13
- export const PopoverContent: FC< PopoverContentProps > = ( { alignItems, gap = 1.5, p, pt, pb, children } ) => (
14
- <Stack alignItems={ alignItems } gap={ gap } p={ p } pt={ pt } pb={ pb }>
5
+ export const PopoverContent: FC< PropsWithChildren< StackProps > > = ( { gap = 1.5, children, ...props } ) => (
6
+ <Stack { ...props } gap={ gap }>
15
7
  { children }
16
8
  </Stack>
17
9
  );
@@ -50,6 +50,7 @@ type RepeaterProps< T > = {
50
50
  };
51
51
  showDuplicate?: boolean;
52
52
  showToggle?: boolean;
53
+ isSortable?: boolean;
53
54
  };
54
55
 
55
56
  const EMPTY_OPEN_ITEM = -1;
@@ -64,6 +65,7 @@ export const Repeater = < T, >( {
64
65
  setValues: setRepeaterValues,
65
66
  showDuplicate = true,
66
67
  showToggle = true,
68
+ isSortable = true,
67
69
  }: RepeaterProps< Item< T > > ) => {
68
70
  const [ openItem, setOpenItem ] = useState( EMPTY_OPEN_ITEM );
69
71
 
@@ -180,7 +182,7 @@ export const Repeater = < T, >( {
180
182
  }
181
183
 
182
184
  return (
183
- <SortableItem id={ key } key={ `sortable-${ key }` } disabled={ disabled }>
185
+ <SortableItem id={ key } key={ `sortable-${ key }` } disabled={ ! isSortable }>
184
186
  <RepeaterItem
185
187
  disabled={ disabled }
186
188
  propDisabled={ value?.disabled }
@@ -1,10 +1,10 @@
1
1
  import * as React from 'react';
2
- import { type MutableRefObject } from 'react';
3
- import { bindPopover, Paper, Popover, type PopupState, TextField } from '@elementor/ui';
2
+ import { type RefObject } from 'react';
3
+ import { bindPopover, Popover, type PopupState, TextField } from '@elementor/ui';
4
4
 
5
5
  type Props = {
6
6
  popupState: PopupState;
7
- anchorRef: MutableRefObject< HTMLElement >;
7
+ anchorRef: RefObject< HTMLDivElement | null >;
8
8
  restoreValue: () => void;
9
9
  value: string;
10
10
  onChange: ( event: React.ChangeEvent< HTMLInputElement > ) => void;
@@ -16,6 +16,15 @@ export const TextFieldPopover = ( props: Props ) => {
16
16
  return (
17
17
  <Popover
18
18
  disablePortal
19
+ slotProps={ {
20
+ paper: {
21
+ sx: {
22
+ borderRadius: 2,
23
+ width: anchorRef.current?.offsetWidth + 'px',
24
+ p: 1.5,
25
+ },
26
+ },
27
+ } }
19
28
  { ...bindPopover( popupState ) }
20
29
  anchorOrigin={ { vertical: 'bottom', horizontal: 'center' } }
21
30
  transformOrigin={ { vertical: 'top', horizontal: 'center' } }
@@ -24,24 +33,16 @@ export const TextFieldPopover = ( props: Props ) => {
24
33
  popupState.close();
25
34
  } }
26
35
  >
27
- <Paper
28
- sx={ {
29
- width: anchorRef.current.offsetWidth + 'px',
30
- borderRadius: 2,
31
- p: 1.5,
36
+ <TextField
37
+ value={ value }
38
+ onChange={ onChange }
39
+ size="tiny"
40
+ type="text"
41
+ fullWidth
42
+ inputProps={ {
43
+ autoFocus: true,
32
44
  } }
33
- >
34
- <TextField
35
- value={ value }
36
- onChange={ onChange }
37
- size="tiny"
38
- type="text"
39
- fullWidth
40
- inputProps={ {
41
- autoFocus: true,
42
- } }
43
- />
44
- </Paper>
45
+ />
45
46
  </Popover>
46
47
  );
47
48
  };
@@ -1,5 +1,5 @@
1
1
  import * as React from 'react';
2
- import { useState } from 'react';
2
+ import { useEffect, useState } from 'react';
3
3
  import { stringPropTypeUtil } from '@elementor/editor-props';
4
4
  import { MenuListItem } from '@elementor/editor-ui';
5
5
  import { ArrowsMoveHorizontalIcon, ArrowsMoveVerticalIcon } from '@elementor/icons';
@@ -38,6 +38,24 @@ export const AspectRatioControl = createControl( ( { label }: { label: string }
38
38
  isCustomSelected ? CUSTOM_RATIO : aspectRatioValue || ''
39
39
  );
40
40
 
41
+ useEffect( () => {
42
+ const isCustomValue =
43
+ aspectRatioValue && ! RATIO_OPTIONS.some( ( option ) => option.value === aspectRatioValue );
44
+
45
+ if ( isCustomValue ) {
46
+ const [ width, height ] = aspectRatioValue.split( '/' );
47
+ setCustomWidth( width || '' );
48
+ setCustomHeight( height || '' );
49
+ setSelectedValue( CUSTOM_RATIO );
50
+ setIsCustom( true );
51
+ } else {
52
+ setSelectedValue( aspectRatioValue || '' );
53
+ setIsCustom( false );
54
+ setCustomWidth( '' );
55
+ setCustomHeight( '' );
56
+ }
57
+ }, [ aspectRatioValue ] );
58
+
41
59
  const handleSelectChange = ( event: SelectChangeEvent< string > ) => {
42
60
  const newValue = event.target.value;
43
61
  const isCustomRatio = newValue === CUSTOM_RATIO;
@@ -71,7 +89,7 @@ export const AspectRatioControl = createControl( ( { label }: { label: string }
71
89
 
72
90
  return (
73
91
  <ControlActions>
74
- <Stack direction="column" pt={ 2 } gap={ 2 }>
92
+ <Stack direction="column" gap={ 2 }>
75
93
  <Grid container gap={ 2 } alignItems="center" flexWrap="nowrap">
76
94
  <Grid item xs={ 6 }>
77
95
  <ControlLabel>{ label }</ControlLabel>
@@ -1,5 +1,5 @@
1
1
  import * as React from 'react';
2
- import { type MutableRefObject, useRef } from 'react';
2
+ import { useRef } from 'react';
3
3
  import { backgroundImagePositionOffsetPropTypeUtil, stringPropTypeUtil } from '@elementor/editor-props';
4
4
  import { MenuListItem } from '@elementor/editor-ui';
5
5
  import { LetterXIcon, LetterYIcon } from '@elementor/icons';
@@ -41,7 +41,7 @@ export const BackgroundImageOverlayPosition = () => {
41
41
  const stringPropContext = useBoundProp( stringPropTypeUtil );
42
42
 
43
43
  const isCustom = !! backgroundImageOffsetContext.value;
44
- const rowRef: MutableRefObject< HTMLElement | undefined > = useRef();
44
+ const rowRef = useRef< HTMLDivElement >( null );
45
45
 
46
46
  const handlePositionChange = ( event: SelectChangeEvent< Positions > ) => {
47
47
  const value = event.target.value || null;
@@ -1,5 +1,5 @@
1
1
  import * as React from 'react';
2
- import { type MutableRefObject, useRef } from 'react';
2
+ import { useRef } from 'react';
3
3
  import { backgroundImageSizeScalePropTypeUtil, stringPropTypeUtil } from '@elementor/editor-props';
4
4
  import {
5
5
  ArrowBarBothIcon,
@@ -55,7 +55,7 @@ export const BackgroundImageOverlaySize = () => {
55
55
  const stringPropContext = useBoundProp( stringPropTypeUtil );
56
56
 
57
57
  const isCustom = !! backgroundImageScaleContext.value;
58
- const rowRef: MutableRefObject< HTMLElement | undefined > = useRef();
58
+ const rowRef = useRef< HTMLDivElement >( null );
59
59
 
60
60
  const handleSizeChange = ( size: Sizes | null ) => {
61
61
  if ( size === 'custom' ) {
@@ -7,7 +7,7 @@ import {
7
7
  colorPropTypeUtil,
8
8
  type PropKey,
9
9
  } from '@elementor/editor-props';
10
- import { Box, CardMedia, Grid, styled, Tab, TabPanel, Tabs, type Theme, UnstableColorIndicator } from '@elementor/ui';
10
+ import { Box, CardMedia, styled, Tab, TabPanel, Tabs, type Theme, UnstableColorIndicator } from '@elementor/ui';
11
11
  import { useWpMediaAttachment } from '@elementor/wp-media';
12
12
  import { __ } from '@wordpress/i18n';
13
13
 
@@ -74,7 +74,7 @@ export const BackgroundOverlayRepeaterControl = createControl( () => {
74
74
  const { propType, value: overlayValues, setValue, disabled } = useBoundProp( backgroundOverlayPropTypeUtil );
75
75
 
76
76
  return (
77
- <PropProvider propType={ propType } value={ overlayValues } setValue={ setValue } disabled={ disabled }>
77
+ <PropProvider propType={ propType } value={ overlayValues } setValue={ setValue } isDisabled={ () => disabled }>
78
78
  <Repeater
79
79
  openOnAdd
80
80
  disabled={ disabled }
@@ -235,14 +235,7 @@ const ImageOverlayContent = () => {
235
235
  return (
236
236
  <PropProvider { ...propContext }>
237
237
  <PropKeyProvider bind={ 'image' }>
238
- <Grid container spacing={ 1 } alignItems="center">
239
- <Grid item xs={ 12 }>
240
- <ImageControl
241
- resolutionLabel={ __( 'Resolution', 'elementor' ) }
242
- sizes={ backgroundResolutionOptions }
243
- />
244
- </Grid>
245
- </Grid>
238
+ <ImageControl sizes={ backgroundResolutionOptions } />
246
239
  </PropKeyProvider>
247
240
  <PropKeyProvider bind={ 'position' }>
248
241
  <BackgroundImageOverlayPosition />
@@ -1,5 +1,5 @@
1
1
  import * as React from 'react';
2
- import { type MutableRefObject, useRef } from 'react';
2
+ import { type RefObject, useRef } from 'react';
3
3
  import { boxShadowPropTypeUtil, type PropKey, shadowPropTypeUtil, type ShadowPropValue } from '@elementor/editor-props';
4
4
  import { FormLabel, Grid, type SxProps, type Theme, UnstableColorIndicator } from '@elementor/ui';
5
5
  import { __ } from '@wordpress/i18n';
@@ -17,7 +17,7 @@ export const BoxShadowRepeaterControl = createControl( () => {
17
17
  const { propType, value, setValue, disabled } = useBoundProp( boxShadowPropTypeUtil );
18
18
 
19
19
  return (
20
- <PropProvider propType={ propType } value={ value } setValue={ setValue } disabled={ disabled }>
20
+ <PropProvider propType={ propType } value={ value } setValue={ setValue } isDisabled={ () => disabled }>
21
21
  <Repeater
22
22
  openOnAdd
23
23
  disabled={ disabled }
@@ -49,7 +49,7 @@ const ItemContent = ( { anchorEl, bind }: { anchorEl: HTMLElement | null; bind:
49
49
 
50
50
  const Content = ( { anchorEl }: { anchorEl: HTMLElement | null } ) => {
51
51
  const context = useBoundProp( shadowPropTypeUtil );
52
- const rowRef: MutableRefObject< HTMLElement | undefined >[] = [ useRef(), useRef() ];
52
+ const rowRef: RefObject< HTMLDivElement >[] = [ useRef( null ), useRef( null ) ];
53
53
 
54
54
  return (
55
55
  <PropProvider { ...context }>
@@ -1,5 +1,5 @@
1
1
  import * as React from 'react';
2
- import { type MutableRefObject, type ReactNode, useId, useRef } from 'react';
2
+ import { type ReactNode, type RefObject, useId, useRef } from 'react';
3
3
  import { type PropKey, type PropTypeUtil, sizePropTypeUtil, type SizePropValue } from '@elementor/editor-props';
4
4
  import { isExperimentActive } from '@elementor/editor-v1-adapters';
5
5
  import { bindPopover, bindToggle, Grid, Popover, Stack, ToggleButton, Tooltip, usePopupState } from '@elementor/ui';
@@ -63,7 +63,7 @@ export function EqualUnequalSizesControl< TMultiPropType extends string, TPropVa
63
63
 
64
64
  const { value: sizeValue, setValue: setSizeValue } = useBoundProp( sizePropTypeUtil );
65
65
 
66
- const rowRefs: MutableRefObject< HTMLElement | undefined >[] = [ useRef(), useRef() ];
66
+ const rowRefs: RefObject< HTMLDivElement >[] = [ useRef( null ), useRef( null ) ];
67
67
 
68
68
  const splitEqualValue = () => {
69
69
  if ( ! sizeValue ) {
@@ -154,7 +154,7 @@ export function EqualUnequalSizesControl< TMultiPropType extends string, TPropVa
154
154
  propType={ multiSizePropType }
155
155
  value={ getMultiSizeValues() }
156
156
  setValue={ setNestedProp }
157
- disabled={ multiSizeDisabled }
157
+ isDisabled={ () => multiSizeDisabled }
158
158
  >
159
159
  <PopoverContent p={ 1.5 }>
160
160
  <PopoverGridContainer ref={ rowRefs[ 1 ] }>
@@ -172,13 +172,7 @@ export function EqualUnequalSizesControl< TMultiPropType extends string, TPropVa
172
172
  );
173
173
  }
174
174
 
175
- const MultiSizeValueControl = ( {
176
- item,
177
- rowRef,
178
- }: {
179
- item: Item;
180
- rowRef: MutableRefObject< HTMLElement | undefined >;
181
- } ) => {
175
+ const MultiSizeValueControl = ( { item, rowRef }: { item: Item; rowRef: RefObject< HTMLDivElement > } ) => {
182
176
  const isUsingNestedProps = isExperimentActive( 'e_v_3_30' );
183
177
 
184
178
  return (
@@ -0,0 +1,186 @@
1
+ import * as React from 'react';
2
+ import { useRef } from 'react';
3
+ import {
4
+ blurFilterPropTypeUtil,
5
+ brightnessFilterPropTypeUtil,
6
+ type FilterItemPropValue,
7
+ filterPropTypeUtil,
8
+ type PropKey,
9
+ type PropTypeUtil,
10
+ type SizePropValue,
11
+ } from '@elementor/editor-props';
12
+ import { MenuListItem } from '@elementor/editor-ui';
13
+ import { Box, Grid, Select, type SelectChangeEvent } from '@elementor/ui';
14
+ import { __ } from '@wordpress/i18n';
15
+
16
+ import { PropKeyProvider, PropProvider, useBoundProp } from '../bound-prop-context';
17
+ import { ControlLabel } from '../components/control-label';
18
+ import { PopoverContent } from '../components/popover-content';
19
+ import { PopoverGridContainer } from '../components/popover-grid-container';
20
+ import { Repeater } from '../components/repeater';
21
+ import { createControl } from '../create-control';
22
+ import { defaultUnits } from '../utils/size-control';
23
+ import { SizeControl } from './size-control';
24
+
25
+ type FilterType = FilterItemPropValue[ '$$type' ];
26
+ type FilterValue = FilterItemPropValue[ 'value' ];
27
+
28
+ const DEFAULT_FILTER_KEY: FilterType = 'blur';
29
+
30
+ type FilterItemConfig = {
31
+ defaultValue: FilterValue;
32
+ name: string;
33
+ valueName: string;
34
+ propType: PropTypeUtil< FilterValue, FilterValue >;
35
+ units?: Exclude< SizePropValue[ 'value' ][ 'unit' ], 'custom' | 'auto' >[];
36
+ };
37
+
38
+ const filterConfig: Record< FilterType, FilterItemConfig > = {
39
+ blur: {
40
+ defaultValue: { $$type: 'radius', radius: { $$type: 'size', value: { size: 0, unit: 'px' } } },
41
+ name: __( 'Blur', 'elementor' ),
42
+ valueName: __( 'Radius', 'elementor' ),
43
+ propType: blurFilterPropTypeUtil,
44
+ units: defaultUnits.filter( ( unit ) => unit !== '%' ),
45
+ },
46
+ brightness: {
47
+ defaultValue: { $$type: 'amount', amount: { $$type: 'size', value: { size: 100, unit: '%' } } },
48
+ name: __( 'Brightness', 'elementor' ),
49
+ valueName: __( 'Amount', 'elementor' ),
50
+ propType: brightnessFilterPropTypeUtil,
51
+ units: [ '%' ],
52
+ },
53
+ };
54
+
55
+ const filterKeys = Object.keys( filterConfig ) as FilterType[];
56
+
57
+ const singleSizeFilterNames = filterKeys.filter( ( name ) => {
58
+ const filter = filterConfig[ name as FilterType ].defaultValue;
59
+
60
+ return filter[ filter.$$type ].$$type === 'size';
61
+ } ) as FilterType[];
62
+
63
+ export const FilterRepeaterControl = createControl( () => {
64
+ const { propType, value: filterValues, setValue, disabled } = useBoundProp( filterPropTypeUtil );
65
+
66
+ return (
67
+ <PropProvider propType={ propType } value={ filterValues } setValue={ setValue }>
68
+ <Repeater
69
+ openOnAdd
70
+ disabled={ disabled }
71
+ values={ filterValues ?? [] }
72
+ setValues={ setValue }
73
+ label={ __( 'Filter', 'elementor' ) }
74
+ itemSettings={ {
75
+ Icon: ItemIcon,
76
+ Label: ItemLabel,
77
+ Content: ItemContent,
78
+ initialValues: {
79
+ $$type: DEFAULT_FILTER_KEY,
80
+ value: filterConfig[ DEFAULT_FILTER_KEY ].defaultValue,
81
+ } as FilterItemPropValue,
82
+ } }
83
+ />
84
+ </PropProvider>
85
+ );
86
+ } );
87
+
88
+ const ItemIcon = () => <></>;
89
+
90
+ const ItemLabel = ( props: { value: FilterItemPropValue } ) => {
91
+ const { $$type } = props.value;
92
+
93
+ return singleSizeFilterNames.includes( $$type ) && <SingleSizeItemLabel value={ props.value } />;
94
+ };
95
+
96
+ const SingleSizeItemLabel = ( { value }: { value: FilterItemPropValue } ) => {
97
+ const { $$type, value: sizeValue } = value;
98
+ const { $$type: key } = filterConfig[ $$type ].defaultValue;
99
+ const defaultUnit = filterConfig[ $$type ].defaultValue[ key ].value.unit;
100
+ const { unit, size } = sizeValue[ key ]?.value ?? { unit: defaultUnit, size: 0 };
101
+
102
+ const label = (
103
+ <Box component="span" style={ { textTransform: 'capitalize' } }>
104
+ { value.$$type }:
105
+ </Box>
106
+ );
107
+
108
+ return (
109
+ <Box component="span">
110
+ { label }
111
+ { unit !== 'custom' ? ` ${ size ?? 0 }${ unit ?? defaultUnit }` : size }
112
+ </Box>
113
+ );
114
+ };
115
+
116
+ const ItemContent = ( { bind }: { bind: PropKey } ) => {
117
+ const { value: filterValues, setValue } = useBoundProp( filterPropTypeUtil );
118
+ const itemIndex = parseInt( bind, 10 );
119
+ const item = filterValues?.[ itemIndex ];
120
+
121
+ const handleChange = ( e: SelectChangeEvent< string > ) => {
122
+ const newFilterValues = [ ...filterValues ];
123
+ const filterType = e.target.value as FilterType;
124
+
125
+ newFilterValues[ itemIndex ] = {
126
+ $$type: filterType,
127
+ value: filterConfig[ filterType ].defaultValue,
128
+ } as FilterItemPropValue;
129
+
130
+ setValue( newFilterValues );
131
+ };
132
+
133
+ return (
134
+ <PropKeyProvider bind={ bind }>
135
+ <PopoverContent p={ 1.5 }>
136
+ <PopoverGridContainer>
137
+ <Grid item xs={ 6 }>
138
+ <ControlLabel>{ __( 'Filter', 'elementor' ) }</ControlLabel>
139
+ </Grid>
140
+ <Grid item xs={ 6 }>
141
+ <Select
142
+ sx={ { overflow: 'hidden' } }
143
+ size="tiny"
144
+ value={ item?.$$type ?? DEFAULT_FILTER_KEY }
145
+ onChange={ handleChange }
146
+ fullWidth
147
+ >
148
+ { filterKeys.map( ( filterKey ) => (
149
+ <MenuListItem key={ filterKey } value={ filterKey }>
150
+ { filterConfig[ filterKey ].name }
151
+ </MenuListItem>
152
+ ) ) }
153
+ </Select>
154
+ </Grid>
155
+ </PopoverGridContainer>
156
+ <Content filterType={ item?.$$type } />
157
+ </PopoverContent>
158
+ </PropKeyProvider>
159
+ );
160
+ };
161
+
162
+ const Content = ( { filterType }: { filterType: FilterType } ) => {
163
+ return singleSizeFilterNames.includes( filterType ) && <SingleSizeItemContent filterType={ filterType } />;
164
+ };
165
+
166
+ const SingleSizeItemContent = ( { filterType }: { filterType: FilterType } ) => {
167
+ const { propType, valueName, defaultValue, units } = filterConfig[ filterType ];
168
+ const { $$type } = defaultValue;
169
+ const context = useBoundProp( propType );
170
+ const rowRef = useRef< HTMLDivElement >( null );
171
+
172
+ return (
173
+ <PropProvider { ...context }>
174
+ <PropKeyProvider bind={ $$type }>
175
+ <PopoverGridContainer ref={ rowRef }>
176
+ <Grid item xs={ 6 }>
177
+ <ControlLabel>{ valueName }</ControlLabel>
178
+ </Grid>
179
+ <Grid item xs={ 6 }>
180
+ <SizeControl anchorRef={ rowRef } units={ units } />
181
+ </Grid>
182
+ </PopoverGridContainer>
183
+ </PropKeyProvider>
184
+ </PropProvider>
185
+ );
186
+ };