@elementor/editor-editing-panel 0.16.0 → 0.18.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 +44 -0
- package/dist/index.d.mts +26 -9
- package/dist/index.d.ts +26 -9
- package/dist/index.js +910 -360
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +886 -327
- package/dist/index.mjs.map +1 -1
- package/package.json +8 -7
- package/src/components/settings-tab.tsx +5 -2
- package/src/components/style-sections/effects-section/box-shadow-repeater.tsx +224 -0
- package/src/components/style-sections/effects-section/effects-section.tsx +18 -0
- package/src/components/style-sections/position-section/z-index-control.tsx +11 -7
- package/src/components/style-sections/size-section.tsx +23 -20
- package/src/components/style-sections/spacing-section/linked-dimensions-control.tsx +62 -47
- package/src/components/style-sections/typography-section/font-size-control.tsx +10 -6
- package/src/components/style-sections/typography-section/font-weight-control.tsx +16 -12
- package/src/components/style-sections/typography-section/letter-spacing-control.tsx +10 -6
- package/src/components/style-sections/typography-section/text-alignment-control.tsx +47 -0
- package/src/components/style-sections/typography-section/text-color-control.tsx +10 -6
- package/src/components/style-sections/typography-section/text-direction-control.tsx +37 -0
- package/src/components/style-sections/typography-section/text-style-control.tsx +37 -34
- package/src/components/style-sections/typography-section/transform-control.tsx +14 -12
- package/src/components/style-sections/typography-section/typography-section.tsx +4 -0
- package/src/components/style-sections/typography-section/word-spacing-control.tsx +10 -6
- package/src/components/style-tab.tsx +5 -1
- package/src/controls/components/control-type-container.tsx +28 -0
- package/src/controls/components/repeater.tsx +197 -0
- package/src/controls/control-actions/actions/popover-action.tsx +58 -0
- package/src/controls/control-actions/control-actions-menu.ts +8 -0
- package/src/controls/control-actions/control-actions.tsx +43 -0
- package/src/controls/control-replacement.ts +15 -7
- package/src/controls/control-types/color-control.tsx +21 -18
- package/src/controls/control-types/image-control.tsx +56 -59
- package/src/controls/control-types/image-media-control.tsx +73 -0
- package/src/controls/control-types/number-control.tsx +13 -9
- package/src/controls/control-types/select-control.tsx +13 -9
- package/src/controls/control-types/size-control.tsx +17 -13
- package/src/controls/control-types/text-area-control.tsx +15 -11
- package/src/controls/control-types/text-control.tsx +9 -3
- package/src/controls/control-types/toggle-control.tsx +3 -2
- package/src/controls/control.tsx +1 -7
- package/src/controls/controls-registry.tsx +19 -10
- package/src/controls/create-control.tsx +31 -0
- package/src/controls/settings-control.tsx +2 -9
- package/src/dynamics/components/dynamic-selection-control.tsx +4 -3
- package/src/dynamics/components/dynamic-selection.tsx +19 -8
- package/src/dynamics/dynamic-control.tsx +1 -1
- package/src/dynamics/hooks/use-prop-dynamic-action.tsx +23 -0
- package/src/dynamics/hooks/use-prop-dynamic-tags.ts +4 -4
- package/src/dynamics/hooks/use-prop-value-history.ts +26 -0
- package/src/dynamics/init.ts +9 -0
- package/src/dynamics/types.ts +6 -3
- package/src/dynamics/utils.ts +18 -5
- package/src/index.ts +2 -0
- package/src/types.ts +35 -14
- package/src/controls/components/control-container.tsx +0 -18
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { __ } from '@wordpress/i18n';
|
|
3
|
+
import { Grid } from '@elementor/ui';
|
|
4
|
+
import { AlignLeftIcon, AlignCenterIcon, AlignRightIcon, AlignJustifiedIcon } 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 Alignments = 'left' | 'center' | 'right' | 'justify';
|
|
10
|
+
|
|
11
|
+
const options: ToggleButtonGroupItem< Alignments >[] = [
|
|
12
|
+
{
|
|
13
|
+
value: 'left',
|
|
14
|
+
label: __( 'Left', 'elementor' ),
|
|
15
|
+
icon: AlignLeftIcon,
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
value: 'center',
|
|
19
|
+
label: __( 'Center', 'elementor' ),
|
|
20
|
+
icon: AlignCenterIcon,
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
value: 'right',
|
|
24
|
+
label: __( 'Right', 'elementor' ),
|
|
25
|
+
icon: AlignRightIcon,
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
value: 'justify',
|
|
29
|
+
label: __( 'Justify', 'elementor' ),
|
|
30
|
+
icon: AlignJustifiedIcon,
|
|
31
|
+
},
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
export const TextAlignmentControl = () => {
|
|
35
|
+
return (
|
|
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 }>
|
|
42
|
+
<ToggleControl options={ options } />
|
|
43
|
+
</Grid>
|
|
44
|
+
</Grid>
|
|
45
|
+
</StyleControl>
|
|
46
|
+
);
|
|
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
|
-
<
|
|
11
|
-
<
|
|
12
|
-
|
|
13
|
-
|
|
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 }>
|
|
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 {
|
|
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 >( '
|
|
13
|
-
const [ textDecoration, setTextDecoration ] = useStyleControl< string | null >( '
|
|
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
|
-
<
|
|
19
|
-
<
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
17
|
+
<Grid container spacing={ 1 } alignItems="center">
|
|
18
|
+
<Grid item xs={ 6 }>
|
|
19
|
+
<ControlLabel>{ __( 'Style', 'elementor' ) }</ControlLabel>
|
|
20
|
+
</Grid>
|
|
21
|
+
<Grid item xs={ 6 }>
|
|
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 {
|
|
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
|
-
|
|
16
|
-
<
|
|
17
|
-
<
|
|
18
|
-
|
|
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 }>
|
|
19
21
|
<ToggleControl options={ options } />
|
|
20
|
-
</
|
|
21
|
-
</
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
</Grid>
|
|
23
|
+
</Grid>
|
|
24
|
+
</StyleControl>
|
|
25
|
+
);
|
|
@@ -10,6 +10,8 @@ import { LetterSpacingControl } from './letter-spacing-control';
|
|
|
10
10
|
import { WordSpacingControl } from './word-spacing-control';
|
|
11
11
|
import { CollapsibleContent } from '../../collapsible-content';
|
|
12
12
|
import { TransformControl } from './transform-control';
|
|
13
|
+
import { TextAlignmentControl } from './text-alignment-control';
|
|
14
|
+
import { TextDirectionControl } from './text-direction-control';
|
|
13
15
|
|
|
14
16
|
export const TypographySection = () => {
|
|
15
17
|
return (
|
|
@@ -24,8 +26,10 @@ export const TypographySection = () => {
|
|
|
24
26
|
<LetterSpacingControl />
|
|
25
27
|
<WordSpacingControl />
|
|
26
28
|
<Divider />
|
|
29
|
+
<TextAlignmentControl />
|
|
27
30
|
<TextStyleControl />
|
|
28
31
|
<TransformControl />
|
|
32
|
+
<TextDirectionControl />
|
|
29
33
|
</Stack>
|
|
30
34
|
</CollapsibleContent>
|
|
31
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
|
-
<
|
|
11
|
-
<
|
|
12
|
-
|
|
13
|
-
|
|
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
|
};
|
|
@@ -8,6 +8,7 @@ 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';
|
|
11
12
|
|
|
12
13
|
const CLASSES_PROP_KEY = 'classes';
|
|
13
14
|
|
|
@@ -22,6 +23,7 @@ export const StyleTab = () => {
|
|
|
22
23
|
<PositionSection />
|
|
23
24
|
<TypographySection />
|
|
24
25
|
<SpacingSection />
|
|
26
|
+
<EffectsSection />
|
|
25
27
|
</Stack>
|
|
26
28
|
</StyleContext>
|
|
27
29
|
);
|
|
@@ -30,7 +32,9 @@ export const StyleTab = () => {
|
|
|
30
32
|
function useClassesProp(): string {
|
|
31
33
|
const { elementType } = useElementContext();
|
|
32
34
|
|
|
33
|
-
const prop = Object.entries( elementType.propsSchema ).find(
|
|
35
|
+
const prop = Object.entries( elementType.propsSchema ).find(
|
|
36
|
+
( [ , propType ] ) => propType.kind === 'array' && propType.key === CLASSES_PROP_KEY
|
|
37
|
+
);
|
|
34
38
|
|
|
35
39
|
if ( ! prop ) {
|
|
36
40
|
throw new Error( 'Element does not have a classes prop' );
|
|
@@ -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
|
+
};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { bindPopover, bindToggle, IconButton, Popover, Stack, Tooltip, Typography, usePopupState } from '@elementor/ui';
|
|
3
|
+
import { ComponentType, ElementType as ReactElementType, useId } from 'react';
|
|
4
|
+
import { XIcon } from '@elementor/icons';
|
|
5
|
+
|
|
6
|
+
const SIZE = 'tiny';
|
|
7
|
+
|
|
8
|
+
export type PopoverActionProps = {
|
|
9
|
+
title: string;
|
|
10
|
+
visible?: boolean;
|
|
11
|
+
icon: ReactElementType;
|
|
12
|
+
popoverContent: ComponentType< { closePopover: () => void } >;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export default function PopoverAction( {
|
|
16
|
+
title,
|
|
17
|
+
visible = true,
|
|
18
|
+
icon: Icon,
|
|
19
|
+
popoverContent: PopoverContent,
|
|
20
|
+
}: PopoverActionProps ) {
|
|
21
|
+
const id = useId();
|
|
22
|
+
const popupState = usePopupState( {
|
|
23
|
+
variant: 'popover',
|
|
24
|
+
popupId: `elementor-popover-action-${ id }`,
|
|
25
|
+
} );
|
|
26
|
+
|
|
27
|
+
if ( ! visible ) {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<>
|
|
33
|
+
<Tooltip placement="top" title={ title }>
|
|
34
|
+
<IconButton aria-label={ title } key={ id } size={ SIZE } { ...bindToggle( popupState ) }>
|
|
35
|
+
<Icon fontSize={ SIZE } />
|
|
36
|
+
</IconButton>
|
|
37
|
+
</Tooltip>
|
|
38
|
+
<Popover
|
|
39
|
+
disablePortal
|
|
40
|
+
disableScrollLock
|
|
41
|
+
anchorOrigin={ {
|
|
42
|
+
vertical: 'bottom',
|
|
43
|
+
horizontal: 'center',
|
|
44
|
+
} }
|
|
45
|
+
{ ...bindPopover( popupState ) }
|
|
46
|
+
>
|
|
47
|
+
<Stack direction="row" alignItems="center" pl={ 1.5 } pr={ 0.5 } py={ 1.5 }>
|
|
48
|
+
<Icon fontSize={ SIZE } sx={ { mr: 0.5 } } />
|
|
49
|
+
<Typography variant="subtitle2">{ title }</Typography>
|
|
50
|
+
<IconButton sx={ { ml: 'auto' } } size={ SIZE } onClick={ popupState.close }>
|
|
51
|
+
<XIcon fontSize={ SIZE } />
|
|
52
|
+
</IconButton>
|
|
53
|
+
</Stack>
|
|
54
|
+
<PopoverContent closePopover={ popupState.close } />
|
|
55
|
+
</Popover>
|
|
56
|
+
</>
|
|
57
|
+
);
|
|
58
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { styled, UnstableFloatingActionBar } from '@elementor/ui';
|
|
3
|
+
import { PropsWithChildren } from 'react';
|
|
4
|
+
import { controlActionsMenu } from './control-actions-menu';
|
|
5
|
+
|
|
6
|
+
const { useMenuItems } = controlActionsMenu;
|
|
7
|
+
|
|
8
|
+
// CSS hack to hide empty floating bars.
|
|
9
|
+
const FloatingBar = styled( UnstableFloatingActionBar )`
|
|
10
|
+
& .MuiPaper-root:empty {
|
|
11
|
+
display: none;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// this is for a fix which would be added later on - to force the width externally
|
|
15
|
+
width: 100%;
|
|
16
|
+
& > :first-of-type {
|
|
17
|
+
width: 100%;
|
|
18
|
+
}
|
|
19
|
+
`;
|
|
20
|
+
|
|
21
|
+
export type ControlActionsProps = PropsWithChildren< {
|
|
22
|
+
fullWidth?: boolean;
|
|
23
|
+
} >;
|
|
24
|
+
|
|
25
|
+
export default function ControlActions( { fullWidth = false, children }: ControlActionsProps ) {
|
|
26
|
+
const items = useMenuItems().default;
|
|
27
|
+
|
|
28
|
+
if ( items.length === 0 ) {
|
|
29
|
+
return children;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<FloatingBar
|
|
34
|
+
actions={ items.map( ( { MenuItem, id } ) => (
|
|
35
|
+
<MenuItem key={ id } />
|
|
36
|
+
) ) }
|
|
37
|
+
// TODO - work on a general layouting solution instead
|
|
38
|
+
sx={ fullWidth ? { width: '100%' } : undefined }
|
|
39
|
+
>
|
|
40
|
+
{ children }
|
|
41
|
+
</FloatingBar>
|
|
42
|
+
);
|
|
43
|
+
}
|