@elementor/editor-editing-panel 1.0.0 → 1.2.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 +70 -0
- package/dist/index.d.mts +10 -19
- package/dist/index.d.ts +10 -19
- package/dist/index.js +1539 -1754
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1534 -1723
- package/dist/index.mjs.map +1 -1
- package/package.json +13 -13
- package/src/components/add-or-remove-content.tsx +3 -3
- package/src/components/collapse-icon.tsx +12 -0
- package/src/components/collapsible-content.tsx +5 -14
- package/src/components/collapsible-field.tsx +5 -3
- package/src/components/css-class-selector.tsx +131 -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 +9 -6
- package/src/components/multi-combobox/index.ts +3 -0
- package/src/components/multi-combobox/multi-combobox.tsx +122 -0
- package/src/components/multi-combobox/types.ts +28 -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 +11 -11
- package/src/components/style-sections/background-section/background-color-field.tsx +4 -4
- package/src/components/style-sections/background-section/background-section.tsx +9 -7
- package/src/components/style-sections/border-section/border-color-field.tsx +4 -4
- package/src/components/style-sections/border-section/border-field.tsx +4 -3
- package/src/components/style-sections/border-section/border-radius-field.tsx +4 -3
- package/src/components/style-sections/border-section/border-section.tsx +7 -10
- package/src/components/style-sections/border-section/border-style-field.tsx +4 -4
- package/src/components/style-sections/border-section/border-width-field.tsx +4 -3
- package/src/components/style-sections/effects-section/effects-section.tsx +7 -10
- package/src/components/style-sections/layout-section/align-items-field.tsx +92 -0
- package/src/components/style-sections/layout-section/display-field.tsx +32 -0
- package/src/components/style-sections/layout-section/flex-direction-field.tsx +64 -0
- package/src/components/style-sections/layout-section/flex-order-field.tsx +114 -0
- package/src/components/style-sections/layout-section/justify-content-field.tsx +109 -0
- package/src/components/style-sections/layout-section/layout-section.tsx +36 -0
- package/src/components/style-sections/layout-section/wrap-field.tsx +52 -0
- package/src/components/style-sections/position-section/dimensions-field.tsx +6 -6
- package/src/components/style-sections/position-section/position-field.tsx +4 -4
- package/src/components/style-sections/position-section/position-section.tsx +45 -15
- package/src/components/style-sections/position-section/z-index-field.tsx +4 -4
- package/src/components/style-sections/size-section/overflow-field.tsx +8 -8
- package/src/components/style-sections/size-section/size-section.tsx +33 -26
- package/src/components/style-sections/spacing-section/spacing-section.tsx +11 -13
- package/src/components/style-sections/typography-section/font-family-field.tsx +40 -0
- package/src/components/style-sections/typography-section/font-size-field.tsx +4 -4
- package/src/components/style-sections/typography-section/font-weight-field.tsx +4 -4
- package/src/components/style-sections/typography-section/letter-spacing-field.tsx +4 -4
- package/src/components/style-sections/typography-section/text-alignment-field.tsx +9 -9
- package/src/components/style-sections/typography-section/text-color-field.tsx +4 -4
- package/src/components/style-sections/typography-section/text-direction-field.tsx +7 -7
- package/src/components/style-sections/typography-section/text-stroke-field.tsx +42 -7
- package/src/components/style-sections/typography-section/text-style-field.tsx +5 -4
- package/src/components/style-sections/typography-section/transform-field.tsx +23 -9
- package/src/components/style-sections/typography-section/typography-section.tsx +26 -27
- package/src/components/style-sections/typography-section/word-spacing-field.tsx +4 -4
- package/src/components/style-tab.tsx +67 -31
- package/src/contexts/classes-prop-context.tsx +1 -1
- package/src/contexts/element-context.tsx +2 -2
- package/src/contexts/style-context.tsx +6 -5
- package/src/control-replacement.tsx +1 -1
- package/src/controls-actions.ts +3 -2
- package/src/controls-registry/control-type-container.tsx +3 -2
- package/src/controls-registry/control.tsx +2 -1
- package/src/controls-registry/controls-registry.tsx +8 -1
- package/src/controls-registry/settings-field.tsx +5 -4
- package/src/controls-registry/styles-field.tsx +3 -2
- package/src/dynamics/components/dynamic-selection-control.tsx +15 -14
- package/src/dynamics/components/dynamic-selection.tsx +9 -8
- package/src/dynamics/dynamic-control.tsx +4 -4
- package/src/dynamics/hooks/use-dynamic-tag.ts +3 -2
- package/src/dynamics/hooks/use-prop-dynamic-action.tsx +6 -5
- package/src/dynamics/hooks/use-prop-dynamic-tags.ts +3 -2
- package/src/dynamics/init.ts +5 -3
- package/src/dynamics/sync/get-elementor-config.ts +1 -1
- package/src/dynamics/types.ts +2 -2
- package/src/dynamics/utils.ts +3 -2
- 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 +25 -4
- package/src/index.ts +1 -1
- package/src/init.ts +5 -4
- package/src/panel.ts +1 -0
- package/src/popover-action.tsx +1 -1
- 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} +1 -1
- package/src/sync/types.ts +20 -21
- package/src/components/accordion-section.tsx +0 -26
- package/src/components/control-label.tsx +0 -10
- package/src/controls/bound-prop-context.tsx +0 -30
- package/src/controls/components/control-toggle-button-group.tsx +0 -68
- package/src/controls/components/repeater.tsx +0 -197
- package/src/controls/components/text-field-inner-selection.tsx +0 -75
- package/src/controls/control-actions/control-actions-context.tsx +0 -27
- package/src/controls/control-actions/control-actions-menu.ts +0 -7
- package/src/controls/control-actions/control-actions.tsx +0 -31
- package/src/controls/controls/box-shadow-repeater-control.tsx +0 -210
- package/src/controls/controls/color-control.tsx +0 -25
- package/src/controls/controls/equal-unequal-sizes-control.tsx +0 -196
- package/src/controls/controls/image-control.tsx +0 -58
- package/src/controls/controls/image-media-control.tsx +0 -64
- package/src/controls/controls/linked-dimensions-control.tsx +0 -139
- package/src/controls/controls/number-control.tsx +0 -29
- package/src/controls/controls/select-control.tsx +0 -30
- package/src/controls/controls/size-control.tsx +0 -71
- package/src/controls/controls/stroke-control.tsx +0 -105
- package/src/controls/controls/text-area-control.tsx +0 -31
- package/src/controls/controls/text-control.tsx +0 -17
- package/src/controls/controls/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-sync-external-state.tsx +0 -51
- package/src/controls/index.ts +0 -24
- package/src/dynamics/hooks/use-prop-value-history.ts +0 -26
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elementor/editor-editing-panel",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"author": "Elementor Team",
|
|
6
6
|
"homepage": "https://elementor.com/",
|
|
@@ -39,19 +39,19 @@
|
|
|
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/editor-panels": "^0.9.0",
|
|
46
|
-
"@elementor/editor-responsive": "^0.12.2",
|
|
47
|
-
"@elementor/editor-styles": "^0.2.0",
|
|
48
|
-
"@elementor/editor-v1-adapters": "^0.8.3",
|
|
49
|
-
"@elementor/icons": "^1.18.1",
|
|
42
|
+
"@elementor/editor": "^0.17.1",
|
|
43
|
+
"@elementor/editor-controls": "^0.1.1",
|
|
44
|
+
"@elementor/editor-elements": "^0.3.1",
|
|
50
45
|
"@elementor/menus": "^0.1.1",
|
|
51
|
-
"@elementor/
|
|
52
|
-
"@elementor/
|
|
53
|
-
"@elementor/
|
|
54
|
-
"@
|
|
46
|
+
"@elementor/editor-props": "^0.3.0",
|
|
47
|
+
"@elementor/editor-panels": "^0.10.1",
|
|
48
|
+
"@elementor/editor-responsive": "^0.12.3",
|
|
49
|
+
"@elementor/editor-styles": "^0.3.0",
|
|
50
|
+
"@elementor/editor-v1-adapters": "^0.8.4",
|
|
51
|
+
"@elementor/icons": "^1.20.0",
|
|
52
|
+
"@elementor/ui": "^1.22.0",
|
|
53
|
+
"@elementor/utils": "^0.3.0",
|
|
54
|
+
"@wordpress/i18n": "^5.13.0"
|
|
55
55
|
},
|
|
56
56
|
"peerDependencies": {
|
|
57
57
|
"react": "^18.3.1"
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import { PropsWithChildren } from 'react';
|
|
2
|
+
import { type PropsWithChildren } from 'react';
|
|
3
|
+
import { ControlLabel } from '@elementor/editor-controls';
|
|
3
4
|
import { MinusIcon, PlusIcon } from '@elementor/icons';
|
|
4
5
|
import { Collapse, IconButton, Stack } from '@elementor/ui';
|
|
5
|
-
import { ControlLabel } from './control-label';
|
|
6
6
|
|
|
7
7
|
const SIZE = 'tiny';
|
|
8
8
|
|
|
@@ -34,7 +34,7 @@ export const AddOrRemoveContent = ( { isAdded, label, onAdd, onRemove, children
|
|
|
34
34
|
</IconButton>
|
|
35
35
|
) }
|
|
36
36
|
</Stack>
|
|
37
|
-
<Collapse in={ isAdded }>
|
|
37
|
+
<Collapse in={ isAdded } unmountOnExit>
|
|
38
38
|
<Stack gap={ 1.5 }>{ children }</Stack>
|
|
39
39
|
</Collapse>
|
|
40
40
|
</Stack>
|
|
@@ -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
|
-
} ) );
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import { useState } from 'react';
|
|
3
|
-
import { __ } from '@wordpress/i18n';
|
|
4
|
-
import { Collapse, IconButton, Stack } from '@elementor/ui';
|
|
5
3
|
import { MinusIcon, PlusIcon } from '@elementor/icons';
|
|
4
|
+
import { Collapse, IconButton, Stack } from '@elementor/ui';
|
|
5
|
+
import { __ } from '@wordpress/i18n';
|
|
6
6
|
|
|
7
7
|
type CollapsibleFieldProps = React.PropsWithChildren< {
|
|
8
8
|
label: React.ReactNode;
|
|
@@ -28,7 +28,9 @@ export const CollapsibleField = ( { label, children, defaultOpen = false }: Coll
|
|
|
28
28
|
{ open ? <MinusIcon fontSize="tiny" /> : <PlusIcon fontSize="tiny" /> }
|
|
29
29
|
</IconButton>
|
|
30
30
|
</Stack>
|
|
31
|
-
<Collapse in={ open }
|
|
31
|
+
<Collapse in={ open } unmountOnExit>
|
|
32
|
+
{ children }
|
|
33
|
+
</Collapse>
|
|
32
34
|
</Stack>
|
|
33
35
|
);
|
|
34
36
|
};
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { updateSettings, useElementSetting, useElementStyles } from '@elementor/editor-elements';
|
|
3
|
+
import { classesPropTypeUtil, type ClassesPropValue } from '@elementor/editor-props';
|
|
4
|
+
import { type StyleDefinitionID } from '@elementor/editor-styles';
|
|
5
|
+
import { Chip, Stack, Typography } from '@elementor/ui';
|
|
6
|
+
import { __ } from '@wordpress/i18n';
|
|
7
|
+
|
|
8
|
+
import { useClassesProp } from '../contexts/classes-prop-context';
|
|
9
|
+
import { useElement } from '../contexts/element-context';
|
|
10
|
+
import { useStyle } from '../contexts/style-context';
|
|
11
|
+
import { MultiCombobox, type Option } from './multi-combobox';
|
|
12
|
+
|
|
13
|
+
const ID = 'elementor-css-class-selector';
|
|
14
|
+
const TAGS_LIMIT = 8;
|
|
15
|
+
|
|
16
|
+
export function CssClassSelector() {
|
|
17
|
+
const options = useOptions();
|
|
18
|
+
|
|
19
|
+
const { id: activeId, setId: setActiveId } = useStyle();
|
|
20
|
+
const [ appliedIds ] = useAppliedClassesIds();
|
|
21
|
+
|
|
22
|
+
const handleApply = useHandleApply();
|
|
23
|
+
const handleActivate = ( { value }: Option ) => setActiveId( value );
|
|
24
|
+
|
|
25
|
+
const active = options.find( ( option ) => option.value === activeId ) || null;
|
|
26
|
+
|
|
27
|
+
const applied = appliedIds
|
|
28
|
+
.map( ( id ) => options.find( ( option ) => option.value === id ) )
|
|
29
|
+
.filter( ( option ) => !! option );
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<Stack gap={ 1 } p={ 2 }>
|
|
33
|
+
<Typography component="label" variant="caption" htmlFor={ ID }>
|
|
34
|
+
{ __( 'CSS Classes', 'elementor' ) }
|
|
35
|
+
</Typography>
|
|
36
|
+
<MultiCombobox
|
|
37
|
+
id={ ID }
|
|
38
|
+
size="tiny"
|
|
39
|
+
options={ options }
|
|
40
|
+
selected={ applied }
|
|
41
|
+
onSelect={ handleApply }
|
|
42
|
+
limitTags={ TAGS_LIMIT }
|
|
43
|
+
optionsLabel={ __( 'Global CSS Classes', 'elementor' ) }
|
|
44
|
+
renderTags={ ( values, getTagProps ) =>
|
|
45
|
+
values.map( ( value, index ) => {
|
|
46
|
+
const chipProps = getTagProps( { index } );
|
|
47
|
+
const isActive = value.value === active?.value;
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<Chip
|
|
51
|
+
{ ...chipProps }
|
|
52
|
+
key={ chipProps.key }
|
|
53
|
+
size="small"
|
|
54
|
+
label={ value.label }
|
|
55
|
+
variant={ isActive ? 'filled' : 'standard' }
|
|
56
|
+
color={ isActive && value.color ? value.color : 'default' }
|
|
57
|
+
onClick={ () => handleActivate( value ) }
|
|
58
|
+
onDelete={ null }
|
|
59
|
+
/>
|
|
60
|
+
);
|
|
61
|
+
} )
|
|
62
|
+
}
|
|
63
|
+
/>
|
|
64
|
+
</Stack>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function useOptions() {
|
|
69
|
+
const { element } = useElement();
|
|
70
|
+
|
|
71
|
+
const styleDefs = useElementStyles( element.id );
|
|
72
|
+
|
|
73
|
+
return Object.values( styleDefs ).map< Option >( ( styleDef ) => ( {
|
|
74
|
+
label: styleDef.label,
|
|
75
|
+
value: styleDef.id,
|
|
76
|
+
fixed: true,
|
|
77
|
+
color: 'primary',
|
|
78
|
+
} ) );
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function useAppliedClassesIds() {
|
|
82
|
+
const { element } = useElement();
|
|
83
|
+
const currentClassesProp = useClassesProp();
|
|
84
|
+
|
|
85
|
+
const value = useElementSetting< ClassesPropValue >( element.id, currentClassesProp )?.value || [];
|
|
86
|
+
|
|
87
|
+
const setValue = ( ids: StyleDefinitionID[] ) => {
|
|
88
|
+
updateSettings( {
|
|
89
|
+
id: element.id,
|
|
90
|
+
props: {
|
|
91
|
+
[ currentClassesProp ]: classesPropTypeUtil.create( ids ),
|
|
92
|
+
},
|
|
93
|
+
} );
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
return [ value, setValue ] as const;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function useHandleApply() {
|
|
100
|
+
const { id: activeId, setId: setActiveId } = useStyle();
|
|
101
|
+
const [ appliedIds, setAppliedIds ] = useAppliedClassesIds();
|
|
102
|
+
|
|
103
|
+
return ( selectedOptions: Option[] ) => {
|
|
104
|
+
const selectedValues = selectedOptions.map( ( { value } ) => value );
|
|
105
|
+
|
|
106
|
+
const isSameClassesAlreadyApplied =
|
|
107
|
+
selectedValues.length === appliedIds.length &&
|
|
108
|
+
selectedValues.every( ( value ) => appliedIds.includes( value ) );
|
|
109
|
+
|
|
110
|
+
// Should not trigger to avoid register an undo step.
|
|
111
|
+
if ( isSameClassesAlreadyApplied ) {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
setAppliedIds( selectedValues );
|
|
116
|
+
|
|
117
|
+
const addedValue = selectedValues.find( ( id ) => ! appliedIds.includes( id ) );
|
|
118
|
+
|
|
119
|
+
if ( addedValue ) {
|
|
120
|
+
setActiveId( addedValue );
|
|
121
|
+
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const removedValue = appliedIds.find( ( id ) => ! selectedValues.includes( id ) );
|
|
126
|
+
|
|
127
|
+
if ( removedValue && removedValue === activeId ) {
|
|
128
|
+
setActiveId( selectedValues[ 0 ] ?? null );
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
}
|
|
@@ -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,14 +1,17 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import { ControlActionsProvider, ControlReplacementProvider } from '@elementor/editor-controls';
|
|
3
3
|
import { useSelectedElement } from '@elementor/editor-elements';
|
|
4
4
|
import { Panel, PanelBody, PanelHeader, PanelHeaderTitle } from '@elementor/editor-panels';
|
|
5
|
-
import { EditingPanelTabs } from './editing-panel-tabs';
|
|
6
|
-
import { ControlActionsProvider, ControlReplacementProvider } from '../controls';
|
|
7
|
-
import { getControlReplacement } from '../control-replacement';
|
|
8
5
|
import { ErrorBoundary } from '@elementor/ui';
|
|
9
|
-
import {
|
|
6
|
+
import { __ } from '@wordpress/i18n';
|
|
7
|
+
|
|
10
8
|
import { ElementProvider } from '../contexts/element-context';
|
|
11
|
-
import {
|
|
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';
|
|
13
|
+
|
|
14
|
+
const { useMenuItems } = controlActionsMenu;
|
|
12
15
|
|
|
13
16
|
export const EditingPanel = () => {
|
|
14
17
|
const { element, elementType } = useSelectedElement();
|
|
@@ -0,0 +1,122 @@
|
|
|
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' | 'onSelect' > & {
|
|
17
|
+
actions?: Actions;
|
|
18
|
+
selected: Option[];
|
|
19
|
+
options: Option[];
|
|
20
|
+
optionsLabel?: string;
|
|
21
|
+
onSelect?: ( value: Option[] ) => void;
|
|
22
|
+
onCreate?: ( value: string ) => void;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export const MultiCombobox = ( {
|
|
26
|
+
actions = {},
|
|
27
|
+
selected,
|
|
28
|
+
options,
|
|
29
|
+
optionsLabel,
|
|
30
|
+
onSelect,
|
|
31
|
+
onCreate,
|
|
32
|
+
...props
|
|
33
|
+
}: Props ) => {
|
|
34
|
+
const { action: actionProps, option: optionProps } = useComboboxActions(
|
|
35
|
+
selected,
|
|
36
|
+
actions,
|
|
37
|
+
// TODO: make the group mechanism more generic, allow passing list of groups.
|
|
38
|
+
optionsLabel,
|
|
39
|
+
onSelect
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<Autocomplete
|
|
44
|
+
{ ...props }
|
|
45
|
+
freeSolo
|
|
46
|
+
multiple
|
|
47
|
+
clearOnBlur
|
|
48
|
+
selectOnFocus
|
|
49
|
+
disableClearable
|
|
50
|
+
handleHomeEndKeys
|
|
51
|
+
value={ selected }
|
|
52
|
+
options={ options }
|
|
53
|
+
renderGroup={ renderGroup }
|
|
54
|
+
renderInput={ ( params ) => <TextField { ...params } /> }
|
|
55
|
+
// TODO: is it relevant for the combobox? or should be in the parent component?
|
|
56
|
+
getLimitTagsText={ ( more ) => <Chip size="tiny" variant="standard" label={ `+${ more }` } clickable /> }
|
|
57
|
+
onChange={ ( _, selectedOrTypedValue, reason ) => {
|
|
58
|
+
if ( reason === 'createOption' ) {
|
|
59
|
+
const typedValue = selectedOrTypedValue.find( ( option ) => typeof option === 'string' );
|
|
60
|
+
|
|
61
|
+
return typedValue && onCreate?.( typedValue );
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const action = selectedOrTypedValue.find( ( value ) => actionProps.is( value ) );
|
|
65
|
+
|
|
66
|
+
if ( reason === 'selectOption' && action ) {
|
|
67
|
+
return actionProps.onChange( action );
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const selectedValues = selectedOrTypedValue.filter( ( v ) => typeof v !== 'string' );
|
|
71
|
+
const fixedValues = options.filter( ( option ) => option.fixed );
|
|
72
|
+
|
|
73
|
+
optionProps.onChange( [ ...new Set( [ ...fixedValues, ...selectedValues ] ) ] );
|
|
74
|
+
} }
|
|
75
|
+
getOptionLabel={ ( option ) => {
|
|
76
|
+
if ( optionProps.is( option ) ) {
|
|
77
|
+
return optionProps.getLabel( option );
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if ( actionProps.is( option ) ) {
|
|
81
|
+
return actionProps.getLabel( option );
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return '';
|
|
85
|
+
} }
|
|
86
|
+
filterOptions={ ( optionList: Option[], params: FilterOptionsState< ActionOption | Option > ) => {
|
|
87
|
+
const filteredOptions = optionProps.getFilteredOptions( optionList, params );
|
|
88
|
+
|
|
89
|
+
const actionOptions = actionProps.getFilteredActions( optionList, params );
|
|
90
|
+
|
|
91
|
+
return [ ...actionOptions, ...filteredOptions ];
|
|
92
|
+
} }
|
|
93
|
+
groupBy={ ( option ) =>
|
|
94
|
+
( optionProps.is( option ) ? optionProps.groupBy() : actionProps.groupBy( option ) ) ?? ''
|
|
95
|
+
}
|
|
96
|
+
/>
|
|
97
|
+
);
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const renderGroup = ( params: AutocompleteRenderGroupParams ) => (
|
|
101
|
+
<Group key={ params.key }>
|
|
102
|
+
<GroupHeader>{ params.group }</GroupHeader>
|
|
103
|
+
<GroupItems>{ params.children }</GroupItems>
|
|
104
|
+
</Group>
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
const Group = styled( 'li' )`
|
|
108
|
+
&:not( :last-of-type ) {
|
|
109
|
+
border-bottom: 1px solid ${ ( { theme } ) => theme.palette.divider };
|
|
110
|
+
}
|
|
111
|
+
`;
|
|
112
|
+
|
|
113
|
+
const GroupHeader = styled( Box )( ( { theme } ) => ( {
|
|
114
|
+
position: 'sticky',
|
|
115
|
+
top: '-8px',
|
|
116
|
+
padding: theme.spacing( 1, 2 ),
|
|
117
|
+
color: theme.palette.text.tertiary,
|
|
118
|
+
} ) );
|
|
119
|
+
|
|
120
|
+
const GroupItems = styled( 'ul' )`
|
|
121
|
+
padding: 0;
|
|
122
|
+
`;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export type Option = {
|
|
2
|
+
label: string;
|
|
3
|
+
value: string;
|
|
4
|
+
fixed?: boolean;
|
|
5
|
+
// TODO: Should be remove from here or use some kind of `meta`
|
|
6
|
+
color?: 'primary' | 'global';
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export type Action = {
|
|
10
|
+
getLabel: ( inputValue: string ) => string;
|
|
11
|
+
apply: ( value: string ) => void;
|
|
12
|
+
condition: ( options: Option[], inputValue: string ) => boolean;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export type ActionsGroup = {
|
|
16
|
+
label: string;
|
|
17
|
+
actions: Action[];
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export type ActionOption = Option & {
|
|
21
|
+
action: {
|
|
22
|
+
groupLabel: string;
|
|
23
|
+
apply: Action[ 'apply' ];
|
|
24
|
+
getLabel: Action[ 'getLabel' ];
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
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
|
+
onSelect?: ( value: Option[] ) => void
|
|
11
|
+
) => ( {
|
|
12
|
+
action: {
|
|
13
|
+
is: ( opt: ActionOption | Option | string ): opt is ActionOption => typeof opt !== 'string' && 'action' in opt,
|
|
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 | string ): opt is Option => typeof opt !== 'string' && ! ( 'action' in opt ),
|
|
38
|
+
getLabel: ( option: Option ) => option.label,
|
|
39
|
+
groupBy: () => optionsLabel ?? '',
|
|
40
|
+
onChange: ( optionValues: Option[] ) => onSelect?.( 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
|
+
}
|