@elementor/editor-editing-panel 0.17.0 → 0.19.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 (81) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/dist/index.d.mts +28 -10
  3. package/dist/index.d.ts +28 -10
  4. package/dist/index.js +941 -398
  5. package/dist/index.js.map +1 -1
  6. package/dist/index.mjs +918 -366
  7. package/dist/index.mjs.map +1 -1
  8. package/package.json +8 -7
  9. package/src/components/editing-panel-error-fallback.tsx +12 -0
  10. package/src/components/editing-panel.tsx +23 -12
  11. package/src/components/settings-tab.tsx +8 -5
  12. package/src/components/style-sections/background-section/background-color-control.tsx +20 -0
  13. package/src/components/style-sections/background-section/background-section.tsx +15 -0
  14. package/src/components/style-sections/effects-section/box-shadow-repeater.tsx +224 -0
  15. package/src/components/style-sections/effects-section/effects-section.tsx +18 -0
  16. package/src/components/style-sections/position-section/z-index-control.tsx +11 -7
  17. package/src/components/style-sections/size-section.tsx +23 -20
  18. package/src/components/style-sections/spacing-section/linked-dimensions-control.tsx +62 -47
  19. package/src/components/style-sections/typography-section/font-size-control.tsx +10 -6
  20. package/src/components/style-sections/typography-section/font-weight-control.tsx +16 -12
  21. package/src/components/style-sections/typography-section/letter-spacing-control.tsx +10 -6
  22. package/src/components/style-sections/typography-section/text-alignment-control.tsx +12 -8
  23. package/src/components/style-sections/typography-section/text-color-control.tsx +10 -6
  24. package/src/components/style-sections/typography-section/text-direction-control.tsx +37 -0
  25. package/src/components/style-sections/typography-section/text-style-control.tsx +37 -34
  26. package/src/components/style-sections/typography-section/transform-control.tsx +14 -12
  27. package/src/components/style-sections/typography-section/typography-section.tsx +2 -0
  28. package/src/components/style-sections/typography-section/word-spacing-control.tsx +10 -6
  29. package/src/components/style-tab.tsx +10 -4
  30. package/src/control-replacement.tsx +3 -0
  31. package/src/controls/components/control-type-container.tsx +28 -0
  32. package/src/controls/components/repeater.tsx +197 -0
  33. package/src/controls/components/text-field-inner-selection.tsx +2 -2
  34. package/src/controls/control-actions/actions/popover-action.tsx +58 -0
  35. package/src/controls/control-actions/control-actions-menu.ts +8 -0
  36. package/src/controls/control-actions/control-actions.tsx +43 -0
  37. package/src/controls/control-context.tsx +1 -1
  38. package/src/controls/control-replacement.ts +16 -8
  39. package/src/controls/control-types/color-control.tsx +21 -18
  40. package/src/controls/control-types/image-control.tsx +56 -59
  41. package/src/controls/control-types/image-media-control.tsx +73 -0
  42. package/src/controls/control-types/number-control.tsx +13 -9
  43. package/src/controls/control-types/select-control.tsx +14 -10
  44. package/src/controls/control-types/size-control.tsx +18 -14
  45. package/src/controls/control-types/text-area-control.tsx +15 -11
  46. package/src/controls/control-types/text-control.tsx +9 -3
  47. package/src/controls/control-types/toggle-control.tsx +4 -3
  48. package/src/controls/control.tsx +1 -7
  49. package/src/controls/controls-registry.tsx +19 -10
  50. package/src/controls/create-control-replacement.tsx +53 -0
  51. package/src/controls/create-control.tsx +40 -0
  52. package/src/controls/hooks/use-style-control.ts +3 -3
  53. package/src/{hooks → controls/hooks}/use-widget-settings.ts +1 -1
  54. package/src/{props → controls/props}/is-transformable.ts +1 -2
  55. package/src/controls/props/types.ts +51 -0
  56. package/src/{contexts/element-context.tsx → controls/providers/element-provider.tsx} +4 -4
  57. package/src/controls/settings-control.tsx +7 -14
  58. package/src/controls/style-control.tsx +1 -1
  59. package/src/{sync → controls/sync}/get-container.ts +1 -1
  60. package/src/{sync → controls/sync}/update-settings.ts +1 -1
  61. package/src/controls/types.ts +39 -0
  62. package/src/dynamics/components/dynamic-selection-control.tsx +2 -2
  63. package/src/dynamics/components/dynamic-selection.tsx +6 -6
  64. package/src/dynamics/dynamic-control.tsx +2 -2
  65. package/src/dynamics/hooks/use-dynamic-tag.ts +2 -2
  66. package/src/dynamics/hooks/use-prop-dynamic-action.tsx +23 -0
  67. package/src/dynamics/hooks/use-prop-dynamic-tags.ts +7 -7
  68. package/src/dynamics/hooks/use-prop-value-history.ts +3 -3
  69. package/src/dynamics/init.ts +10 -1
  70. package/src/dynamics/types.ts +7 -3
  71. package/src/dynamics/utils.ts +17 -4
  72. package/src/hooks/use-element-style-prop.ts +3 -2
  73. package/src/hooks/use-element-styles.ts +1 -1
  74. package/src/hooks/use-element-type.ts +1 -1
  75. package/src/index.ts +3 -1
  76. package/src/sync/get-element-styles.ts +2 -2
  77. package/src/sync/get-selected-elements.ts +1 -1
  78. package/src/sync/types.ts +2 -1
  79. package/src/sync/update-style.ts +3 -2
  80. package/src/controls/components/control-container.tsx +0 -18
  81. package/src/types.ts +0 -68
@@ -1,16 +1,20 @@
1
1
  import * as React from 'react';
2
+ import { __ } from '@wordpress/i18n';
3
+ import { Grid } from '@elementor/ui';
2
4
  import { StyleControl } from '../../../controls/style-control';
3
5
  import { SizeControl } from '../../../controls/control-types/size-control';
4
- import { ControlContainer } from '../../../controls/components/control-container';
5
- import { __ } from '@wordpress/i18n';
6
6
 
7
7
  export const FontSizeControl = () => {
8
8
  return (
9
9
  <StyleControl bind="font-size">
10
- <ControlContainer>
11
- <StyleControl.Label>{ __( 'Font Size', 'elementor' ) }</StyleControl.Label>
12
- <SizeControl />
13
- </ControlContainer>
10
+ <Grid container spacing={ 1 } alignItems="center">
11
+ <Grid item xs={ 6 }>
12
+ <StyleControl.Label>{ __( 'Font Size', 'elementor' ) }</StyleControl.Label>
13
+ </Grid>
14
+ <Grid item xs={ 6 }>
15
+ <SizeControl />
16
+ </Grid>
17
+ </Grid>
14
18
  </StyleControl>
15
19
  );
16
20
  };
@@ -1,24 +1,28 @@
1
1
  import * as React from 'react';
2
+ import { __ } from '@wordpress/i18n';
3
+ import { Grid } from '@elementor/ui';
2
4
  import { StyleControl } from '../../../controls/style-control';
3
5
  import { SelectControl } from '../../../controls/control-types/select-control';
4
- import { ControlContainer } from '../../../controls/components/control-container';
5
- import { __ } from '@wordpress/i18n';
6
6
 
7
7
  const fontWeightOptions = [
8
- { label: __( 'Light - 400', 'elementor' ), value: 400 },
9
- { label: __( 'Regular - 500', 'elementor' ), value: 500 },
10
- { label: __( 'Semi Bold - 600', 'elementor' ), value: 600 },
11
- { label: __( 'Bold - 700', 'elementor' ), value: 700 },
12
- { label: __( 'Black - 900', 'elementor' ), value: 900 },
8
+ { label: __( 'Light - 400', 'elementor' ), value: '400' },
9
+ { label: __( 'Regular - 500', 'elementor' ), value: '500' },
10
+ { label: __( 'Semi Bold - 600', 'elementor' ), value: '600' },
11
+ { label: __( 'Bold - 700', 'elementor' ), value: '700' },
12
+ { label: __( 'Black - 900', 'elementor' ), value: '900' },
13
13
  ];
14
14
 
15
15
  export const FontWeightControl = () => {
16
16
  return (
17
- <StyleControl bind="fontWeight">
18
- <ControlContainer>
19
- <StyleControl.Label>{ __( 'Font Weight', 'elementor' ) }</StyleControl.Label>
20
- <SelectControl options={ fontWeightOptions } />
21
- </ControlContainer>
17
+ <StyleControl bind="font-weight">
18
+ <Grid container spacing={ 1 } alignItems="center">
19
+ <Grid item xs={ 6 }>
20
+ <StyleControl.Label>{ __( 'Font Weight', 'elementor' ) }</StyleControl.Label>
21
+ </Grid>
22
+ <Grid item xs={ 6 }>
23
+ <SelectControl options={ fontWeightOptions } />
24
+ </Grid>
25
+ </Grid>
22
26
  </StyleControl>
23
27
  );
24
28
  };
@@ -1,16 +1,20 @@
1
1
  import * as React from 'react';
2
+ import { __ } from '@wordpress/i18n';
3
+ import { Grid } from '@elementor/ui';
2
4
  import { StyleControl } from '../../../controls/style-control';
3
5
  import { SizeControl } from '../../../controls/control-types/size-control';
4
- import { ControlContainer } from '../../../controls/components/control-container';
5
- import { __ } from '@wordpress/i18n';
6
6
 
7
7
  export const LetterSpacingControl = () => {
8
8
  return (
9
9
  <StyleControl bind="letter-spacing">
10
- <ControlContainer>
11
- <StyleControl.Label>{ __( 'Letter Spacing', 'elementor' ) }</StyleControl.Label>
12
- <SizeControl />
13
- </ControlContainer>
10
+ <Grid container spacing={ 1 } alignItems="center">
11
+ <Grid item xs={ 6 }>
12
+ <StyleControl.Label>{ __( 'Letter Spacing', 'elementor' ) }</StyleControl.Label>
13
+ </Grid>
14
+ <Grid item xs={ 6 }>
15
+ <SizeControl />
16
+ </Grid>
17
+ </Grid>
14
18
  </StyleControl>
15
19
  );
16
20
  };
@@ -1,7 +1,7 @@
1
1
  import * as React from 'react';
2
- import { AlignLeftIcon, AlignCenterIcon, AlignRightIcon, AlignJustifiedIcon } from '@elementor/icons';
3
2
  import { __ } from '@wordpress/i18n';
4
- import { ControlContainer } from '../../../controls/components/control-container';
3
+ import { Grid } from '@elementor/ui';
4
+ import { AlignLeftIcon, AlignCenterIcon, AlignRightIcon, AlignJustifiedIcon } from '@elementor/icons';
5
5
  import { ToggleButtonGroupItem } from '../../../controls/components/control-toggle-button-group';
6
6
  import { StyleControl } from '../../../controls/style-control';
7
7
  import { ToggleControl } from '../../../controls/control-types/toggle-control';
@@ -33,11 +33,15 @@ const options: ToggleButtonGroupItem< Alignments >[] = [
33
33
 
34
34
  export const TextAlignmentControl = () => {
35
35
  return (
36
- <ControlContainer>
37
- <StyleControl.Label>{ __( 'Alignment', 'elementor' ) }</StyleControl.Label>
38
- <StyleControl bind={ 'text-align' }>
39
- <ToggleControl options={ options } />
40
- </StyleControl>
41
- </ControlContainer>
36
+ <StyleControl bind={ 'text-align' }>
37
+ <Grid container spacing={ 1 } alignItems="center">
38
+ <Grid item xs={ 6 }>
39
+ <StyleControl.Label>{ __( 'Alignment', 'elementor' ) }</StyleControl.Label>
40
+ </Grid>
41
+ <Grid item xs={ 6 } display="flex" justifyContent="end">
42
+ <ToggleControl options={ options } />
43
+ </Grid>
44
+ </Grid>
45
+ </StyleControl>
42
46
  );
43
47
  };
@@ -1,16 +1,20 @@
1
1
  import * as React from 'react';
2
- import { StyleControl } from '../../../controls/style-control';
3
- import { ControlContainer } from '../../../controls/components/control-container';
4
2
  import { __ } from '@wordpress/i18n';
3
+ import { Grid } from '@elementor/ui';
4
+ import { StyleControl } from '../../../controls/style-control';
5
5
  import { ColorControl } from '../../../controls/control-types/color-control';
6
6
 
7
7
  export const TextColorControl = () => {
8
8
  return (
9
9
  <StyleControl bind="color">
10
- <ControlContainer>
11
- <StyleControl.Label>{ __( 'Text Color', 'elementor' ) }</StyleControl.Label>
12
- <ColorControl />
13
- </ControlContainer>
10
+ <Grid container spacing={ 1 } alignItems="center">
11
+ <Grid item xs={ 6 }>
12
+ <StyleControl.Label>{ __( 'Text Color', 'elementor' ) }</StyleControl.Label>
13
+ </Grid>
14
+ <Grid item xs={ 6 }>
15
+ <ColorControl />
16
+ </Grid>
17
+ </Grid>
14
18
  </StyleControl>
15
19
  );
16
20
  };
@@ -0,0 +1,37 @@
1
+ import * as React from 'react';
2
+ import { __ } from '@wordpress/i18n';
3
+ import { Grid } from '@elementor/ui';
4
+ import { TextDirectionLtrIcon, TextDirectionRtlIcon } from '@elementor/icons';
5
+ import { ToggleButtonGroupItem } from '../../../controls/components/control-toggle-button-group';
6
+ import { StyleControl } from '../../../controls/style-control';
7
+ import { ToggleControl } from '../../../controls/control-types/toggle-control';
8
+
9
+ type Direction = 'ltr' | 'rtl';
10
+
11
+ const options: ToggleButtonGroupItem< Direction >[] = [
12
+ {
13
+ value: 'ltr',
14
+ label: __( 'Left to Right', 'elementor' ),
15
+ icon: TextDirectionLtrIcon,
16
+ },
17
+ {
18
+ value: 'rtl',
19
+ label: __( 'Right to Left', 'elementor' ),
20
+ icon: TextDirectionRtlIcon,
21
+ },
22
+ ];
23
+
24
+ export const TextDirectionControl = () => {
25
+ return (
26
+ <StyleControl bind={ 'direction' }>
27
+ <Grid container spacing={ 1 } alignItems="center">
28
+ <Grid item xs={ 6 }>
29
+ <StyleControl.Label>{ __( 'Direction', 'elementor' ) }</StyleControl.Label>
30
+ </Grid>
31
+ <Grid item xs={ 6 } display="flex" justifyContent="end">
32
+ <ToggleControl options={ options } />
33
+ </Grid>
34
+ </Grid>
35
+ </StyleControl>
36
+ );
37
+ };
@@ -1,49 +1,52 @@
1
1
  import * as React from 'react';
2
- import { ToggleButton as ToggleButtonBase, ToggleButtonGroup, ToggleButtonProps } from '@elementor/ui';
2
+ import { __ } from '@wordpress/i18n';
3
+ import { Grid, ToggleButton as ToggleButtonBase, ToggleButtonGroup, ToggleButtonProps } from '@elementor/ui';
3
4
  import { ItalicIcon, StrikethroughIcon, UnderlineIcon } from '@elementor/icons';
4
5
  import { ControlLabel } from '../../control-label';
5
6
  import { useStyleControl } from '../../../controls/hooks/use-style-control';
6
- import { __ } from '@wordpress/i18n';
7
- import { ControlContainer } from '../../../controls/components/control-container';
8
7
 
9
8
  const buttonSize = 'tiny';
10
9
 
11
10
  export const TextStyleControl = () => {
12
- const [ fontStyle, setFontStyle ] = useStyleControl< string | null >( 'fontStyle' );
13
- const [ textDecoration, setTextDecoration ] = useStyleControl< string | null >( 'textDecoration' );
11
+ const [ fontStyle, setFontStyle ] = useStyleControl< string | null >( 'font-style' );
12
+ const [ textDecoration, setTextDecoration ] = useStyleControl< string | null >( 'text-decoration' );
14
13
 
15
14
  const formats = [ fontStyle, ...( textDecoration || '' ).split( ' ' ) ];
16
15
 
17
16
  return (
18
- <ControlContainer>
19
- <ControlLabel>{ __( 'Style', 'elementor' ) }</ControlLabel>
20
- <ToggleButtonGroup value={ formats }>
21
- <ToggleButton
22
- value="italic"
23
- onChange={ ( v ) => setFontStyle( fontStyle === v ? null : v ) }
24
- aria-label="italic"
25
- sx={ { marginLeft: 'auto' } }
26
- >
27
- <ItalicIcon fontSize={ buttonSize } />
28
- </ToggleButton>
29
- <ShorthandControl
30
- value="line-through"
31
- currentValues={ textDecoration || '' }
32
- updateValues={ setTextDecoration }
33
- aria-label="line-through"
34
- >
35
- <StrikethroughIcon fontSize={ buttonSize } />
36
- </ShorthandControl>
37
- <ShorthandControl
38
- value="underline"
39
- currentValues={ textDecoration || '' }
40
- updateValues={ setTextDecoration }
41
- aria-label="underline"
42
- >
43
- <UnderlineIcon fontSize={ buttonSize } />
44
- </ShorthandControl>
45
- </ToggleButtonGroup>
46
- </ControlContainer>
17
+ <Grid container spacing={ 1 } alignItems="center">
18
+ <Grid item xs={ 6 }>
19
+ <ControlLabel>{ __( 'Style', 'elementor' ) }</ControlLabel>
20
+ </Grid>
21
+ <Grid item xs={ 6 } display="flex" justifyContent="end">
22
+ <ToggleButtonGroup value={ formats }>
23
+ <ToggleButton
24
+ value="italic"
25
+ onChange={ ( v ) => setFontStyle( fontStyle === v ? null : v ) }
26
+ aria-label="italic"
27
+ sx={ { marginLeft: 'auto' } }
28
+ >
29
+ <ItalicIcon fontSize={ buttonSize } />
30
+ </ToggleButton>
31
+ <ShorthandControl
32
+ value="line-through"
33
+ currentValues={ textDecoration || '' }
34
+ updateValues={ setTextDecoration }
35
+ aria-label="line-through"
36
+ >
37
+ <StrikethroughIcon fontSize={ buttonSize } />
38
+ </ShorthandControl>
39
+ <ShorthandControl
40
+ value="underline"
41
+ currentValues={ textDecoration || '' }
42
+ updateValues={ setTextDecoration }
43
+ aria-label="underline"
44
+ >
45
+ <UnderlineIcon fontSize={ buttonSize } />
46
+ </ShorthandControl>
47
+ </ToggleButtonGroup>
48
+ </Grid>
49
+ </Grid>
47
50
  );
48
51
  };
49
52
 
@@ -1,9 +1,9 @@
1
1
  import * as React from 'react';
2
- import { ControlContainer } from '../../../controls/components/control-container';
3
- import { StyleControl } from '../../../controls/style-control';
4
2
  import { __ } from '@wordpress/i18n';
5
- import { ToggleControl } from '../../../controls/control-types/toggle-control';
3
+ import { Grid } from '@elementor/ui';
6
4
  import { LetterCaseIcon, LetterCaseLowerIcon, LetterCaseUpperIcon } from '@elementor/icons';
5
+ import { StyleControl } from '../../../controls/style-control';
6
+ import { ToggleControl } from '../../../controls/control-types/toggle-control';
7
7
 
8
8
  const options = [
9
9
  { value: 'capitalize', label: __( 'Capitalize', 'elementor' ), icon: LetterCaseIcon },
@@ -11,13 +11,15 @@ const options = [
11
11
  { value: 'lowercase', label: __( 'Lowercase', 'elementor' ), icon: LetterCaseLowerIcon },
12
12
  ];
13
13
 
14
- export const TransformControl = () => {
15
- return (
16
- <ControlContainer>
17
- <StyleControl.Label>{ __( 'Transform', 'elementor' ) }</StyleControl.Label>
18
- <StyleControl bind={ 'text-transform' }>
14
+ export const TransformControl = () => (
15
+ <StyleControl bind={ 'text-transform' }>
16
+ <Grid container spacing={ 1 } alignItems="center">
17
+ <Grid item xs={ 6 }>
18
+ <StyleControl.Label>{ __( 'Transform', 'elementor' ) }</StyleControl.Label>
19
+ </Grid>
20
+ <Grid item xs={ 6 } display="flex" justifyContent="end">
19
21
  <ToggleControl options={ options } />
20
- </StyleControl>
21
- </ControlContainer>
22
- );
23
- };
22
+ </Grid>
23
+ </Grid>
24
+ </StyleControl>
25
+ );
@@ -11,6 +11,7 @@ import { WordSpacingControl } from './word-spacing-control';
11
11
  import { CollapsibleContent } from '../../collapsible-content';
12
12
  import { TransformControl } from './transform-control';
13
13
  import { TextAlignmentControl } from './text-alignment-control';
14
+ import { TextDirectionControl } from './text-direction-control';
14
15
 
15
16
  export const TypographySection = () => {
16
17
  return (
@@ -28,6 +29,7 @@ export const TypographySection = () => {
28
29
  <TextAlignmentControl />
29
30
  <TextStyleControl />
30
31
  <TransformControl />
32
+ <TextDirectionControl />
31
33
  </Stack>
32
34
  </CollapsibleContent>
33
35
  </Stack>
@@ -1,16 +1,20 @@
1
1
  import * as React from 'react';
2
+ import { __ } from '@wordpress/i18n';
3
+ import { Grid } from '@elementor/ui';
2
4
  import { StyleControl } from '../../../controls/style-control';
3
5
  import { SizeControl } from '../../../controls/control-types/size-control';
4
- import { ControlContainer } from '../../../controls/components/control-container';
5
- import { __ } from '@wordpress/i18n';
6
6
 
7
7
  export const WordSpacingControl = () => {
8
8
  return (
9
9
  <StyleControl bind="word-spacing">
10
- <ControlContainer>
11
- <StyleControl.Label>{ __( 'Word Spacing', 'elementor' ) }</StyleControl.Label>
12
- <SizeControl />
13
- </ControlContainer>
10
+ <Grid container spacing={ 1 } alignItems="center">
11
+ <Grid item xs={ 6 }>
12
+ <StyleControl.Label>{ __( 'Word Spacing', 'elementor' ) }</StyleControl.Label>
13
+ </Grid>
14
+ <Grid item xs={ 6 }>
15
+ <SizeControl />
16
+ </Grid>
17
+ </Grid>
14
18
  </StyleControl>
15
19
  );
16
20
  };
@@ -1,6 +1,6 @@
1
1
  import * as React from 'react';
2
2
  import { StyleContext } from '../contexts/style-context';
3
- import { useElementContext } from '../contexts/element-context';
3
+ import { useElement } from '../controls/providers/element-provider';
4
4
  import { useElementStyles } from '../hooks/use-element-styles';
5
5
  import { Stack } from '@elementor/ui';
6
6
  import { SizeSection } from './style-sections/size-section';
@@ -8,6 +8,8 @@ import { TypographySection } from './style-sections/typography-section/typograph
8
8
  import { PositionSection } from './style-sections/position-section/position-section';
9
9
  import { StyleDefinition } from '@elementor/editor-style';
10
10
  import { SpacingSection } from './style-sections/spacing-section/spacing-section';
11
+ import { EffectsSection } from './style-sections/effects-section/effects-section';
12
+ import { BackgroundSection } from './style-sections/background-section/background-section';
11
13
 
12
14
  const CLASSES_PROP_KEY = 'classes';
13
15
 
@@ -21,16 +23,20 @@ export const StyleTab = () => {
21
23
  <SizeSection />
22
24
  <PositionSection />
23
25
  <TypographySection />
26
+ <BackgroundSection />
24
27
  <SpacingSection />
28
+ <EffectsSection />
25
29
  </Stack>
26
30
  </StyleContext>
27
31
  );
28
32
  };
29
33
 
30
34
  function useClassesProp(): string {
31
- const { elementType } = useElementContext();
35
+ const { elementType } = useElement();
32
36
 
33
- const prop = Object.entries( elementType.propsSchema ).find( ( [ , { type } ] ) => type.key === CLASSES_PROP_KEY );
37
+ const prop = Object.entries( elementType.propsSchema ).find(
38
+ ( [ , propType ] ) => propType.kind === 'array' && propType.key === CLASSES_PROP_KEY
39
+ );
34
40
 
35
41
  if ( ! prop ) {
36
42
  throw new Error( 'Element does not have a classes prop' );
@@ -40,7 +46,7 @@ function useClassesProp(): string {
40
46
  }
41
47
 
42
48
  function useStyleDefinition(): StyleDefinition | null {
43
- const { element } = useElementContext();
49
+ const { element } = useElement();
44
50
  const elementStyles = useElementStyles( element.id );
45
51
 
46
52
  return Object.values( elementStyles || {} )[ 0 ] ?? null;
@@ -0,0 +1,3 @@
1
+ import { createControlReplacement } from './controls/create-control-replacement';
2
+
3
+ export const { replaceControl, getControlReplacement } = createControlReplacement();
@@ -0,0 +1,28 @@
1
+ import * as React from 'react';
2
+ import { styled, Box, BoxProps } from '@elementor/ui';
3
+ import { ControlLayout, ControlType, getLayoutByType } from '../controls-registry';
4
+
5
+ export const ControlTypeContainer = ( {
6
+ controlType,
7
+ children,
8
+ }: React.PropsWithChildren< { controlType: ControlType } > ) => {
9
+ const layout = getLayoutByType( controlType );
10
+
11
+ return <StyledContainer layout={ layout }>{ children }</StyledContainer>;
12
+ };
13
+
14
+ const StyledContainer = styled( Box, {
15
+ shouldForwardProp: ( prop: string ) => ! [ 'layout' ].includes( prop ),
16
+ } )< BoxProps & { layout: ControlLayout } >( ( { layout, theme } ) => ( {
17
+ display: 'grid',
18
+ gridGap: theme.spacing( 1 ),
19
+ ...getGridLayout( layout ),
20
+ } ) );
21
+
22
+ const getGridLayout = ( layout: ControlLayout ) => ( {
23
+ justifyContent: 'space-between',
24
+ gridTemplateColumns: {
25
+ full: '1fr',
26
+ 'two-columns': 'repeat(2, 1fr)',
27
+ }[ layout ],
28
+ } );
@@ -0,0 +1,197 @@
1
+ import * as React from 'react';
2
+ import { useId, useRef, useState } from 'react';
3
+ import { __ } from '@wordpress/i18n';
4
+ import { PlusIcon, XIcon, CopyIcon, EyeIcon, EyeOffIcon } from '@elementor/icons';
5
+ import {
6
+ Box,
7
+ Stack,
8
+ Popover,
9
+ IconButton,
10
+ bindTrigger,
11
+ bindPopover,
12
+ usePopupState,
13
+ UnstableTagProps,
14
+ UnstableTag,
15
+ Typography,
16
+ } from '@elementor/ui';
17
+
18
+ const SIZE = 'tiny';
19
+
20
+ type AnchorEl = HTMLElement | null;
21
+
22
+ type Item< T > = {
23
+ disabled?: boolean;
24
+ } & T;
25
+
26
+ export type RepeaterProps< T > = {
27
+ label: string;
28
+ values?: T[];
29
+ setValues: ( newValue: T[] ) => void;
30
+ itemSettings: {
31
+ initialValues: T;
32
+ Label: React.ComponentType< { value: T } >;
33
+ Icon: React.ComponentType< { value: T } >;
34
+ Content: React.ComponentType< {
35
+ value: T;
36
+ setValue: ( newValue: T ) => void;
37
+ anchorEl: AnchorEl;
38
+ } >;
39
+ };
40
+ };
41
+
42
+ export const Repeater = < T, >( {
43
+ label,
44
+ itemSettings,
45
+ values: repeaterValues = [],
46
+ setValues: setRepeaterValues,
47
+ }: RepeaterProps< Item< T > > ) => {
48
+ const addRepeaterItem = () => {
49
+ const newItem = structuredClone( itemSettings.initialValues );
50
+
51
+ setRepeaterValues( [ ...repeaterValues, newItem ] );
52
+ };
53
+
54
+ const duplicateRepeaterItem = ( index: number ) => {
55
+ setRepeaterValues( [
56
+ ...repeaterValues.slice( 0, index ),
57
+ structuredClone( repeaterValues[ index ] ),
58
+ ...repeaterValues.slice( index ),
59
+ ] );
60
+ };
61
+
62
+ const removeRepeaterItem = ( index: number ) => {
63
+ setRepeaterValues( repeaterValues.filter( ( _, i ) => i !== index ) );
64
+ };
65
+
66
+ const toggleDisableRepeaterItem = ( index: number ) => {
67
+ setRepeaterValues(
68
+ repeaterValues.map( ( value, i ) => {
69
+ if ( i === index ) {
70
+ const { disabled, ...rest } = value;
71
+
72
+ // If the items should not be disabled, remove the disabled property.
73
+ return { ...rest, ...( disabled ? {} : { disabled: true } ) } as Item< T >;
74
+ }
75
+
76
+ return value;
77
+ } )
78
+ );
79
+ };
80
+
81
+ return (
82
+ <Stack>
83
+ <Stack direction="row" justifyContent="space-between" sx={ { py: 0.5 } }>
84
+ <Typography component="label" variant="caption" color="text.secondary">
85
+ { label }
86
+ </Typography>
87
+ <IconButton size={ SIZE } onClick={ addRepeaterItem } aria-label={ __( 'Add item', 'elementor' ) }>
88
+ <PlusIcon fontSize={ SIZE } />
89
+ </IconButton>
90
+ </Stack>
91
+ <Stack gap={ 1 }>
92
+ { repeaterValues.map( ( value, index ) => (
93
+ <RepeaterItem
94
+ key={ index }
95
+ disabled={ value.disabled }
96
+ label={ <itemSettings.Label value={ value } /> }
97
+ startIcon={ <itemSettings.Icon value={ value } /> }
98
+ removeItem={ () => removeRepeaterItem( index ) }
99
+ duplicateItem={ () => duplicateRepeaterItem( index ) }
100
+ toggleDisableItem={ () => toggleDisableRepeaterItem( index ) }
101
+ >
102
+ { ( props ) => (
103
+ <itemSettings.Content
104
+ { ...props }
105
+ value={ value }
106
+ setValue={ ( newValue ) =>
107
+ setRepeaterValues(
108
+ repeaterValues.map( ( item, i ) => ( i === index ? newValue : item ) )
109
+ )
110
+ }
111
+ />
112
+ ) }
113
+ </RepeaterItem>
114
+ ) ) }
115
+ </Stack>
116
+ </Stack>
117
+ );
118
+ };
119
+
120
+ type RepeaterItemProps = {
121
+ label: React.ReactNode;
122
+ disabled?: boolean;
123
+ startIcon: UnstableTagProps[ 'startIcon' ];
124
+ removeItem: () => void;
125
+ duplicateItem: () => void;
126
+ toggleDisableItem: () => void;
127
+ children: ( { anchorEl }: { anchorEl: AnchorEl } ) => React.ReactNode;
128
+ };
129
+
130
+ const RepeaterItem = ( {
131
+ label,
132
+ disabled,
133
+ startIcon,
134
+ children,
135
+ removeItem,
136
+ duplicateItem,
137
+ toggleDisableItem,
138
+ }: RepeaterItemProps ) => {
139
+ const popupId = useId();
140
+ const tagRef = useRef< HTMLElement >( null );
141
+ const [ anchorEl, setAnchorEl ] = useState< AnchorEl >( null );
142
+
143
+ const popoverState = usePopupState( { popupId, variant: 'popover' } );
144
+
145
+ const popoverProps = bindPopover( popoverState );
146
+
147
+ return (
148
+ <>
149
+ <UnstableTag
150
+ ref={ tagRef }
151
+ label={ label }
152
+ showActionsOnHover
153
+ variant="outlined"
154
+ aria-label={ __( 'Open item', 'elementor' ) }
155
+ { ...bindTrigger( popoverState ) }
156
+ startIcon={ startIcon }
157
+ actions={
158
+ <>
159
+ <IconButton
160
+ size={ SIZE }
161
+ onClick={ duplicateItem }
162
+ aria-label={ __( 'Duplicate item', 'elementor' ) }
163
+ >
164
+ <CopyIcon fontSize={ SIZE } />
165
+ </IconButton>
166
+ <IconButton
167
+ size={ SIZE }
168
+ onClick={ toggleDisableItem }
169
+ aria-label={
170
+ disabled ? __( 'Enable item', 'elementor' ) : __( 'Disable item', 'elementor' )
171
+ }
172
+ >
173
+ { disabled ? <EyeOffIcon fontSize={ SIZE } /> : <EyeIcon fontSize={ SIZE } /> }
174
+ </IconButton>
175
+ <IconButton
176
+ size={ SIZE }
177
+ onClick={ removeItem }
178
+ aria-label={ __( 'Remove item', 'elementor' ) }
179
+ >
180
+ <XIcon fontSize={ SIZE } />
181
+ </IconButton>
182
+ </>
183
+ }
184
+ />
185
+ <Popover
186
+ disablePortal
187
+ slotProps={ {
188
+ paper: { ref: setAnchorEl, sx: { width: tagRef.current?.getBoundingClientRect().width } },
189
+ } }
190
+ anchorOrigin={ { vertical: 'bottom', horizontal: 'left' } }
191
+ { ...popoverProps }
192
+ >
193
+ <Box p={ 2 }>{ children( { anchorEl } ) }</Box>
194
+ </Popover>
195
+ </>
196
+ );
197
+ };
@@ -1,7 +1,7 @@
1
- import { PropValue } from '../../types';
2
1
  import * as React from 'react';
3
- import { bindMenu, bindTrigger, Button, InputAdornment, Menu, MenuItem, TextField, usePopupState } from '@elementor/ui';
4
2
  import { useId } from 'react';
3
+ import { bindMenu, bindTrigger, Button, InputAdornment, Menu, MenuItem, TextField, usePopupState } from '@elementor/ui';
4
+ import { PropValue } from '../props/types';
5
5
 
6
6
  export type TextFieldInnerSelectionProps = {
7
7
  placeholder?: string;