@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.
- package/CHANGELOG.md +30 -0
- package/dist/index.js +693 -120
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +711 -111
- package/dist/index.mjs.map +1 -1
- package/package.json +7 -6
- package/src/components/style-sections/position-section/position-section.tsx +15 -0
- package/src/components/style-sections/position-section/z-index-control.tsx +16 -0
- package/src/components/style-sections/size-section.tsx +9 -12
- package/src/components/style-sections/spacing-section/linked-dimensions-control.tsx +140 -0
- package/src/components/style-sections/spacing-section/spacing-section.tsx +22 -0
- package/src/components/style-sections/typography-section/letter-spacing-control.tsx +16 -0
- package/src/components/style-sections/typography-section/text-color-control.tsx +16 -0
- package/src/components/style-sections/typography-section/transform-control.tsx +23 -0
- package/src/components/style-sections/typography-section/typography-section.tsx +15 -1
- package/src/components/style-sections/typography-section/word-spacing-control.tsx +16 -0
- package/src/components/style-tab.tsx +29 -5
- package/src/contexts/style-context.tsx +8 -2
- package/src/controls/components/control-toggle-button-group.tsx +59 -0
- package/src/controls/components/text-field-inner-selection.tsx +79 -0
- package/src/controls/control-types/color-control.tsx +24 -0
- package/src/controls/control-types/number-control.tsx +25 -0
- package/src/controls/control-types/size-control.tsx +20 -34
- package/src/controls/control-types/toggle-control.tsx +25 -0
- package/src/controls/hooks/use-style-control.ts +2 -1
- package/src/controls/settings-control.tsx +1 -1
- package/src/dynamics/components/dynamic-selection-control.tsx +180 -0
- package/src/dynamics/components/dynamic-selection.tsx +144 -0
- package/src/dynamics/dynamic-control.tsx +42 -0
- package/src/dynamics/hooks/use-dynamic-tag.ts +10 -0
- package/src/{hooks/use-dynamic-tags-config.ts → dynamics/hooks/use-prop-dynamic-tags.ts} +6 -5
- package/src/dynamics/init.ts +10 -0
- package/src/{sync → dynamics/sync}/get-atomic-dynamic-tags.ts +1 -1
- package/src/{sync → dynamics/sync}/get-elementor-config.ts +1 -1
- package/src/dynamics/types.ts +32 -0
- package/src/dynamics/utils.ts +9 -0
- package/src/init.ts +4 -0
- package/src/props/is-transformable.ts +14 -0
- package/src/sync/types.ts +0 -13
- package/src/sync/update-style.ts +2 -2
- 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
|
-
<
|
|
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
|
|
11
|
-
const
|
|
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={
|
|
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
|
|
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 {
|
|
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
|
|
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 = (
|
|
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
|
-
<
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
|