@elementor/editor-editing-panel 1.44.0 → 1.46.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 (61) hide show
  1. package/CHANGELOG.md +62 -0
  2. package/dist/index.d.mts +11 -4
  3. package/dist/index.d.ts +11 -4
  4. package/dist/index.js +867 -765
  5. package/dist/index.js.map +1 -1
  6. package/dist/index.mjs +740 -636
  7. package/dist/index.mjs.map +1 -1
  8. package/package.json +14 -13
  9. package/src/components/css-classes/css-class-menu.tsx +6 -8
  10. package/src/components/css-classes/css-class-selector.tsx +17 -11
  11. package/src/components/settings-tab.tsx +25 -2
  12. package/src/components/style-indicator.tsx +19 -15
  13. package/src/components/style-sections/border-section/border-field.tsx +4 -6
  14. package/src/components/style-sections/border-section/border-radius-field.tsx +12 -9
  15. package/src/components/style-sections/effects-section/effects-section.tsx +6 -0
  16. package/src/components/style-sections/layout-section/align-content-field.tsx +10 -14
  17. package/src/components/style-sections/layout-section/align-items-field.tsx +13 -17
  18. package/src/components/style-sections/layout-section/align-self-child-field.tsx +13 -17
  19. package/src/components/style-sections/layout-section/flex-direction-field.tsx +13 -17
  20. package/src/components/style-sections/layout-section/flex-order-field.tsx +31 -36
  21. package/src/components/style-sections/layout-section/flex-size-field.tsx +67 -69
  22. package/src/components/style-sections/layout-section/justify-content-field.tsx +10 -14
  23. package/src/components/style-sections/layout-section/layout-section.tsx +2 -2
  24. package/src/components/style-sections/layout-section/opacity-control-field.tsx +25 -0
  25. package/src/components/style-sections/layout-section/utils/rotated-icon.tsx +1 -1
  26. package/src/components/style-sections/layout-section/wrap-field.tsx +13 -17
  27. package/src/components/style-sections/position-section/dimensions-field.tsx +39 -21
  28. package/src/components/style-sections/position-section/offset-field.tsx +5 -2
  29. package/src/components/style-sections/position-section/position-section.tsx +6 -6
  30. package/src/components/style-sections/size-section/object-position-field.tsx +2 -24
  31. package/src/components/style-sections/size-section/size-section.tsx +52 -37
  32. package/src/components/style-sections/spacing-section/spacing-section.tsx +1 -1
  33. package/src/components/style-sections/typography-section/column-gap-field.tsx +5 -2
  34. package/src/components/style-sections/typography-section/font-size-field.tsx +5 -2
  35. package/src/components/style-sections/typography-section/letter-spacing-field.tsx +5 -2
  36. package/src/components/style-sections/typography-section/line-height-field.tsx +5 -2
  37. package/src/components/style-sections/typography-section/text-alignment-field.tsx +12 -9
  38. package/src/components/style-sections/typography-section/text-stroke-field.tsx +4 -6
  39. package/src/components/style-sections/typography-section/typography-section.tsx +4 -2
  40. package/src/components/style-sections/typography-section/word-spacing-field.tsx +5 -2
  41. package/src/controls-registry/controls-registry.tsx +30 -10
  42. package/src/controls-registry/styles-field.tsx +1 -3
  43. package/src/dynamics/components/background-control-dynamic-tag.tsx +48 -0
  44. package/src/dynamics/components/dynamic-selection-control.tsx +10 -18
  45. package/src/dynamics/components/dynamic-selection.tsx +58 -77
  46. package/src/dynamics/hooks/use-prop-dynamic-action.tsx +1 -1
  47. package/src/dynamics/init.ts +21 -0
  48. package/src/hooks/use-styles-field.ts +9 -3
  49. package/src/hooks/use-styles-fields.ts +4 -4
  50. package/src/index.ts +1 -0
  51. package/src/popover-action.tsx +3 -5
  52. package/src/provider-colors-registry.ts +20 -0
  53. package/src/styles-inheritance/components/infotip/label-chip.tsx +4 -5
  54. package/src/styles-inheritance/components/styles-inheritance-indicator.tsx +32 -40
  55. package/src/styles-inheritance/components/styles-inheritance-infotip.tsx +1 -5
  56. package/src/styles-inheritance/components/styles-inheritance-section-indicators.tsx +29 -44
  57. package/src/styles-inheritance/components/ui-providers.tsx +18 -0
  58. package/src/styles-inheritance/hooks/use-normalized-inheritance-chain-items.tsx +1 -17
  59. package/src/styles-inheritance/types.ts +0 -2
  60. package/src/styles-inheritance/utils.ts +17 -1
  61. package/src/utils/get-styles-provider-color.ts +28 -0
@@ -16,7 +16,7 @@ export const SpacingSection = () => {
16
16
  <LinkedDimensionsControl
17
17
  label={ __( 'Margin', 'elementor' ) }
18
18
  isSiteRtl={ isSiteRtl }
19
- extendedValues={ [ 'auto' ] }
19
+ extendedOptions={ [ 'auto' ] }
20
20
  />
21
21
  </StylesField>
22
22
  <PanelDivider />
@@ -1,4 +1,5 @@
1
1
  import * as React from 'react';
2
+ import { type MutableRefObject, useRef } from 'react';
2
3
  import { SizeControl } from '@elementor/editor-controls';
3
4
  import { Grid } from '@elementor/ui';
4
5
  import { __ } from '@wordpress/i18n';
@@ -7,14 +8,16 @@ import { StylesField } from '../../../controls-registry/styles-field';
7
8
  import { ControlLabel } from '../../control-label';
8
9
 
9
10
  export const ColumnGapField = () => {
11
+ const rowRef: MutableRefObject< HTMLElement | undefined > = useRef();
12
+
10
13
  return (
11
14
  <StylesField bind="column-gap">
12
- <Grid container gap={ 2 } alignItems="center" flexWrap="nowrap">
15
+ <Grid container gap={ 2 } alignItems="center" flexWrap="nowrap" ref={ rowRef }>
13
16
  <Grid item xs={ 6 }>
14
17
  <ControlLabel>{ __( 'Column gap', 'elementor' ) }</ControlLabel>
15
18
  </Grid>
16
19
  <Grid item xs={ 6 }>
17
- <SizeControl />
20
+ <SizeControl anchorRef={ rowRef } />
18
21
  </Grid>
19
22
  </Grid>
20
23
  </StylesField>
@@ -1,4 +1,5 @@
1
1
  import * as React from 'react';
2
+ import { type MutableRefObject, useRef } from 'react';
2
3
  import { SizeControl } from '@elementor/editor-controls';
3
4
  import { Grid } from '@elementor/ui';
4
5
  import { __ } from '@wordpress/i18n';
@@ -7,14 +8,16 @@ import { StylesField } from '../../../controls-registry/styles-field';
7
8
  import { ControlLabel } from '../../control-label';
8
9
 
9
10
  export const FontSizeField = () => {
11
+ const rowRef: MutableRefObject< HTMLElement | undefined > = useRef();
12
+
10
13
  return (
11
14
  <StylesField bind="font-size">
12
- <Grid container gap={ 2 } alignItems="center" flexWrap="nowrap">
15
+ <Grid container gap={ 2 } alignItems="center" flexWrap="nowrap" ref={ rowRef }>
13
16
  <Grid item xs={ 6 }>
14
17
  <ControlLabel>{ __( 'Font size', 'elementor' ) }</ControlLabel>
15
18
  </Grid>
16
19
  <Grid item xs={ 6 }>
17
- <SizeControl />
20
+ <SizeControl anchorRef={ rowRef } />
18
21
  </Grid>
19
22
  </Grid>
20
23
  </StylesField>
@@ -1,4 +1,5 @@
1
1
  import * as React from 'react';
2
+ import { type MutableRefObject, useRef } from 'react';
2
3
  import { SizeControl } from '@elementor/editor-controls';
3
4
  import { Grid } from '@elementor/ui';
4
5
  import { __ } from '@wordpress/i18n';
@@ -7,14 +8,16 @@ import { StylesField } from '../../../controls-registry/styles-field';
7
8
  import { ControlLabel } from '../../control-label';
8
9
 
9
10
  export const LetterSpacingField = () => {
11
+ const rowRef: MutableRefObject< HTMLElement | undefined > = useRef();
12
+
10
13
  return (
11
14
  <StylesField bind="letter-spacing">
12
- <Grid container gap={ 2 } alignItems="center" flexWrap="nowrap">
15
+ <Grid container gap={ 2 } alignItems="center" flexWrap="nowrap" ref={ rowRef }>
13
16
  <Grid item xs={ 6 }>
14
17
  <ControlLabel>{ __( 'Letter spacing', 'elementor' ) }</ControlLabel>
15
18
  </Grid>
16
19
  <Grid item xs={ 6 }>
17
- <SizeControl />
20
+ <SizeControl anchorRef={ rowRef } />
18
21
  </Grid>
19
22
  </Grid>
20
23
  </StylesField>
@@ -1,4 +1,5 @@
1
1
  import * as React from 'react';
2
+ import { type MutableRefObject, useRef } from 'react';
2
3
  import { SizeControl } from '@elementor/editor-controls';
3
4
  import { Grid } from '@elementor/ui';
4
5
  import { __ } from '@wordpress/i18n';
@@ -7,14 +8,16 @@ import { StylesField } from '../../../controls-registry/styles-field';
7
8
  import { ControlLabel } from '../../control-label';
8
9
 
9
10
  export const LineHeightField = () => {
11
+ const rowRef: MutableRefObject< HTMLElement | undefined > = useRef();
12
+
10
13
  return (
11
14
  <StylesField bind="line-height">
12
- <Grid container gap={ 2 } alignItems="center" flexWrap="nowrap">
15
+ <Grid container gap={ 2 } alignItems="center" flexWrap="nowrap" ref={ rowRef }>
13
16
  <Grid item xs={ 6 }>
14
17
  <ControlLabel>{ __( 'Line height', 'elementor' ) }</ControlLabel>
15
18
  </Grid>
16
19
  <Grid item xs={ 6 }>
17
- <SizeControl />
20
+ <SizeControl anchorRef={ rowRef } />
18
21
  </Grid>
19
22
  </Grid>
20
23
  </StylesField>
@@ -5,6 +5,7 @@ import { Grid, withDirection } from '@elementor/ui';
5
5
  import { __ } from '@wordpress/i18n';
6
6
 
7
7
  import { StylesField } from '../../../controls-registry/styles-field';
8
+ import { UiProviders } from '../../../styles-inheritance/components/ui-providers';
8
9
  import { ControlLabel } from '../../control-label';
9
10
 
10
11
  type Alignments = 'start' | 'center' | 'end' | 'justify';
@@ -41,15 +42,17 @@ const options: ToggleButtonGroupItem< Alignments >[] = [
41
42
 
42
43
  export const TextAlignmentField = () => {
43
44
  return (
44
- <StylesField bind={ 'text-align' }>
45
- <Grid container gap={ 2 } alignItems="center" flexWrap="nowrap">
46
- <Grid item xs={ 6 }>
47
- <ControlLabel>{ __( 'Text align', 'elementor' ) }</ControlLabel>
45
+ <UiProviders>
46
+ <StylesField bind={ 'text-align' }>
47
+ <Grid container gap={ 2 } alignItems="center" flexWrap="nowrap">
48
+ <Grid item xs={ 6 }>
49
+ <ControlLabel>{ __( 'Text align', 'elementor' ) }</ControlLabel>
50
+ </Grid>
51
+ <Grid item xs={ 6 } display="flex" justifyContent="end">
52
+ <ToggleControl options={ options } />
53
+ </Grid>
48
54
  </Grid>
49
- <Grid item xs={ 6 } display="flex" justifyContent="end">
50
- <ToggleControl options={ options } />
51
- </Grid>
52
- </Grid>
53
- </StylesField>
55
+ </StylesField>
56
+ </UiProviders>
54
57
  );
55
58
  };
@@ -2,7 +2,6 @@ import * as React from 'react';
2
2
  import { StrokeControl } from '@elementor/editor-controls';
3
3
  import { __ } from '@wordpress/i18n';
4
4
 
5
- import { useStyle } from '../../../contexts/style-context';
6
5
  import { StylesField } from '../../../controls-registry/styles-field';
7
6
  import { useStylesField } from '../../../hooks/use-styles-field';
8
7
  import { AddOrRemoveContent } from '../../add-or-remove-content';
@@ -26,18 +25,17 @@ const initTextStroke = {
26
25
  };
27
26
 
28
27
  export const TextStrokeField = () => {
29
- const { canEdit } = useStyle();
30
- const [ textStroke, setTextStroke ] = useStylesField( 'stroke' );
28
+ const { value, setValue, canEdit } = useStylesField( 'stroke' );
31
29
 
32
30
  const addTextStroke = () => {
33
- setTextStroke( initTextStroke );
31
+ setValue( initTextStroke );
34
32
  };
35
33
 
36
34
  const removeTextStroke = () => {
37
- setTextStroke( null );
35
+ setValue( null );
38
36
  };
39
37
 
40
- const hasTextStroke = Boolean( textStroke );
38
+ const hasTextStroke = Boolean( value );
41
39
 
42
40
  return (
43
41
  <StylesField bind={ 'stroke' }>
@@ -23,9 +23,11 @@ import { TransformField } from './transform-field';
23
23
  import { WordSpacingField } from './word-spacing-field';
24
24
 
25
25
  export const TypographySection = () => {
26
- const [ columnCount ] = useStylesField< NumberPropValue >( 'column-count' );
27
- const isVersion330Active = isExperimentActive( 'e_v_3_30' );
26
+ const { value: columnCount } = useStylesField< NumberPropValue >( 'column-count' );
28
27
  const hasMultiColumns = !! ( columnCount?.value && columnCount?.value > 1 );
28
+
29
+ const isVersion330Active = isExperimentActive( 'e_v_3_30' );
30
+
29
31
  return (
30
32
  <SectionContent>
31
33
  <FontFamilyField />
@@ -1,4 +1,5 @@
1
1
  import * as React from 'react';
2
+ import { type MutableRefObject, useRef } from 'react';
2
3
  import { SizeControl } from '@elementor/editor-controls';
3
4
  import { Grid } from '@elementor/ui';
4
5
  import { __ } from '@wordpress/i18n';
@@ -7,14 +8,16 @@ import { StylesField } from '../../../controls-registry/styles-field';
7
8
  import { ControlLabel } from '../../control-label';
8
9
 
9
10
  export const WordSpacingField = () => {
11
+ const rowRef: MutableRefObject< HTMLElement | undefined > = useRef();
12
+
10
13
  return (
11
14
  <StylesField bind="word-spacing">
12
- <Grid container gap={ 2 } alignItems="center" flexWrap="nowrap">
15
+ <Grid container gap={ 2 } alignItems="center" flexWrap="nowrap" ref={ rowRef }>
13
16
  <Grid item xs={ 6 }>
14
17
  <ControlLabel>{ __( 'Word spacing', 'elementor' ) }</ControlLabel>
15
18
  </Grid>
16
19
  <Grid item xs={ 6 }>
17
- <SizeControl />
20
+ <SizeControl anchorRef={ rowRef } />
18
21
  </Grid>
19
22
  </Grid>
20
23
  </StylesField>
@@ -1,7 +1,9 @@
1
1
  import {
2
2
  type ControlComponent,
3
3
  ImageControl,
4
+ KeyValueControl,
4
5
  LinkControl,
6
+ RepeatableControl,
5
7
  SelectControl,
6
8
  SizeControl,
7
9
  SvgMediaControl,
@@ -11,19 +13,35 @@ import {
11
13
  UrlControl,
12
14
  } from '@elementor/editor-controls';
13
15
  import { type ControlLayout } from '@elementor/editor-elements';
16
+ import {
17
+ booleanPropTypeUtil,
18
+ imagePropTypeUtil,
19
+ imageSrcPropTypeUtil,
20
+ keyValuePropTypeUtil,
21
+ linkPropTypeUtil,
22
+ type PropTypeUtil,
23
+ sizePropTypeUtil,
24
+ stringPropTypeUtil,
25
+ } from '@elementor/editor-props';
14
26
 
15
- type ControlRegistry = Record< string, { component: ControlComponent; layout: ControlLayout } >;
27
+ type ControlRegistry = Record<
28
+ string,
29
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
30
+ { component: ControlComponent; layout: ControlLayout; propTypeUtil?: PropTypeUtil< string, any > }
31
+ >;
16
32
 
17
33
  const controlTypes = {
18
- image: { component: ImageControl, layout: 'full' },
19
- 'svg-media': { component: SvgMediaControl, layout: 'full' },
20
- text: { component: TextControl, layout: 'full' },
21
- textarea: { component: TextAreaControl, layout: 'full' },
22
- size: { component: SizeControl, layout: 'two-columns' },
23
- select: { component: SelectControl, layout: 'two-columns' },
24
- link: { component: LinkControl, layout: 'full' },
25
- url: { component: UrlControl, layout: 'full' },
26
- switch: { component: SwitchControl, layout: 'two-columns' },
34
+ image: { component: ImageControl, layout: 'full', propTypeUtil: imagePropTypeUtil },
35
+ 'svg-media': { component: SvgMediaControl, layout: 'full', propTypeUtil: imageSrcPropTypeUtil },
36
+ text: { component: TextControl, layout: 'full', propTypeUtil: stringPropTypeUtil },
37
+ textarea: { component: TextAreaControl, layout: 'full', propTypeUtil: stringPropTypeUtil },
38
+ size: { component: SizeControl, layout: 'two-columns', propTypeUtil: sizePropTypeUtil },
39
+ select: { component: SelectControl, layout: 'two-columns', propTypeUtil: stringPropTypeUtil },
40
+ link: { component: LinkControl, layout: 'full', propTypeUtil: linkPropTypeUtil },
41
+ url: { component: UrlControl, layout: 'full', propTypeUtil: stringPropTypeUtil },
42
+ switch: { component: SwitchControl, layout: 'two-columns', propTypeUtil: booleanPropTypeUtil },
43
+ repeatable: { component: RepeatableControl, layout: 'full', propTypeUtil: undefined },
44
+ 'key-value': { component: KeyValueControl, layout: 'full', propTypeUtil: keyValuePropTypeUtil },
27
45
  } as const satisfies ControlRegistry;
28
46
 
29
47
  export type ControlType = keyof typeof controlTypes;
@@ -35,3 +53,5 @@ export type ControlTypes = {
35
53
  export const getControl = ( type: ControlType ) => controlTypes[ type ]?.component;
36
54
 
37
55
  export const getDefaultLayout = ( type: ControlType ) => controlTypes[ type ].layout;
56
+
57
+ export const getPropTypeUtil = ( type: ControlType ) => controlTypes[ type ]?.propTypeUtil;
@@ -3,7 +3,6 @@ import { ControlAdornmentsProvider, PropKeyProvider, PropProvider } from '@eleme
3
3
  import { type PropKey, type PropValue } from '@elementor/editor-props';
4
4
  import { getStylesSchema } from '@elementor/editor-styles';
5
5
 
6
- import { useStyle } from '../contexts/style-context';
7
6
  import { useStylesField } from '../hooks/use-styles-field';
8
7
  import { StylesInheritanceIndicator } from '../styles-inheritance/components/styles-inheritance-indicator';
9
8
  import { createTopLevelOjectType } from './create-top-level-object-type';
@@ -15,8 +14,7 @@ export type StylesFieldProps = {
15
14
  };
16
15
 
17
16
  export const StylesField = ( { bind, placeholder, children }: StylesFieldProps ) => {
18
- const [ value, setValue ] = useStylesField( bind );
19
- const { canEdit } = useStyle();
17
+ const { value, setValue, canEdit } = useStylesField( bind );
20
18
 
21
19
  const stylesSchema = getStylesSchema();
22
20
 
@@ -0,0 +1,48 @@
1
+ import * as React from 'react';
2
+ import { PropKeyProvider, PropProvider, useBoundProp } from '@elementor/editor-controls';
3
+ import {
4
+ backgroundImageOverlayPropTypeUtil,
5
+ type BackgroundOverlayImagePropType,
6
+ type BackgroundOverlayPropType,
7
+ type ObjectPropType,
8
+ type UnionPropType,
9
+ } from '@elementor/editor-props';
10
+ import { DatabaseIcon } from '@elementor/icons';
11
+
12
+ import { useDynamicTag } from '../hooks/use-dynamic-tag';
13
+
14
+ // Since this is injected, the initial prop provider does not dig into the nested structure of the value.
15
+ // We need to synthetically create a type that matches the expected structure of the value.
16
+
17
+ export const BackgroundControlDynamicTagIcon = () => <DatabaseIcon fontSize="tiny" />;
18
+
19
+ export const BackgroundControlDynamicTagLabel = ( { value }: { value: BackgroundOverlayPropType } ) => {
20
+ const context = useBoundProp( backgroundImageOverlayPropTypeUtil );
21
+
22
+ return (
23
+ <PropProvider { ...context } value={ value.value }>
24
+ <PropKeyProvider bind="image">
25
+ <Wrapper rawValue={ value.value } />
26
+ </PropKeyProvider>
27
+ </PropProvider>
28
+ );
29
+ };
30
+
31
+ const Wrapper = ( { rawValue }: { rawValue: BackgroundOverlayPropType[ 'value' ] } ) => {
32
+ const { propType } = useBoundProp< BackgroundOverlayPropType, UnionPropType >();
33
+
34
+ const imageOverlayPropType = propType.prop_types[ 'background-image-overlay' ] as ObjectPropType;
35
+ return (
36
+ <PropProvider propType={ imageOverlayPropType.shape.image } value={ rawValue } setValue={ () => void 0 }>
37
+ <PropKeyProvider bind="src">
38
+ <Content rawValue={ rawValue.image } />
39
+ </PropKeyProvider>
40
+ </PropProvider>
41
+ );
42
+ };
43
+
44
+ const Content = ( { rawValue }: { rawValue: BackgroundOverlayImagePropType } ) => {
45
+ const src = rawValue.value.src;
46
+ const dynamicTag = useDynamicTag( src.value.name || '' );
47
+ return <React.Fragment>{ dynamicTag?.label }</React.Fragment>;
48
+ };
@@ -1,7 +1,7 @@
1
1
  import * as React from 'react';
2
2
  import { ControlFormLabel, useBoundProp } from '@elementor/editor-controls';
3
3
  import type { Control, ControlsSection } from '@elementor/editor-elements';
4
- import { PopoverHeader } from '@elementor/editor-ui';
4
+ import { PopoverHeader, PopoverScrollableContent } from '@elementor/editor-ui';
5
5
  import { DatabaseIcon, SettingsIcon, XIcon } from '@elementor/icons';
6
6
  import {
7
7
  bindPopover,
@@ -10,7 +10,6 @@ import {
10
10
  Divider,
11
11
  Grid,
12
12
  IconButton,
13
- Paper,
14
13
  Popover,
15
14
  Stack,
16
15
  Tab,
@@ -81,12 +80,7 @@ export const DynamicSelectionControl = () => {
81
80
  { ...bindPopover( selectionPopoverState ) }
82
81
  >
83
82
  <Stack>
84
- <PopoverHeader
85
- title={ __( 'Dynamic tags', 'elementor' ) }
86
- onClose={ selectionPopoverState.close }
87
- icon={ <DatabaseIcon fontSize={ SIZE } /> }
88
- />
89
- <DynamicSelection onSelect={ selectionPopoverState.close } />
83
+ <DynamicSelection close={ selectionPopoverState.close } />
90
84
  </Stack>
91
85
  </Popover>
92
86
  </Box>
@@ -113,14 +107,12 @@ export const DynamicSettingsPopover = ( { dynamicTag }: { dynamicTag: DynamicTag
113
107
  anchorOrigin={ { vertical: 'bottom', horizontal: 'center' } }
114
108
  { ...bindPopover( popupState ) }
115
109
  >
116
- <Paper component={ Stack } sx={ { minHeight: '300px', width: '220px' } }>
117
- <PopoverHeader
118
- title={ dynamicTag.label }
119
- onClose={ popupState.close }
120
- icon={ <DatabaseIcon fontSize={ SIZE } /> }
121
- />
122
- <DynamicSettings controls={ dynamicTag.atomic_controls } />
123
- </Paper>
110
+ <PopoverHeader
111
+ title={ dynamicTag.label }
112
+ onClose={ popupState.close }
113
+ icon={ <DatabaseIcon fontSize={ SIZE } /> }
114
+ />
115
+ <DynamicSettings controls={ dynamicTag.atomic_controls } />
124
116
  </Popover>
125
117
  </>
126
118
  );
@@ -136,7 +128,7 @@ const DynamicSettings = ( { controls }: { controls: DynamicTag[ 'atomic_controls
136
128
  }
137
129
 
138
130
  return (
139
- <>
131
+ <PopoverScrollableContent>
140
132
  <Tabs size="small" variant="fullWidth" { ...getTabsProps() }>
141
133
  { tabs.map( ( { value }, index ) => (
142
134
  <Tab key={ index } label={ value.label } sx={ { px: 1, py: 0.5 } } { ...getTabProps( index ) } />
@@ -158,7 +150,7 @@ const DynamicSettings = ( { controls }: { controls: DynamicTag[ 'atomic_controls
158
150
  </TabPanel>
159
151
  );
160
152
  } ) }
161
- </>
153
+ </PopoverScrollableContent>
162
154
  );
163
155
  };
164
156
 
@@ -1,19 +1,9 @@
1
- import * as React from 'react';
2
1
  import { Fragment, useState } from 'react';
2
+ import * as React from 'react';
3
3
  import { useBoundProp } from '@elementor/editor-controls';
4
- import { DatabaseIcon, SearchIcon } from '@elementor/icons';
5
- import {
6
- Box,
7
- Divider,
8
- InputAdornment,
9
- Link,
10
- MenuItem,
11
- MenuList,
12
- MenuSubheader,
13
- Stack,
14
- TextField,
15
- Typography,
16
- } from '@elementor/ui';
4
+ import { PopoverHeader, PopoverMenuList, PopoverSearch } from '@elementor/editor-ui';
5
+ import { DatabaseIcon } from '@elementor/icons';
6
+ import { Box, Divider, Link, Stack, Typography, useTheme } from '@elementor/ui';
17
7
  import { __ } from '@wordpress/i18n';
18
8
 
19
9
  import { usePersistDynamicValue } from '../../hooks/use-persist-dynamic-value';
@@ -31,7 +21,7 @@ type OptionEntry = [ string, Option[] ];
31
21
  const SIZE = 'tiny';
32
22
 
33
23
  type DynamicSelectionProps = {
34
- onSelect?: () => void;
24
+ close: () => void;
35
25
  };
36
26
 
37
27
  type NoResultsProps = {
@@ -39,9 +29,10 @@ type NoResultsProps = {
39
29
  onClear?: () => void;
40
30
  };
41
31
 
42
- export const DynamicSelection = ( { onSelect }: DynamicSelectionProps ) => {
32
+ export const DynamicSelection = ( { close: closePopover }: DynamicSelectionProps ) => {
43
33
  const [ searchValue, setSearchValue ] = useState( '' );
44
34
  const { groups: dynamicGroups } = getAtomicDynamicTags() || {};
35
+ const theme = useTheme();
45
36
 
46
37
  const { value: anyValue } = useBoundProp();
47
38
  const { bind, value: dynamicValue, setValue } = useBoundProp( dynamicPropTypeUtil );
@@ -54,79 +45,69 @@ export const DynamicSelection = ( { onSelect }: DynamicSelectionProps ) => {
54
45
 
55
46
  const hasNoDynamicTags = ! options.length && ! searchValue.trim();
56
47
 
57
- const handleSearch = ( event: React.ChangeEvent< HTMLInputElement > ) => {
58
- setSearchValue( event.target.value );
48
+ const handleSearch = ( value: string ) => {
49
+ setSearchValue( value );
59
50
  };
60
51
 
61
- const handleSetDynamicTag = ( value: string, label: string ) => {
52
+ const handleSetDynamicTag = ( value: string ) => {
62
53
  if ( ! isCurrentValueDynamic ) {
63
54
  updatePropValueHistory( anyValue );
64
55
  }
65
56
 
66
- setValue( { name: value, settings: { label } } );
57
+ const selectedOption = options.flatMap( ( [ , items ] ) => items ).find( ( item ) => item.value === value );
58
+
59
+ setValue( { name: value, settings: { label: selectedOption?.label } } );
67
60
 
68
- onSelect?.();
61
+ closePopover();
69
62
  };
70
63
 
64
+ const virtualizedItems = options.flatMap( ( [ category, items ] ) => [
65
+ {
66
+ type: 'category' as const,
67
+ value: category,
68
+ label: dynamicGroups?.[ category ]?.title || category,
69
+ },
70
+ ...items.map( ( item ) => ( {
71
+ type: 'item' as const,
72
+ value: item.value,
73
+ label: item.label,
74
+ } ) ),
75
+ ] );
76
+
71
77
  return (
72
- <Stack>
73
- { hasNoDynamicTags ? (
74
- <NoDynamicTags />
75
- ) : (
76
- <Fragment>
77
- <Box px={ 1.5 } pb={ 1 }>
78
- <TextField
79
- fullWidth
80
- size={ SIZE }
78
+ <>
79
+ <PopoverHeader
80
+ title={ __( 'Dynamic tags', 'elementor' ) }
81
+ onClose={ closePopover }
82
+ icon={ <DatabaseIcon fontSize={ SIZE } /> }
83
+ />
84
+ <Stack>
85
+ { hasNoDynamicTags ? (
86
+ <NoDynamicTags />
87
+ ) : (
88
+ <Fragment>
89
+ <PopoverSearch
81
90
  value={ searchValue }
82
- onChange={ handleSearch }
91
+ onSearch={ handleSearch }
83
92
  placeholder={ __( 'Search dynamic tags…', 'elementor' ) }
84
- InputProps={ {
85
- startAdornment: (
86
- <InputAdornment position="start">
87
- <SearchIcon fontSize={ SIZE } />
88
- </InputAdornment>
89
- ),
90
- } }
91
93
  />
92
- </Box>
93
- <Divider />
94
- <Box sx={ { overflowY: 'auto', height: 260, width: 220 } }>
95
- { options.length > 0 ? (
96
- <MenuList role="listbox" tabIndex={ 0 }>
97
- { options.map( ( [ category, items ], index ) => (
98
- <Fragment key={ index }>
99
- <MenuSubheader
100
- sx={ { px: 1.5, typography: 'caption', color: 'text.tertiary' } }
101
- >
102
- { dynamicGroups?.[ category ]?.title || category }
103
- </MenuSubheader>
104
- { items.map( ( { value, label: tagLabel } ) => {
105
- const isSelected = isCurrentValueDynamic && value === dynamicValue?.name;
106
-
107
- return (
108
- <MenuItem
109
- key={ value }
110
- selected={ isSelected }
111
- // eslint-disable-next-line jsx-a11y/no-autofocus
112
- autoFocus={ isSelected }
113
- sx={ { px: 3.5, typography: 'caption' } }
114
- onClick={ () => handleSetDynamicTag( value, tagLabel ) }
115
- >
116
- { tagLabel }
117
- </MenuItem>
118
- );
119
- } ) }
120
- </Fragment>
121
- ) ) }
122
- </MenuList>
123
- ) : (
124
- <NoResults searchValue={ searchValue } onClear={ () => setSearchValue( '' ) } />
125
- ) }
126
- </Box>
127
- </Fragment>
128
- ) }
129
- </Stack>
94
+ <Divider />
95
+ <PopoverMenuList
96
+ items={ virtualizedItems }
97
+ onSelect={ handleSetDynamicTag }
98
+ onClose={ closePopover }
99
+ selectedValue={ dynamicValue?.name }
100
+ itemStyle={ ( item ) =>
101
+ item.type === 'item' ? { paddingInlineStart: theme.spacing( 3.5 ) } : {}
102
+ }
103
+ noResultsComponent={
104
+ <NoResults searchValue={ searchValue } onClear={ () => setSearchValue( '' ) } />
105
+ }
106
+ />
107
+ </Fragment>
108
+ ) }
109
+ </Stack>
110
+ </>
130
111
  );
131
112
  };
132
113
 
@@ -173,7 +154,7 @@ const NoDynamicTags = () => (
173
154
  { __( 'Streamline your workflow with dynamic tags', 'elementor' ) }
174
155
  </Typography>
175
156
  <Typography align="center" variant="caption">
176
- { __( 'You’ll need Elementor Pro to use this feature.', 'elementor' ) }
157
+ { __( "You'll need Elementor Pro to use this feature.", 'elementor' ) }
177
158
  </Typography>
178
159
  </Stack>
179
160
  </Box>
@@ -16,6 +16,6 @@ export const usePropDynamicAction = (): PopoverActionProps => {
16
16
  visible,
17
17
  icon: DatabaseIcon,
18
18
  title: __( 'Dynamic tags', 'elementor' ),
19
- popoverContent: ( { closePopover } ) => <DynamicSelection onSelect={ closePopover } />,
19
+ content: ( { close } ) => <DynamicSelection close={ close } />,
20
20
  };
21
21
  };
@@ -1,7 +1,14 @@
1
1
  import { settingsTransformersRegistry, styleTransformersRegistry } from '@elementor/editor-canvas';
2
+ import { injectIntoRepeaterItemIcon, injectIntoRepeaterItemLabel } from '@elementor/editor-controls';
3
+ import { type BackgroundOverlayPropType, type PropValue } from '@elementor/editor-props';
4
+ import { type InjectedComponent } from '@elementor/locations';
2
5
 
3
6
  import { registerControlReplacement } from '../control-replacement';
4
7
  import { controlActionsMenu } from '../controls-actions';
8
+ import {
9
+ BackgroundControlDynamicTagIcon,
10
+ BackgroundControlDynamicTagLabel,
11
+ } from './components/background-control-dynamic-tag';
5
12
  import { DynamicSelectionControl } from './components/dynamic-selection-control';
6
13
  import { dynamicTransformer } from './dynamic-transformer';
7
14
  import { usePropDynamicAction } from './hooks/use-prop-dynamic-action';
@@ -15,6 +22,20 @@ export const init = () => {
15
22
  condition: ( { value } ) => isDynamicPropValue( value ),
16
23
  } );
17
24
 
25
+ injectIntoRepeaterItemLabel( {
26
+ id: 'dynamic-background-image',
27
+ condition: ( { value } ) =>
28
+ isDynamicPropValue( ( value as BackgroundOverlayPropType ).value?.image?.value?.src ),
29
+ component: BackgroundControlDynamicTagLabel as InjectedComponent< { value: PropValue } >,
30
+ } );
31
+
32
+ injectIntoRepeaterItemIcon( {
33
+ id: 'dynamic-background-image',
34
+ condition: ( { value } ) =>
35
+ isDynamicPropValue( ( value as BackgroundOverlayPropType ).value?.image?.value?.src ),
36
+ component: BackgroundControlDynamicTagIcon,
37
+ } );
38
+
18
39
  registerPopoverAction( {
19
40
  id: 'dynamic-tags',
20
41
  useProps: usePropDynamicAction,