@elementor/editor-editing-panel 0.19.0 → 1.1.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 +97 -0
- package/dist/index.d.mts +10 -36
- package/dist/index.d.ts +10 -36
- package/dist/index.js +1256 -1445
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1311 -1482
- package/dist/index.mjs.map +1 -1
- package/package.json +15 -14
- package/src/components/add-or-remove-content.tsx +42 -0
- package/src/components/collapse-icon.tsx +12 -0
- package/src/components/collapsible-content.tsx +5 -14
- package/src/components/collapsible-field.tsx +36 -0
- package/src/components/css-class-selector-section.tsx +76 -0
- package/src/components/editing-panel-hooks.tsx +2 -0
- package/src/components/editing-panel-tabs.tsx +23 -13
- package/src/components/editing-panel.tsx +21 -21
- package/src/components/multi-combobox/index.ts +3 -0
- package/src/components/multi-combobox/multi-combobox.tsx +120 -0
- package/src/components/multi-combobox/types.ts +26 -0
- package/src/components/multi-combobox/use-combobox-actions.ts +62 -0
- package/src/components/section.tsx +37 -0
- package/src/components/sections-list.tsx +6 -0
- package/src/components/settings-tab.tsx +17 -16
- package/src/components/style-sections/background-section/background-color-field.tsx +21 -0
- package/src/components/style-sections/background-section/background-section.tsx +10 -8
- package/src/components/style-sections/border-section/border-color-field.tsx +21 -0
- package/src/components/style-sections/border-section/border-field.tsx +48 -0
- package/src/components/style-sections/border-section/border-radius-field.tsx +49 -0
- package/src/components/style-sections/border-section/border-section.tsx +13 -0
- package/src/components/style-sections/border-section/border-style-field.tsx +32 -0
- package/src/components/style-sections/border-section/border-width-field.tsx +43 -0
- package/src/components/style-sections/effects-section/effects-section.tsx +8 -11
- package/src/components/style-sections/layout-section/display-field.tsx +32 -0
- package/src/components/style-sections/layout-section/justify-content-field.tsx +82 -0
- package/src/components/style-sections/layout-section/layout-section.tsx +17 -0
- package/src/components/style-sections/layout-section/utils/rotate-flex-icon.ts +12 -0
- package/src/components/style-sections/position-section/dimensions-field.tsx +46 -0
- package/src/components/style-sections/position-section/position-field.tsx +28 -0
- package/src/components/style-sections/position-section/position-section.tsx +51 -8
- package/src/components/style-sections/position-section/z-index-field.tsx +21 -0
- package/src/components/style-sections/size-section/overflow-field.tsx +45 -0
- package/src/components/style-sections/size-section/size-section.tsx +62 -0
- package/src/components/style-sections/spacing-section/spacing-section.tsx +12 -14
- package/src/components/style-sections/typography-section/font-family-field.tsx +40 -0
- package/src/components/style-sections/typography-section/font-size-field.tsx +21 -0
- package/src/components/style-sections/typography-section/{font-weight-control.tsx → font-weight-field.tsx} +9 -8
- package/src/components/style-sections/typography-section/letter-spacing-field.tsx +21 -0
- package/src/components/style-sections/typography-section/text-alignment-field.tsx +47 -0
- package/src/components/style-sections/typography-section/text-color-field.tsx +21 -0
- package/src/components/style-sections/typography-section/{text-direction-control.tsx → text-direction-field.tsx} +12 -12
- package/src/components/style-sections/typography-section/text-stroke-field.tsx +16 -0
- package/src/components/style-sections/typography-section/{text-style-control.tsx → text-style-field.tsx} +9 -8
- package/src/components/style-sections/typography-section/transform-field.tsx +40 -0
- package/src/components/style-sections/typography-section/typography-section.tsx +31 -30
- package/src/components/style-sections/typography-section/word-spacing-field.tsx +21 -0
- package/src/components/style-tab.tsx +82 -29
- package/src/contexts/classes-prop-context.tsx +24 -0
- package/src/{controls/providers/element-provider.tsx → contexts/element-context.tsx} +3 -7
- package/src/contexts/style-context.tsx +10 -23
- package/src/control-replacement.tsx +1 -1
- package/src/{controls/control-actions/control-actions-menu.ts → controls-actions.ts} +2 -1
- package/src/{controls/components → controls-registry}/control-type-container.tsx +3 -2
- package/src/{controls → controls-registry}/control.tsx +2 -1
- package/src/{controls → controls-registry}/controls-registry.tsx +8 -6
- package/src/controls-registry/settings-field.tsx +36 -0
- package/src/controls-registry/styles-field.tsx +20 -0
- package/src/dynamics/components/dynamic-selection-control.tsx +18 -17
- package/src/dynamics/components/dynamic-selection.tsx +10 -9
- package/src/dynamics/dynamic-control.tsx +7 -6
- package/src/dynamics/hooks/use-dynamic-tag.ts +3 -2
- package/src/dynamics/hooks/use-prop-dynamic-action.tsx +7 -6
- package/src/dynamics/hooks/use-prop-dynamic-tags.ts +3 -2
- package/src/dynamics/init.ts +3 -3
- package/src/dynamics/sync/get-elementor-config.ts +1 -1
- package/src/dynamics/types.ts +2 -2
- package/src/dynamics/utils.ts +3 -3
- package/src/hooks/use-close-editor-panel.ts +23 -0
- package/src/hooks/use-direction.ts +13 -0
- package/src/hooks/use-open-editor-panel.ts +4 -3
- package/src/hooks/use-prop-value-history.ts +45 -0
- package/src/hooks/use-style-prop-history.ts +75 -0
- package/src/hooks/use-styles-field.ts +51 -0
- package/src/index.ts +2 -3
- package/src/init.ts +5 -4
- package/src/panel.ts +1 -0
- package/src/{controls/control-actions/actions/popover-action.tsx → popover-action.tsx} +2 -2
- package/src/sync/enqueue-font.ts +7 -0
- package/src/sync/get-elementor-config.ts +7 -0
- package/src/sync/{should-use-v2-panel.ts → is-atomic-widget-selected.ts} +2 -3
- package/src/sync/types.ts +20 -21
- package/src/components/accordion-section.tsx +0 -25
- package/src/components/control-label.tsx +0 -10
- package/src/components/style-sections/background-section/background-color-control.tsx +0 -20
- package/src/components/style-sections/effects-section/box-shadow-repeater.tsx +0 -224
- package/src/components/style-sections/position-section/z-index-control.tsx +0 -20
- package/src/components/style-sections/size-section.tsx +0 -49
- package/src/components/style-sections/spacing-section/linked-dimensions-control.tsx +0 -155
- package/src/components/style-sections/typography-section/font-size-control.tsx +0 -20
- package/src/components/style-sections/typography-section/letter-spacing-control.tsx +0 -20
- package/src/components/style-sections/typography-section/text-alignment-control.tsx +0 -47
- package/src/components/style-sections/typography-section/text-color-control.tsx +0 -20
- package/src/components/style-sections/typography-section/transform-control.tsx +0 -25
- package/src/components/style-sections/typography-section/word-spacing-control.tsx +0 -20
- package/src/controls/components/control-toggle-button-group.tsx +0 -59
- package/src/controls/components/repeater.tsx +0 -197
- package/src/controls/components/text-field-inner-selection.tsx +0 -79
- package/src/controls/control-actions/control-actions.tsx +0 -43
- package/src/controls/control-context.tsx +0 -22
- package/src/controls/control-replacement.ts +0 -34
- package/src/controls/control-types/color-control.tsx +0 -27
- package/src/controls/control-types/image-control.tsx +0 -66
- package/src/controls/control-types/image-media-control.tsx +0 -73
- package/src/controls/control-types/number-control.tsx +0 -29
- package/src/controls/control-types/select-control.tsx +0 -30
- package/src/controls/control-types/size-control.tsx +0 -71
- package/src/controls/control-types/text-area-control.tsx +0 -31
- package/src/controls/control-types/text-control.tsx +0 -17
- package/src/controls/control-types/toggle-control.tsx +0 -26
- package/src/controls/create-control-replacement.tsx +0 -53
- package/src/controls/create-control.tsx +0 -40
- package/src/controls/hooks/use-style-control.ts +0 -29
- package/src/controls/hooks/use-sync-external-state.tsx +0 -51
- package/src/controls/hooks/use-widget-settings.ts +0 -16
- package/src/controls/props/is-transformable.ts +0 -13
- package/src/controls/props/types.ts +0 -51
- package/src/controls/settings-control.tsx +0 -37
- package/src/controls/style-control.tsx +0 -20
- package/src/controls/sync/get-container.ts +0 -8
- package/src/controls/sync/update-settings.ts +0 -14
- package/src/controls/types.ts +0 -39
- package/src/dynamics/hooks/use-prop-value-history.ts +0 -26
- package/src/hooks/use-element-style-prop.ts +0 -46
- package/src/hooks/use-element-styles.ts +0 -13
- package/src/hooks/use-element-type.ts +0 -33
- package/src/hooks/use-selected-elements.ts +0 -9
- package/src/sync/get-element-styles.ts +0 -9
- package/src/sync/get-selected-elements.ts +0 -21
- package/src/sync/get-widgets-cache.ts +0 -7
- package/src/sync/update-style.ts +0 -25
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elementor/editor-editing-panel",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"author": "Elementor Team",
|
|
6
6
|
"homepage": "https://elementor.com/",
|
|
@@ -10,9 +10,9 @@
|
|
|
10
10
|
"types": "dist/index.d.ts",
|
|
11
11
|
"exports": {
|
|
12
12
|
".": {
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
13
14
|
"import": "./dist/index.mjs",
|
|
14
|
-
"require": "./dist/index.js"
|
|
15
|
-
"types": "./dist/index.d.ts"
|
|
15
|
+
"require": "./dist/index.js"
|
|
16
16
|
},
|
|
17
17
|
"./package.json": "./package.json"
|
|
18
18
|
},
|
|
@@ -39,17 +39,18 @@
|
|
|
39
39
|
"dev": "tsup --config=../../tsup.dev.ts"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@elementor/editor": "^0.
|
|
43
|
-
"@elementor/editor-
|
|
44
|
-
"@elementor/editor-
|
|
45
|
-
"@elementor/
|
|
46
|
-
"@elementor/editor-
|
|
47
|
-
"@elementor/
|
|
48
|
-
"@elementor/
|
|
49
|
-
"@elementor/
|
|
50
|
-
"@elementor/
|
|
51
|
-
"@elementor/
|
|
52
|
-
"@elementor/
|
|
42
|
+
"@elementor/editor": "^0.17.0",
|
|
43
|
+
"@elementor/editor-controls": "^0.1.0",
|
|
44
|
+
"@elementor/editor-elements": "^0.3.0",
|
|
45
|
+
"@elementor/menus": "^0.1.1",
|
|
46
|
+
"@elementor/editor-props": "^0.3.0",
|
|
47
|
+
"@elementor/editor-panels": "^0.10.0",
|
|
48
|
+
"@elementor/editor-responsive": "^0.12.2",
|
|
49
|
+
"@elementor/editor-styles": "^0.2.1",
|
|
50
|
+
"@elementor/editor-v1-adapters": "^0.8.4",
|
|
51
|
+
"@elementor/icons": "^1.19.0",
|
|
52
|
+
"@elementor/ui": "^1.21.13",
|
|
53
|
+
"@elementor/utils": "^0.3.0",
|
|
53
54
|
"@wordpress/i18n": "^4.45.0"
|
|
54
55
|
},
|
|
55
56
|
"peerDependencies": {
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { type PropsWithChildren } from 'react';
|
|
3
|
+
import { ControlLabel } from '@elementor/editor-controls';
|
|
4
|
+
import { MinusIcon, PlusIcon } from '@elementor/icons';
|
|
5
|
+
import { Collapse, IconButton, Stack } from '@elementor/ui';
|
|
6
|
+
|
|
7
|
+
const SIZE = 'tiny';
|
|
8
|
+
|
|
9
|
+
type Props = {
|
|
10
|
+
label: string;
|
|
11
|
+
isAdded: boolean;
|
|
12
|
+
onAdd: () => void;
|
|
13
|
+
onRemove: () => void;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export const AddOrRemoveContent = ( { isAdded, label, onAdd, onRemove, children }: PropsWithChildren< Props > ) => {
|
|
17
|
+
return (
|
|
18
|
+
<Stack gap={ 1.5 }>
|
|
19
|
+
<Stack
|
|
20
|
+
direction="row"
|
|
21
|
+
sx={ {
|
|
22
|
+
justifyContent: 'space-between',
|
|
23
|
+
alignItems: 'center',
|
|
24
|
+
} }
|
|
25
|
+
>
|
|
26
|
+
<ControlLabel>{ label }</ControlLabel>
|
|
27
|
+
{ isAdded ? (
|
|
28
|
+
<IconButton size={ SIZE } onClick={ onRemove }>
|
|
29
|
+
<MinusIcon fontSize={ SIZE } />
|
|
30
|
+
</IconButton>
|
|
31
|
+
) : (
|
|
32
|
+
<IconButton size={ SIZE } onClick={ onAdd }>
|
|
33
|
+
<PlusIcon fontSize={ SIZE } />
|
|
34
|
+
</IconButton>
|
|
35
|
+
) }
|
|
36
|
+
</Stack>
|
|
37
|
+
<Collapse in={ isAdded } unmountOnExit>
|
|
38
|
+
<Stack gap={ 1.5 }>{ children }</Stack>
|
|
39
|
+
</Collapse>
|
|
40
|
+
</Stack>
|
|
41
|
+
);
|
|
42
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { ChevronDownIcon } from '@elementor/icons';
|
|
2
|
+
import { styled } from '@elementor/ui';
|
|
3
|
+
|
|
4
|
+
// TODO: Replace this with future Rotate component that will be implemented in elementor-ui
|
|
5
|
+
export const CollapseIcon = styled( ChevronDownIcon, {
|
|
6
|
+
shouldForwardProp: ( prop ) => prop !== 'open',
|
|
7
|
+
} )< { open: boolean } >( ( { theme, open } ) => ( {
|
|
8
|
+
transform: open ? 'rotate(180deg)' : 'rotate(0deg)',
|
|
9
|
+
transition: theme.transitions.create( 'transform', {
|
|
10
|
+
duration: theme.transitions.duration.standard,
|
|
11
|
+
} ),
|
|
12
|
+
} ) );
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import { useState } from 'react';
|
|
3
|
-
import {
|
|
4
|
-
import { Button, Collapse, Stack, styled } from '@elementor/ui';
|
|
3
|
+
import { Button, Collapse, Stack } from '@elementor/ui';
|
|
5
4
|
import { __ } from '@wordpress/i18n';
|
|
6
5
|
|
|
6
|
+
import { CollapseIcon } from './collapse-icon';
|
|
7
|
+
|
|
7
8
|
type CollapsibleContentProps = React.PropsWithChildren< {
|
|
8
9
|
defaultOpen?: boolean;
|
|
9
10
|
} >;
|
|
@@ -23,23 +24,13 @@ export const CollapsibleContent = ( { children, defaultOpen = false }: Collapsib
|
|
|
23
24
|
color="secondary"
|
|
24
25
|
variant="outlined"
|
|
25
26
|
onClick={ handleToggle }
|
|
26
|
-
endIcon={ <
|
|
27
|
+
endIcon={ <CollapseIcon open={ open } /> }
|
|
27
28
|
>
|
|
28
29
|
{ open ? __( 'Show less', 'elementor' ) : __( 'Show more', 'elementor' ) }
|
|
29
30
|
</Button>
|
|
30
|
-
<Collapse in={ open } timeout="auto">
|
|
31
|
+
<Collapse in={ open } timeout="auto" unmountOnExit>
|
|
31
32
|
{ children }
|
|
32
33
|
</Collapse>
|
|
33
34
|
</Stack>
|
|
34
35
|
);
|
|
35
36
|
};
|
|
36
|
-
|
|
37
|
-
// TODO: Replace this with future Rotate component that will be implemented in elementor-ui
|
|
38
|
-
const ChevronIcon = styled( ChevronDownIcon, {
|
|
39
|
-
shouldForwardProp: ( prop ) => prop !== 'open',
|
|
40
|
-
} )< { open: boolean } >( ( { theme, open } ) => ( {
|
|
41
|
-
transform: open ? 'rotate(180deg)' : 'rotate(0)',
|
|
42
|
-
transition: theme.transitions.create( 'transform', {
|
|
43
|
-
duration: theme.transitions.duration.standard,
|
|
44
|
-
} ),
|
|
45
|
-
} ) );
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { MinusIcon, PlusIcon } from '@elementor/icons';
|
|
4
|
+
import { Collapse, IconButton, Stack } from '@elementor/ui';
|
|
5
|
+
import { __ } from '@wordpress/i18n';
|
|
6
|
+
|
|
7
|
+
type CollapsibleFieldProps = React.PropsWithChildren< {
|
|
8
|
+
label: React.ReactNode;
|
|
9
|
+
defaultOpen?: boolean;
|
|
10
|
+
} >;
|
|
11
|
+
|
|
12
|
+
export const CollapsibleField = ( { label, children, defaultOpen = false }: CollapsibleFieldProps ) => {
|
|
13
|
+
const [ open, setOpen ] = useState( defaultOpen );
|
|
14
|
+
|
|
15
|
+
const handleToggle = () => {
|
|
16
|
+
setOpen( ( prevOpen ) => ! prevOpen );
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<Stack gap={ 1.5 }>
|
|
21
|
+
<Stack direction="row" justifyContent="space-between" alignItems="center" sx={ { py: 0.5 } }>
|
|
22
|
+
{ label }
|
|
23
|
+
<IconButton
|
|
24
|
+
onClick={ handleToggle }
|
|
25
|
+
size="tiny"
|
|
26
|
+
aria-label={ open ? __( 'Close', 'elementor' ) : __( 'Expand', 'elementor' ) }
|
|
27
|
+
>
|
|
28
|
+
{ open ? <MinusIcon fontSize="tiny" /> : <PlusIcon fontSize="tiny" /> }
|
|
29
|
+
</IconButton>
|
|
30
|
+
</Stack>
|
|
31
|
+
<Collapse in={ open } unmountOnExit>
|
|
32
|
+
{ children }
|
|
33
|
+
</Collapse>
|
|
34
|
+
</Stack>
|
|
35
|
+
);
|
|
36
|
+
};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { useElementSetting, useElementStyles } from '@elementor/editor-elements';
|
|
3
|
+
import { type ClassesPropValue } from '@elementor/editor-props';
|
|
4
|
+
import { Chip, Stack, Typography } from '@elementor/ui';
|
|
5
|
+
import { __ } from '@wordpress/i18n';
|
|
6
|
+
|
|
7
|
+
import { useClassesProp } from '../contexts/classes-prop-context';
|
|
8
|
+
import { useElement } from '../contexts/element-context';
|
|
9
|
+
import { useStyle } from '../contexts/style-context';
|
|
10
|
+
import { MultiCombobox, type Option } from './multi-combobox';
|
|
11
|
+
|
|
12
|
+
const ID = 'elementor-css-class-selector';
|
|
13
|
+
const TAGS_LIMIT = 8;
|
|
14
|
+
|
|
15
|
+
export function CssClassSelectorSection() {
|
|
16
|
+
const options = useOptions();
|
|
17
|
+
|
|
18
|
+
const { id: activeId, setId: setActiveId } = useStyle();
|
|
19
|
+
const appliedIds = useAppliedClassesIds();
|
|
20
|
+
|
|
21
|
+
const applied = options.filter( ( option ) => appliedIds.includes( option.value ) );
|
|
22
|
+
const active = options.find( ( option ) => option.value === activeId ) || null;
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<Stack gap={ 1 } p={ 2 }>
|
|
26
|
+
<Typography component="label" variant="caption" htmlFor={ ID }>
|
|
27
|
+
{ __( 'CSS Classes', 'elementor' ) }
|
|
28
|
+
</Typography>
|
|
29
|
+
<MultiCombobox
|
|
30
|
+
id={ ID }
|
|
31
|
+
size="tiny"
|
|
32
|
+
options={ options }
|
|
33
|
+
selected={ applied }
|
|
34
|
+
limitTags={ TAGS_LIMIT }
|
|
35
|
+
optionsLabel={ __( 'Global CSS Classes', 'elementor' ) }
|
|
36
|
+
renderTags={ ( tagValue, getTagProps ) =>
|
|
37
|
+
tagValue.map( ( option, index ) => {
|
|
38
|
+
const chipProps = getTagProps( { index } );
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<Chip
|
|
42
|
+
{ ...chipProps }
|
|
43
|
+
key={ chipProps.key }
|
|
44
|
+
size="small"
|
|
45
|
+
label={ option.label }
|
|
46
|
+
variant={ option.value === active?.value ? 'filled' : 'standard' }
|
|
47
|
+
color={ option.color ?? 'default' }
|
|
48
|
+
onClick={ () => setActiveId( option.value ) }
|
|
49
|
+
onDelete={ null }
|
|
50
|
+
/>
|
|
51
|
+
);
|
|
52
|
+
} )
|
|
53
|
+
}
|
|
54
|
+
/>
|
|
55
|
+
</Stack>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function useAppliedClassesIds() {
|
|
60
|
+
const { element } = useElement();
|
|
61
|
+
const currentClassesProp = useClassesProp();
|
|
62
|
+
|
|
63
|
+
return useElementSetting< ClassesPropValue >( element.id, currentClassesProp )?.value || [];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function useOptions() {
|
|
67
|
+
const { element } = useElement();
|
|
68
|
+
|
|
69
|
+
const styleDefs = useElementStyles( element.id );
|
|
70
|
+
|
|
71
|
+
return Object.values( styleDefs ).map< Option >( ( styleDef ) => ( {
|
|
72
|
+
label: styleDef.label,
|
|
73
|
+
value: styleDef.id,
|
|
74
|
+
color: 'primary',
|
|
75
|
+
} ) );
|
|
76
|
+
}
|
|
@@ -1,26 +1,36 @@
|
|
|
1
|
-
import { Stack, Tabs, Tab, TabPanel, useTabs } from '@elementor/ui';
|
|
2
1
|
import * as React from 'react';
|
|
2
|
+
import { Fragment } from 'react';
|
|
3
|
+
import { Divider, Stack, Tab, TabPanel, Tabs, useTabs } from '@elementor/ui';
|
|
3
4
|
import { __ } from '@wordpress/i18n';
|
|
5
|
+
|
|
6
|
+
import { useElement } from '../contexts/element-context';
|
|
4
7
|
import { SettingsTab } from './settings-tab';
|
|
5
8
|
import { StyleTab } from './style-tab';
|
|
6
9
|
|
|
7
10
|
type TabValue = 'settings' | 'style';
|
|
8
11
|
|
|
9
12
|
export const EditingPanelTabs = () => {
|
|
13
|
+
const { element } = useElement();
|
|
14
|
+
|
|
10
15
|
const { getTabProps, getTabPanelProps, getTabsProps } = useTabs< TabValue >( 'settings' );
|
|
11
16
|
|
|
12
17
|
return (
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
<
|
|
23
|
-
|
|
24
|
-
|
|
18
|
+
// When switching between elements, the local states should be reset. We are using key to rerender the tabs.
|
|
19
|
+
// Reference: https://react.dev/learn/preserving-and-resetting-state#resetting-a-form-with-a-key
|
|
20
|
+
<Fragment key={ element.id }>
|
|
21
|
+
<Stack direction="column" sx={ { width: '100%' } }>
|
|
22
|
+
<Tabs variant="fullWidth" indicatorColor="secondary" textColor="inherit" { ...getTabsProps() }>
|
|
23
|
+
<Tab label={ __( 'General', 'elementor' ) } { ...getTabProps( 'settings' ) } />
|
|
24
|
+
<Tab label={ __( 'Style', 'elementor' ) } { ...getTabProps( 'style' ) } />
|
|
25
|
+
</Tabs>
|
|
26
|
+
<Divider />
|
|
27
|
+
<TabPanel { ...getTabPanelProps( 'settings' ) } disablePadding>
|
|
28
|
+
<SettingsTab />
|
|
29
|
+
</TabPanel>
|
|
30
|
+
<TabPanel { ...getTabPanelProps( 'style' ) } disablePadding>
|
|
31
|
+
<StyleTab />
|
|
32
|
+
</TabPanel>
|
|
33
|
+
</Stack>
|
|
34
|
+
</Fragment>
|
|
25
35
|
);
|
|
26
36
|
};
|
|
@@ -1,29 +1,27 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
2
|
+
import { ControlActionsProvider, ControlReplacementProvider } from '@elementor/editor-controls';
|
|
3
|
+
import { useSelectedElement } from '@elementor/editor-elements';
|
|
4
4
|
import { Panel, PanelBody, PanelHeader, PanelHeaderTitle } from '@elementor/editor-panels';
|
|
5
|
-
import { ElementProvider } from '../controls/providers/element-provider';
|
|
6
|
-
import useElementType from '../hooks/use-element-type';
|
|
7
|
-
import { EditingPanelTabs } from './editing-panel-tabs';
|
|
8
|
-
import { ControlReplacementProvider } from '../controls/create-control-replacement';
|
|
9
|
-
import { getControlReplacement } from '../control-replacement';
|
|
10
5
|
import { ErrorBoundary } from '@elementor/ui';
|
|
11
|
-
import {
|
|
6
|
+
import { __ } from '@wordpress/i18n';
|
|
12
7
|
|
|
13
|
-
|
|
14
|
-
|
|
8
|
+
import { ElementProvider } from '../contexts/element-context';
|
|
9
|
+
import { getControlReplacement } from '../control-replacement';
|
|
10
|
+
import { controlActionsMenu } from '../controls-actions';
|
|
11
|
+
import { EditorPanelErrorFallback } from './editing-panel-error-fallback';
|
|
12
|
+
import { EditingPanelTabs } from './editing-panel-tabs';
|
|
15
13
|
|
|
16
|
-
|
|
14
|
+
const { useMenuItems } = controlActionsMenu;
|
|
17
15
|
|
|
18
|
-
|
|
19
|
-
const elementType =
|
|
16
|
+
export const EditingPanel = () => {
|
|
17
|
+
const { element, elementType } = useSelectedElement();
|
|
18
|
+
const controlReplacement = getControlReplacement();
|
|
19
|
+
const menuItems = useMenuItems().default;
|
|
20
20
|
|
|
21
|
-
if (
|
|
21
|
+
if ( ! element || ! elementType ) {
|
|
22
22
|
return null;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
const controlReplacement = getControlReplacement();
|
|
26
|
-
|
|
27
25
|
/* translators: %s: Element type title. */
|
|
28
26
|
const panelTitle = __( 'Edit %s', 'elementor' ).replace( '%s', elementType.title );
|
|
29
27
|
|
|
@@ -34,11 +32,13 @@ export const EditingPanel = () => {
|
|
|
34
32
|
<PanelHeaderTitle>{ panelTitle }</PanelHeaderTitle>
|
|
35
33
|
</PanelHeader>
|
|
36
34
|
<PanelBody>
|
|
37
|
-
<
|
|
38
|
-
<
|
|
39
|
-
<
|
|
40
|
-
|
|
41
|
-
|
|
35
|
+
<ControlActionsProvider items={ menuItems }>
|
|
36
|
+
<ControlReplacementProvider { ...controlReplacement }>
|
|
37
|
+
<ElementProvider element={ element } elementType={ elementType }>
|
|
38
|
+
<EditingPanelTabs />
|
|
39
|
+
</ElementProvider>
|
|
40
|
+
</ControlReplacementProvider>
|
|
41
|
+
</ControlActionsProvider>
|
|
42
42
|
</PanelBody>
|
|
43
43
|
</Panel>
|
|
44
44
|
</ErrorBoundary>
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import {
|
|
3
|
+
Autocomplete,
|
|
4
|
+
type AutocompleteProps,
|
|
5
|
+
type AutocompleteRenderGroupParams,
|
|
6
|
+
Box,
|
|
7
|
+
Chip,
|
|
8
|
+
styled,
|
|
9
|
+
TextField,
|
|
10
|
+
} from '@elementor/ui';
|
|
11
|
+
import { type FilterOptionsState } from '@mui/base';
|
|
12
|
+
|
|
13
|
+
import { type ActionOption, type Actions, type Option } from './types';
|
|
14
|
+
import { useComboboxActions } from './use-combobox-actions';
|
|
15
|
+
|
|
16
|
+
type Props = Omit< AutocompleteProps< Option, true, true, true >, 'renderInput' | 'getLimitTagsText' > & {
|
|
17
|
+
actions?: Actions;
|
|
18
|
+
selected: Option[];
|
|
19
|
+
options: Option[];
|
|
20
|
+
optionsLabel?: string;
|
|
21
|
+
onApply?: ( value: Option[] ) => void;
|
|
22
|
+
onCreate?: ( value: string ) => void;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export const MultiCombobox = ( {
|
|
26
|
+
actions = {},
|
|
27
|
+
selected,
|
|
28
|
+
options,
|
|
29
|
+
optionsLabel,
|
|
30
|
+
onApply,
|
|
31
|
+
onCreate,
|
|
32
|
+
...props
|
|
33
|
+
}: Props ) => {
|
|
34
|
+
const { action: actionProps, option: optionProps } = useComboboxActions( selected, actions, optionsLabel, onApply );
|
|
35
|
+
|
|
36
|
+
const handleSelectOption = ( values: Array< ActionOption | Option | string > ) => {
|
|
37
|
+
const action = values.find( ( value ) => actionProps.is( value as ActionOption ) );
|
|
38
|
+
|
|
39
|
+
if ( action ) {
|
|
40
|
+
return actionProps.onChange( action as ActionOption );
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return optionProps.onChange( values as Option[] );
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const handleCreateOption = ( values: Array< ActionOption | Option | string > ) => {
|
|
47
|
+
const value = values.find( ( option ) => typeof option === 'string' );
|
|
48
|
+
|
|
49
|
+
onCreate?.( value as string );
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
<Autocomplete
|
|
54
|
+
{ ...props }
|
|
55
|
+
freeSolo
|
|
56
|
+
multiple
|
|
57
|
+
clearOnBlur
|
|
58
|
+
selectOnFocus
|
|
59
|
+
disableClearable
|
|
60
|
+
handleHomeEndKeys
|
|
61
|
+
value={ selected }
|
|
62
|
+
options={ options }
|
|
63
|
+
renderGroup={ renderGroup }
|
|
64
|
+
renderInput={ ( params ) => <TextField { ...params } /> }
|
|
65
|
+
getLimitTagsText={ ( more ) => <Chip size="tiny" variant="standard" label={ `+${ more }` } clickable /> }
|
|
66
|
+
onChange={ ( _, values, reason ) => {
|
|
67
|
+
if ( reason === 'selectOption' ) {
|
|
68
|
+
return handleSelectOption( values );
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if ( reason === 'createOption' ) {
|
|
72
|
+
return handleCreateOption( values );
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
onApply?.( values as ActionOption[] );
|
|
76
|
+
} }
|
|
77
|
+
getOptionLabel={ ( option ) => {
|
|
78
|
+
if ( optionProps.is( option as ActionOption ) ) {
|
|
79
|
+
return optionProps.getLabel( option as Option );
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return actionProps.getLabel( option as ActionOption ) ?? '';
|
|
83
|
+
} }
|
|
84
|
+
filterOptions={ ( optionList: Option[], params: FilterOptionsState< ActionOption | Option > ) => {
|
|
85
|
+
const filteredoptions = optionProps.getFilteredOptions( optionList, params );
|
|
86
|
+
|
|
87
|
+
const actionOptions = actionProps.getFilteredActions( optionList, params );
|
|
88
|
+
|
|
89
|
+
return [ ...actionOptions, ...filteredoptions ];
|
|
90
|
+
} }
|
|
91
|
+
groupBy={ ( option ) =>
|
|
92
|
+
( optionProps.is( option ) ? optionProps.groupBy() : actionProps.groupBy( option ) ) ?? ''
|
|
93
|
+
}
|
|
94
|
+
/>
|
|
95
|
+
);
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
export const renderGroup = ( params: AutocompleteRenderGroupParams ) => (
|
|
99
|
+
<Group key={ params.key }>
|
|
100
|
+
<GroupHeader>{ params.group }</GroupHeader>
|
|
101
|
+
<GroupItems>{ params.children }</GroupItems>
|
|
102
|
+
</Group>
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
const Group = styled( 'li' )`
|
|
106
|
+
&:not( :last-of-type ) {
|
|
107
|
+
border-bottom: 1px solid ${ ( { theme } ) => theme.palette.divider };
|
|
108
|
+
}
|
|
109
|
+
`;
|
|
110
|
+
|
|
111
|
+
const GroupHeader = styled( Box )( ( { theme } ) => ( {
|
|
112
|
+
position: 'sticky',
|
|
113
|
+
top: '-8px',
|
|
114
|
+
padding: theme.spacing( 1, 2 ),
|
|
115
|
+
color: theme.palette.text.tertiary,
|
|
116
|
+
} ) );
|
|
117
|
+
|
|
118
|
+
const GroupItems = styled( 'ul' )`
|
|
119
|
+
padding: 0;
|
|
120
|
+
`;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export type Option = {
|
|
2
|
+
label: string;
|
|
3
|
+
value: string;
|
|
4
|
+
color?: 'primary' | 'global';
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export type Action = {
|
|
8
|
+
getLabel: ( inputValue: string ) => string;
|
|
9
|
+
apply: ( value: string ) => void;
|
|
10
|
+
condition: ( options: Option[], inputValue: string ) => boolean;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export type ActionsGroup = {
|
|
14
|
+
label: string;
|
|
15
|
+
actions: Action[];
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export type ActionOption = Option & {
|
|
19
|
+
action: {
|
|
20
|
+
groupLabel: string;
|
|
21
|
+
apply: Action[ 'apply' ];
|
|
22
|
+
getLabel: Action[ 'getLabel' ];
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export type Actions = Record< string, ActionsGroup >;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { createFilterOptions } from '@elementor/ui';
|
|
2
|
+
import { type FilterOptionsState } from '@mui/base';
|
|
3
|
+
|
|
4
|
+
import { type Action, type ActionOption, type Actions, type Option } from './types';
|
|
5
|
+
|
|
6
|
+
export const useComboboxActions = (
|
|
7
|
+
applied: Option[],
|
|
8
|
+
actions: Actions,
|
|
9
|
+
optionsLabel?: string,
|
|
10
|
+
onApply?: ( value: Option[] ) => void
|
|
11
|
+
) => ( {
|
|
12
|
+
action: {
|
|
13
|
+
is: ( opt: ActionOption ): opt is ActionOption => !! opt.action,
|
|
14
|
+
getLabel: ( option: ActionOption ) => option.action.getLabel( option.label ),
|
|
15
|
+
groupBy: ( option: ActionOption ) => option.action.groupLabel,
|
|
16
|
+
onChange: ( { action, label }: ActionOption ) => action?.apply( label ),
|
|
17
|
+
getFilteredActions: ( optionList: Option[], params: FilterOptionsState< ActionOption > ) => {
|
|
18
|
+
const actionGroups = Object.values( actions );
|
|
19
|
+
|
|
20
|
+
return actionGroups.reduce< Option[] >( ( groups, group ) => {
|
|
21
|
+
const actionOptions = group.actions.reduce< Option[] >( ( groupActions, action ) => {
|
|
22
|
+
const shouldShowAction = action.condition( optionList, params.inputValue );
|
|
23
|
+
|
|
24
|
+
if ( shouldShowAction ) {
|
|
25
|
+
const actionOption = createActionOption( group.label, action, params.inputValue );
|
|
26
|
+
groupActions.unshift( actionOption );
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return groupActions;
|
|
30
|
+
}, [] );
|
|
31
|
+
|
|
32
|
+
return [ ...groups, ...actionOptions ];
|
|
33
|
+
}, [] );
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
option: {
|
|
37
|
+
is: ( opt: ActionOption | Option ): opt is Option => ! ( 'action' in opt ),
|
|
38
|
+
getLabel: ( option: Option ) => option.label,
|
|
39
|
+
groupBy: () => optionsLabel ?? '',
|
|
40
|
+
onChange: ( optionValues: Option[] ) => onApply?.( optionValues ),
|
|
41
|
+
getFilteredOptions: ( optionList: Option[], params: FilterOptionsState< Option > ) => {
|
|
42
|
+
const appliedValues = applied.map( ( option ) => option.value );
|
|
43
|
+
|
|
44
|
+
const optionsWithoutApplied = optionList.filter( ( option ) => ! appliedValues.includes( option.value ) );
|
|
45
|
+
|
|
46
|
+
return filter( optionsWithoutApplied, params );
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
} );
|
|
50
|
+
|
|
51
|
+
// Helper functions.
|
|
52
|
+
const filter = createFilterOptions< Option >();
|
|
53
|
+
|
|
54
|
+
const createActionOption = ( groupLabel: string, action: Action, inputValue: string ): ActionOption => ( {
|
|
55
|
+
value: '',
|
|
56
|
+
label: inputValue,
|
|
57
|
+
action: {
|
|
58
|
+
groupLabel,
|
|
59
|
+
apply: action.apply,
|
|
60
|
+
getLabel: action.getLabel,
|
|
61
|
+
},
|
|
62
|
+
} );
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { type PropsWithChildren, useId, useState } from 'react';
|
|
3
|
+
import { Collapse, Divider, ListItemButton, ListItemText, Stack } from '@elementor/ui';
|
|
4
|
+
|
|
5
|
+
import { CollapseIcon } from './collapse-icon';
|
|
6
|
+
|
|
7
|
+
type Props = PropsWithChildren< {
|
|
8
|
+
title: string;
|
|
9
|
+
defaultExpanded?: boolean;
|
|
10
|
+
} >;
|
|
11
|
+
|
|
12
|
+
export function Section( { title, children, defaultExpanded = false }: Props ) {
|
|
13
|
+
const [ isOpen, setIsOpen ] = useState( !! defaultExpanded );
|
|
14
|
+
|
|
15
|
+
const id = useId();
|
|
16
|
+
const labelId = `label-${ id }`;
|
|
17
|
+
const contentId = `content-${ id }`;
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<>
|
|
21
|
+
<ListItemButton
|
|
22
|
+
id={ labelId }
|
|
23
|
+
aria-controls={ contentId }
|
|
24
|
+
onClick={ () => setIsOpen( ( prev ) => ! prev ) }
|
|
25
|
+
>
|
|
26
|
+
<ListItemText secondary={ title } />
|
|
27
|
+
<CollapseIcon open={ isOpen } color="secondary" />
|
|
28
|
+
</ListItemButton>
|
|
29
|
+
<Collapse id={ contentId } aria-labelledby={ labelId } in={ isOpen } timeout="auto" unmountOnExit>
|
|
30
|
+
<Stack gap={ 2.5 } p={ 2 }>
|
|
31
|
+
{ children }
|
|
32
|
+
</Stack>
|
|
33
|
+
</Collapse>
|
|
34
|
+
<Divider />
|
|
35
|
+
</>
|
|
36
|
+
);
|
|
37
|
+
}
|