@elementor/editor-editing-panel 0.15.0 → 0.16.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 (41) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/dist/index.js +693 -120
  3. package/dist/index.js.map +1 -1
  4. package/dist/index.mjs +711 -111
  5. package/dist/index.mjs.map +1 -1
  6. package/package.json +7 -6
  7. package/src/components/style-sections/position-section/position-section.tsx +15 -0
  8. package/src/components/style-sections/position-section/z-index-control.tsx +16 -0
  9. package/src/components/style-sections/size-section.tsx +9 -12
  10. package/src/components/style-sections/spacing-section/linked-dimensions-control.tsx +140 -0
  11. package/src/components/style-sections/spacing-section/spacing-section.tsx +22 -0
  12. package/src/components/style-sections/typography-section/letter-spacing-control.tsx +16 -0
  13. package/src/components/style-sections/typography-section/text-color-control.tsx +16 -0
  14. package/src/components/style-sections/typography-section/transform-control.tsx +23 -0
  15. package/src/components/style-sections/typography-section/typography-section.tsx +15 -1
  16. package/src/components/style-sections/typography-section/word-spacing-control.tsx +16 -0
  17. package/src/components/style-tab.tsx +29 -5
  18. package/src/contexts/style-context.tsx +8 -2
  19. package/src/controls/components/control-toggle-button-group.tsx +59 -0
  20. package/src/controls/components/text-field-inner-selection.tsx +79 -0
  21. package/src/controls/control-types/color-control.tsx +24 -0
  22. package/src/controls/control-types/number-control.tsx +25 -0
  23. package/src/controls/control-types/size-control.tsx +20 -34
  24. package/src/controls/control-types/toggle-control.tsx +25 -0
  25. package/src/controls/hooks/use-style-control.ts +2 -1
  26. package/src/controls/settings-control.tsx +1 -1
  27. package/src/dynamics/components/dynamic-selection-control.tsx +180 -0
  28. package/src/dynamics/components/dynamic-selection.tsx +144 -0
  29. package/src/dynamics/dynamic-control.tsx +42 -0
  30. package/src/dynamics/hooks/use-dynamic-tag.ts +10 -0
  31. package/src/{hooks/use-dynamic-tags-config.ts → dynamics/hooks/use-prop-dynamic-tags.ts} +6 -5
  32. package/src/dynamics/init.ts +10 -0
  33. package/src/{sync → dynamics/sync}/get-atomic-dynamic-tags.ts +1 -1
  34. package/src/{sync → dynamics/sync}/get-elementor-config.ts +1 -1
  35. package/src/dynamics/types.ts +32 -0
  36. package/src/dynamics/utils.ts +9 -0
  37. package/src/init.ts +4 -0
  38. package/src/props/is-transformable.ts +14 -0
  39. package/src/sync/types.ts +0 -13
  40. package/src/sync/update-style.ts +2 -2
  41. package/src/types.ts +9 -6
@@ -0,0 +1,140 @@
1
+ import { PropValue } from '../../../types';
2
+ import { SizeControl } from '../../../controls/control-types/size-control';
3
+ import * as React from 'react';
4
+ import { ControlContainer } from '../../../controls/components/control-container';
5
+ import { Stack, ToggleButton } from '@elementor/ui';
6
+ import { DetachIcon, LinkIcon, SideBottomIcon, SideLeftIcon, SideRightIcon, SideTopIcon } from '@elementor/icons';
7
+ import { ControlLabel } from '../../control-label';
8
+ import { ControlContext, useControl } from '../../../controls/control-context';
9
+ import { __ } from '@wordpress/i18n';
10
+
11
+ export type Position = 'top' | 'right' | 'bottom' | 'left';
12
+
13
+ export type LinkedDimensionsValue = {
14
+ $$type: 'linked-dimensions';
15
+ value: {
16
+ isLinked: boolean;
17
+ top: PropValue;
18
+ right: PropValue;
19
+ bottom: PropValue;
20
+ left: PropValue;
21
+ };
22
+ };
23
+
24
+ export const LinkedDimensionsControl = ( { label }: { label: string } ) => {
25
+ const { value, setValue } = useControl< LinkedDimensionsValue >();
26
+ const { top, right, bottom, left, isLinked = false } = value?.value || {};
27
+
28
+ const setLinkedValue = ( position: Position, newValue: PropValue ) => {
29
+ const updatedValue = {
30
+ isLinked,
31
+ top: isLinked ? newValue : top,
32
+ right: isLinked ? newValue : right,
33
+ bottom: isLinked ? newValue : bottom,
34
+ left: isLinked ? newValue : left,
35
+ [ position ]: newValue,
36
+ };
37
+
38
+ setValue( {
39
+ $$type: 'linked-dimensions',
40
+ value: updatedValue,
41
+ } );
42
+ };
43
+
44
+ const toggleLinked = () => {
45
+ const updatedValue = {
46
+ isLinked: ! isLinked,
47
+ top,
48
+ right: ! isLinked ? top : right,
49
+ bottom: ! isLinked ? top : bottom,
50
+ left: ! isLinked ? top : left,
51
+ };
52
+
53
+ setValue( {
54
+ $$type: 'linked-dimensions',
55
+ value: updatedValue,
56
+ } );
57
+ };
58
+
59
+ const LinkedIcon = isLinked ? LinkIcon : DetachIcon;
60
+
61
+ return (
62
+ <>
63
+ <Stack direction="row" gap={ 2 }>
64
+ <ControlLabel>{ label }</ControlLabel>
65
+ <ToggleButton
66
+ aria-label={ __( 'Link Inputs', 'elementor' ) }
67
+ size={ 'tiny' }
68
+ value={ 'check' }
69
+ selected={ isLinked }
70
+ sx={ { marginLeft: 'auto' } }
71
+ onChange={ toggleLinked }
72
+ >
73
+ <LinkedIcon fontSize={ 'tiny' } />
74
+ </ToggleButton>
75
+ </Stack>
76
+ <Stack direction="row" gap={ 2 }>
77
+ <ControlContainer direction={ 'column' }>
78
+ <ControlLabel>{ __( 'Top', 'elementor' ) }</ControlLabel>
79
+ <Control
80
+ bind={ 'top' }
81
+ value={ top }
82
+ setValue={ setLinkedValue }
83
+ startIcon={ <SideTopIcon fontSize={ 'tiny' } /> }
84
+ />
85
+ </ControlContainer>
86
+ <ControlContainer direction={ 'column' }>
87
+ <ControlLabel>{ __( 'Right', 'elementor' ) }</ControlLabel>
88
+ <Control
89
+ bind={ 'right' }
90
+ value={ right }
91
+ setValue={ setLinkedValue }
92
+ startIcon={ <SideRightIcon fontSize={ 'tiny' } /> }
93
+ />
94
+ </ControlContainer>
95
+ </Stack>
96
+ <Stack direction="row" gap={ 2 }>
97
+ <ControlContainer direction={ 'column' }>
98
+ <ControlLabel>{ __( 'Bottom', 'elementor' ) }</ControlLabel>
99
+ <Control
100
+ bind={ 'bottom' }
101
+ value={ bottom }
102
+ setValue={ setLinkedValue }
103
+ startIcon={ <SideBottomIcon fontSize={ 'tiny' } /> }
104
+ />
105
+ </ControlContainer>
106
+ <ControlContainer direction={ 'column' }>
107
+ <ControlLabel>{ __( 'Left', 'elementor' ) }</ControlLabel>
108
+ <Control
109
+ bind={ 'left' }
110
+ value={ left }
111
+ setValue={ setLinkedValue }
112
+ startIcon={ <SideLeftIcon fontSize={ 'tiny' } /> }
113
+ />
114
+ </ControlContainer>
115
+ </Stack>
116
+ </>
117
+ );
118
+ };
119
+
120
+ const Control = ( {
121
+ bind,
122
+ startIcon,
123
+ value,
124
+ setValue,
125
+ }: {
126
+ bind: Position;
127
+ value: PropValue;
128
+ startIcon: React.ReactNode;
129
+ setValue: ( bind: Position, newValue: PropValue ) => void;
130
+ } ) => (
131
+ <ControlContext.Provider
132
+ value={ {
133
+ bind,
134
+ setValue: ( newValue ) => setValue( bind, newValue ),
135
+ value,
136
+ } }
137
+ >
138
+ <SizeControl startIcon={ startIcon } />
139
+ </ControlContext.Provider>
140
+ );
@@ -0,0 +1,22 @@
1
+ import * as React from 'react';
2
+ import { AccordionSection } from '../../accordion-section';
3
+ import { Divider, Stack } from '@elementor/ui';
4
+ import { __ } from '@wordpress/i18n';
5
+ import { LinkedDimensionsControl } from './linked-dimensions-control';
6
+ import { StyleControl } from '../../../controls/style-control';
7
+
8
+ export const SpacingSection = () => {
9
+ return (
10
+ <AccordionSection title={ __( 'Spacing', 'elementor' ) }>
11
+ <Stack gap={ 1.5 }>
12
+ <StyleControl bind={ 'padding' }>
13
+ <LinkedDimensionsControl label={ __( 'Padding', 'elementor' ) } />
14
+ </StyleControl>
15
+ <Divider />
16
+ <StyleControl bind={ 'margin' }>
17
+ <LinkedDimensionsControl label={ __( 'Margin', 'elementor' ) } />
18
+ </StyleControl>
19
+ </Stack>
20
+ </AccordionSection>
21
+ );
22
+ };
@@ -0,0 +1,16 @@
1
+ import * as React from 'react';
2
+ import { StyleControl } from '../../../controls/style-control';
3
+ import { SizeControl } from '../../../controls/control-types/size-control';
4
+ import { ControlContainer } from '../../../controls/components/control-container';
5
+ import { __ } from '@wordpress/i18n';
6
+
7
+ export const LetterSpacingControl = () => {
8
+ return (
9
+ <StyleControl bind="letter-spacing">
10
+ <ControlContainer>
11
+ <StyleControl.Label>{ __( 'Letter Spacing', 'elementor' ) }</StyleControl.Label>
12
+ <SizeControl />
13
+ </ControlContainer>
14
+ </StyleControl>
15
+ );
16
+ };
@@ -0,0 +1,16 @@
1
+ import * as React from 'react';
2
+ import { StyleControl } from '../../../controls/style-control';
3
+ import { ControlContainer } from '../../../controls/components/control-container';
4
+ import { __ } from '@wordpress/i18n';
5
+ import { ColorControl } from '../../../controls/control-types/color-control';
6
+
7
+ export const TextColorControl = () => {
8
+ return (
9
+ <StyleControl bind="color">
10
+ <ControlContainer>
11
+ <StyleControl.Label>{ __( 'Text Color', 'elementor' ) }</StyleControl.Label>
12
+ <ColorControl />
13
+ </ControlContainer>
14
+ </StyleControl>
15
+ );
16
+ };
@@ -0,0 +1,23 @@
1
+ import * as React from 'react';
2
+ import { ControlContainer } from '../../../controls/components/control-container';
3
+ import { StyleControl } from '../../../controls/style-control';
4
+ import { __ } from '@wordpress/i18n';
5
+ import { ToggleControl } from '../../../controls/control-types/toggle-control';
6
+ import { LetterCaseIcon, LetterCaseLowerIcon, LetterCaseUpperIcon } from '@elementor/icons';
7
+
8
+ const options = [
9
+ { value: 'capitalize', label: __( 'Capitalize', 'elementor' ), icon: LetterCaseIcon },
10
+ { value: 'uppercase', label: __( 'Uppercase', 'elementor' ), icon: LetterCaseUpperIcon },
11
+ { value: 'lowercase', label: __( 'Lowercase', 'elementor' ), icon: LetterCaseLowerIcon },
12
+ ];
13
+
14
+ export const TransformControl = () => {
15
+ return (
16
+ <ControlContainer>
17
+ <StyleControl.Label>{ __( 'Transform', 'elementor' ) }</StyleControl.Label>
18
+ <StyleControl bind={ 'text-transform' }>
19
+ <ToggleControl options={ options } />
20
+ </StyleControl>
21
+ </ControlContainer>
22
+ );
23
+ };
@@ -5,6 +5,11 @@ import { TextStyleControl } from './text-style-control';
5
5
  import { __ } from '@wordpress/i18n';
6
6
  import { FontSizeControl } from './font-size-control';
7
7
  import { FontWeightControl } from './font-weight-control';
8
+ import { TextColorControl } from './text-color-control';
9
+ import { LetterSpacingControl } from './letter-spacing-control';
10
+ import { WordSpacingControl } from './word-spacing-control';
11
+ import { CollapsibleContent } from '../../collapsible-content';
12
+ import { TransformControl } from './transform-control';
8
13
 
9
14
  export const TypographySection = () => {
10
15
  return (
@@ -13,7 +18,16 @@ export const TypographySection = () => {
13
18
  <FontWeightControl />
14
19
  <FontSizeControl />
15
20
  <Divider />
16
- <TextStyleControl />
21
+ <TextColorControl />
22
+ <CollapsibleContent>
23
+ <Stack gap={ 1.5 } sx={ { pt: 1.5 } }>
24
+ <LetterSpacingControl />
25
+ <WordSpacingControl />
26
+ <Divider />
27
+ <TextStyleControl />
28
+ <TransformControl />
29
+ </Stack>
30
+ </CollapsibleContent>
17
31
  </Stack>
18
32
  </AccordionSection>
19
33
  );
@@ -0,0 +1,16 @@
1
+ import * as React from 'react';
2
+ import { StyleControl } from '../../../controls/style-control';
3
+ import { SizeControl } from '../../../controls/control-types/size-control';
4
+ import { ControlContainer } from '../../../controls/components/control-container';
5
+ import { __ } from '@wordpress/i18n';
6
+
7
+ export const WordSpacingControl = () => {
8
+ return (
9
+ <StyleControl bind="word-spacing">
10
+ <ControlContainer>
11
+ <StyleControl.Label>{ __( 'Word Spacing', 'elementor' ) }</StyleControl.Label>
12
+ <SizeControl />
13
+ </ControlContainer>
14
+ </StyleControl>
15
+ );
16
+ };
@@ -5,19 +5,43 @@ import { useElementStyles } from '../hooks/use-element-styles';
5
5
  import { Stack } from '@elementor/ui';
6
6
  import { SizeSection } from './style-sections/size-section';
7
7
  import { TypographySection } from './style-sections/typography-section/typography-section';
8
+ import { PositionSection } from './style-sections/position-section/position-section';
9
+ import { StyleDefinition } from '@elementor/editor-style';
10
+ import { SpacingSection } from './style-sections/spacing-section/spacing-section';
11
+
12
+ const CLASSES_PROP_KEY = 'classes';
8
13
 
9
14
  export const StyleTab = () => {
10
- const { element } = useElementContext();
11
- const elementStyles = useElementStyles( element.id );
12
- // TODO: Handle selected style state.
13
- const [ selectedStyleDef = null ] = Object.values( elementStyles || {} );
15
+ const styleDefinition = useStyleDefinition();
16
+ const classesProp = useClassesProp();
14
17
 
15
18
  return (
16
- <StyleContext selectedStyleDef={ selectedStyleDef }>
19
+ <StyleContext selectedStyleDef={ styleDefinition } selectedClassesProp={ classesProp }>
17
20
  <Stack>
18
21
  <SizeSection />
22
+ <PositionSection />
19
23
  <TypographySection />
24
+ <SpacingSection />
20
25
  </Stack>
21
26
  </StyleContext>
22
27
  );
23
28
  };
29
+
30
+ function useClassesProp(): string {
31
+ const { elementType } = useElementContext();
32
+
33
+ const prop = Object.entries( elementType.propsSchema ).find( ( [ , { type } ] ) => type.key === CLASSES_PROP_KEY );
34
+
35
+ if ( ! prop ) {
36
+ throw new Error( 'Element does not have a classes prop' );
37
+ }
38
+
39
+ return prop[ 0 ];
40
+ }
41
+
42
+ function useStyleDefinition(): StyleDefinition | null {
43
+ const { element } = useElementContext();
44
+ const elementStyles = useElementStyles( element.id );
45
+
46
+ return Object.values( elementStyles || {} )[ 0 ] ?? null;
47
+ }
@@ -6,6 +6,7 @@ import { StyleDefinition, StyleVariant } from '@elementor/editor-style';
6
6
  type ContextValue = {
7
7
  selectedStyleDef: StyleDefinition | null;
8
8
  selectedMeta: StyleVariant[ 'meta' ];
9
+ selectedClassesProp: string;
9
10
  };
10
11
 
11
12
  const Context = createContext< ContextValue | null >( null );
@@ -13,14 +14,19 @@ const Context = createContext< ContextValue | null >( null );
13
14
  type Props = {
14
15
  children: ReactNode;
15
16
  selectedStyleDef: StyleDefinition | null;
17
+ selectedClassesProp: string;
16
18
  };
17
19
 
18
- export function StyleContext( { children, selectedStyleDef }: Props ) {
20
+ export function StyleContext( { children, selectedStyleDef, selectedClassesProp }: Props ) {
19
21
  const breakpoint = useActiveBreakpoint();
20
22
  // TODO: Handle state when we support it.
21
23
  const selectedMeta = { breakpoint, state: null } as const;
22
24
 
23
- return <Context.Provider value={ { selectedStyleDef, selectedMeta } }>{ children }</Context.Provider>;
25
+ return (
26
+ <Context.Provider value={ { selectedStyleDef, selectedMeta, selectedClassesProp } }>
27
+ { children }
28
+ </Context.Provider>
29
+ );
24
30
  }
25
31
 
26
32
  export function useStyleContext() {
@@ -0,0 +1,59 @@
1
+ import * as React from 'react';
2
+ import { JSX } from 'react';
3
+ import { StackProps, styled, ToggleButton, ToggleButtonGroup } from '@elementor/ui';
4
+
5
+ export type ToggleButtonGroupItem< TValue > = {
6
+ value: TValue;
7
+ label: string;
8
+ icon: JSX.ElementType;
9
+ };
10
+
11
+ const StyledToggleButtonGroup = styled( ToggleButtonGroup )`
12
+ ${ ( { justify } ) => `justify-content: ${ justify };` }
13
+ `;
14
+
15
+ type ExclusiveValue< TValue > = TValue | null;
16
+ type NonExclusiveValue< TValue > = TValue[];
17
+
18
+ type Props< TValue > = {
19
+ justify?: StackProps[ 'justifyContent' ];
20
+ size?: 'tiny' | 'small' | 'medium' | 'large';
21
+ items: ToggleButtonGroupItem< TValue >[];
22
+ } & (
23
+ | {
24
+ exclusive?: false;
25
+ value: NonExclusiveValue< TValue >;
26
+ onChange: ( value: NonExclusiveValue< TValue > ) => void;
27
+ }
28
+ | {
29
+ exclusive: true;
30
+ value: ExclusiveValue< TValue >;
31
+ onChange: ( value: ExclusiveValue< TValue > ) => void;
32
+ }
33
+ );
34
+
35
+ export const ControlToggleButtonGroup = < TValue, >( {
36
+ justify = 'end',
37
+ size = 'tiny',
38
+ value,
39
+ onChange,
40
+ items,
41
+ exclusive = false,
42
+ }: Props< TValue > ) => {
43
+ const handleChange = (
44
+ _: React.MouseEvent< HTMLElement >,
45
+ newValue: typeof exclusive extends true ? ExclusiveValue< TValue > : NonExclusiveValue< TValue >
46
+ ) => {
47
+ onChange( newValue as never );
48
+ };
49
+
50
+ return (
51
+ <StyledToggleButtonGroup justify={ justify } value={ value } onChange={ handleChange } exclusive={ exclusive }>
52
+ { items.map( ( { label, value: buttonValue, icon: Icon } ) => (
53
+ <ToggleButton key={ buttonValue } value={ buttonValue } aria-label={ label } size={ size }>
54
+ <Icon fontSize={ size } />
55
+ </ToggleButton>
56
+ ) ) }
57
+ </StyledToggleButtonGroup>
58
+ );
59
+ };
@@ -0,0 +1,79 @@
1
+ import { PropValue } from '../../types';
2
+ import * as React from 'react';
3
+ import { bindMenu, bindTrigger, Button, InputAdornment, Menu, MenuItem, TextField, usePopupState } from '@elementor/ui';
4
+ import { useId } from 'react';
5
+
6
+ export type TextFieldInnerSelectionProps = {
7
+ placeholder?: string;
8
+ type: string;
9
+ value: PropValue;
10
+ onChange: ( event: React.ChangeEvent< HTMLInputElement > ) => void;
11
+ endAdornment: React.ReactNode;
12
+ startAdornment?: React.ReactNode;
13
+ };
14
+
15
+ export const TextFieldInnerSelection = ( {
16
+ placeholder,
17
+ type,
18
+ value,
19
+ onChange,
20
+ endAdornment,
21
+ startAdornment,
22
+ }: TextFieldInnerSelectionProps ) => {
23
+ return (
24
+ <TextField
25
+ size="tiny"
26
+ type={ type }
27
+ value={ value }
28
+ onChange={ onChange }
29
+ placeholder={ placeholder }
30
+ InputProps={ {
31
+ endAdornment,
32
+ startAdornment,
33
+ } }
34
+ />
35
+ );
36
+ };
37
+
38
+ export type SelectionEndAdornmentProps< T extends string > = {
39
+ options: T[];
40
+ onClick: ( value: T ) => void;
41
+ value: T;
42
+ };
43
+
44
+ export const SelectionEndAdornment = < T extends string >( {
45
+ options,
46
+ onClick,
47
+ value,
48
+ }: SelectionEndAdornmentProps< T > ) => {
49
+ const popupState = usePopupState( {
50
+ variant: 'popover',
51
+ popupId: useId(),
52
+ } );
53
+
54
+ const handleMenuItemClick = ( index: number ) => {
55
+ onClick( options[ index ] );
56
+ popupState.close();
57
+ };
58
+
59
+ return (
60
+ <InputAdornment position="end">
61
+ <Button
62
+ size="small"
63
+ color="inherit"
64
+ sx={ { font: 'inherit', minWidth: 'initial' } }
65
+ { ...bindTrigger( popupState ) }
66
+ >
67
+ { value.toUpperCase() }
68
+ </Button>
69
+
70
+ <Menu MenuListProps={ { dense: true } } { ...bindMenu( popupState ) }>
71
+ { options.map( ( option, index ) => (
72
+ <MenuItem key={ option } onClick={ () => handleMenuItemClick( index ) }>
73
+ { option.toUpperCase() }
74
+ </MenuItem>
75
+ ) ) }
76
+ </Menu>
77
+ </InputAdornment>
78
+ );
79
+ };
@@ -0,0 +1,24 @@
1
+ import * as React from 'react';
2
+ import { UnstableColorPicker } from '@elementor/ui';
3
+ import { useControl } from '../control-context';
4
+
5
+ export const ColorControl = () => {
6
+ const { value, setValue } = useControl< string >();
7
+
8
+ const handleChange = debounce( ( selectedColor: string ) => {
9
+ setValue( selectedColor );
10
+ } );
11
+
12
+ return <UnstableColorPicker value={ value } onChange={ handleChange }></UnstableColorPicker>;
13
+ };
14
+
15
+ // TODO: Remove this when the color picker component sends one event per color change [DES-422].
16
+ const debounce = < TArgs extends unknown[], TReturn >( func: ( ...args: TArgs ) => TReturn, wait = 300 ) => {
17
+ let timer: ReturnType< typeof setTimeout >;
18
+
19
+ return ( ...args: TArgs ) => {
20
+ clearTimeout( timer );
21
+
22
+ timer = setTimeout( () => func( ...args ), wait );
23
+ };
24
+ };
@@ -0,0 +1,25 @@
1
+ import * as React from 'react';
2
+ import { TextField } from '@elementor/ui';
3
+ import { useControl } from '../control-context';
4
+
5
+ const isEmptyOrNaN = ( value?: string | number ) =>
6
+ value === undefined || value === '' || Number.isNaN( Number( value ) );
7
+
8
+ export const NumberControl = ( { placeholder }: { placeholder?: string } ) => {
9
+ const { value, setValue } = useControl< number | undefined >();
10
+
11
+ const handleChange = ( event: React.ChangeEvent< HTMLInputElement > ) => {
12
+ const eventValue: string = event.target.value;
13
+ setValue( isEmptyOrNaN( eventValue ) ? undefined : Number( eventValue ) );
14
+ };
15
+
16
+ return (
17
+ <TextField
18
+ size="tiny"
19
+ type="number"
20
+ value={ isEmptyOrNaN( value ) ? '' : value }
21
+ onChange={ handleChange }
22
+ placeholder={ placeholder }
23
+ />
24
+ );
25
+ };
@@ -1,13 +1,9 @@
1
1
  import * as React from 'react';
2
- import { MenuItem, Select, SelectChangeEvent, Stack, TextField } from '@elementor/ui';
2
+ import { InputAdornment } from '@elementor/ui';
3
3
  import { TransformablePropValue } from '../../types';
4
4
  import { useControl } from '../control-context';
5
5
  import { useSyncExternalState } from '../hooks/use-sync-external-state';
6
-
7
- export type SizeControlProps = {
8
- units?: Unit[];
9
- placeholder?: string;
10
- };
6
+ import { SelectionEndAdornment, TextFieldInnerSelection } from '../components/text-field-inner-selection';
11
7
 
12
8
  export type Unit = 'px' | '%' | 'em' | 'rem' | 'vw';
13
9
 
@@ -15,7 +11,13 @@ const defaultUnits: Unit[] = [ 'px', '%', 'em', 'rem', 'vw' ];
15
11
 
16
12
  export type SizeControlValue = TransformablePropValue< { unit: Unit; size: number } >;
17
13
 
18
- export const SizeControl = ( { units = defaultUnits, placeholder }: SizeControlProps ) => {
14
+ export type SizeControlProps = {
15
+ placeholder?: string;
16
+ startIcon?: React.ReactNode;
17
+ units?: Unit[];
18
+ };
19
+
20
+ export const SizeControl = ( { units = defaultUnits, placeholder, startIcon }: SizeControlProps ) => {
19
21
  const { value, setValue } = useControl< SizeControlValue >();
20
22
 
21
23
  const [ state, setState ] = useSyncExternalState< SizeControlValue >( {
@@ -28,9 +30,7 @@ export const SizeControl = ( { units = defaultUnits, placeholder }: SizeControlP
28
30
  } ),
29
31
  } );
30
32
 
31
- const handleUnitChange = ( event: SelectChangeEvent< Unit > ) => {
32
- const unit = event.target.value as Unit;
33
-
33
+ const handleUnitChange = ( unit: Unit ) => {
34
34
  setState( ( prev ) => ( {
35
35
  ...prev,
36
36
  value: {
@@ -53,29 +53,15 @@ export const SizeControl = ( { units = defaultUnits, placeholder }: SizeControlP
53
53
  };
54
54
 
55
55
  return (
56
- <Stack direction="row">
57
- <TextField
58
- size="tiny"
59
- type="number"
60
- value={ Number.isNaN( state.value.size ) ? '' : state.value.size }
61
- onChange={ handleSizeChange }
62
- placeholder={ placeholder }
63
- />
64
- <Select
65
- size="tiny"
66
- value={ state.value.unit }
67
- onChange={ handleUnitChange }
68
- MenuProps={ {
69
- anchorOrigin: { vertical: 'bottom', horizontal: 'right' },
70
- transformOrigin: { vertical: 'top', horizontal: 'right' },
71
- } }
72
- >
73
- { units.map( ( unit ) => (
74
- <MenuItem key={ unit } value={ unit }>
75
- { unit.toUpperCase() }
76
- </MenuItem>
77
- ) ) }
78
- </Select>
79
- </Stack>
56
+ <TextFieldInnerSelection
57
+ endAdornment={
58
+ <SelectionEndAdornment options={ units } onClick={ handleUnitChange } value={ state.value.unit } />
59
+ }
60
+ placeholder={ placeholder }
61
+ startAdornment={ startIcon ?? <InputAdornment position="start">{ startIcon }</InputAdornment> }
62
+ type="number"
63
+ value={ Number.isNaN( state.value.size ) ? '' : state.value.size }
64
+ onChange={ handleSizeChange }
65
+ />
80
66
  );
81
67
  };
@@ -0,0 +1,25 @@
1
+ import * as React from 'react';
2
+ import { useControl } from '../control-context';
3
+ import { ControlToggleButtonGroup, ToggleButtonGroupItem } from '../components/control-toggle-button-group';
4
+ import { PropValue } from '../../types';
5
+
6
+ type ToggleControlProps< T extends PropValue > = {
7
+ options: ToggleButtonGroupItem< T >[];
8
+ };
9
+
10
+ export const ToggleControl = < T extends PropValue >( { options }: ToggleControlProps< T > ) => {
11
+ const { value, setValue } = useControl< T >();
12
+
13
+ const handleToggle = ( option: T | null ) => {
14
+ setValue( option || undefined );
15
+ };
16
+
17
+ return (
18
+ <ControlToggleButtonGroup
19
+ items={ options }
20
+ value={ value || null }
21
+ onChange={ handleToggle }
22
+ exclusive={ true }
23
+ />
24
+ );
25
+ };
@@ -6,7 +6,7 @@ import { PropKey, PropValue } from '../../types';
6
6
 
7
7
  export const useStyleControl = < T extends PropValue >( propName: PropKey ) => {
8
8
  const { element } = useElementContext();
9
- const { selectedStyleDef, selectedMeta } = useStyleContext();
9
+ const { selectedStyleDef, selectedMeta, selectedClassesProp } = useStyleContext();
10
10
 
11
11
  const value = useElementStyleProp< T >( {
12
12
  elementID: element.id,
@@ -21,6 +21,7 @@ export const useStyleControl = < T extends PropValue >( propName: PropKey ) => {
21
21
  styleDefID: selectedStyleDef?.id,
22
22
  props: { [ propName ]: newValue },
23
23
  meta: selectedMeta,
24
+ bind: selectedClassesProp,
24
25
  } );
25
26
  };
26
27
 
@@ -15,7 +15,7 @@ type Props = {
15
15
  export const SettingsControlProvider = ( { bind, children }: Props ) => {
16
16
  const { element, elementType } = useElementContext();
17
17
 
18
- const defaultValue = elementType.propsSchema[ bind ]?.default;
18
+ const defaultValue = elementType.propsSchema[ bind ]?.type.default;
19
19
  const settingsValue = useWidgetSettings( { id: element.id, bind } );
20
20
  const value = settingsValue ?? defaultValue ?? null;
21
21