@elementor/editor-editing-panel 1.0.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 +46 -0
- package/dist/index.d.mts +10 -19
- package/dist/index.d.ts +10 -19
- package/dist/index.js +1283 -1751
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1305 -1762
- package/dist/index.mjs.map +1 -1
- package/package.json +12 -12
- 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-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 +9 -6
- 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 +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/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 +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 +3 -3
- 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.1.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"author": "Elementor Team",
|
|
6
6
|
"homepage": "https://elementor.com/",
|
|
@@ -39,18 +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/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.0",
|
|
43
|
+
"@elementor/editor-controls": "^0.1.0",
|
|
44
|
+
"@elementor/editor-elements": "^0.3.0",
|
|
50
45
|
"@elementor/menus": "^0.1.1",
|
|
51
|
-
"@elementor/
|
|
52
|
-
"@elementor/
|
|
53
|
-
"@elementor/
|
|
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",
|
|
54
54
|
"@wordpress/i18n": "^4.45.0"
|
|
55
55
|
},
|
|
56
56
|
"peerDependencies": {
|
|
@@ -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,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,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,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
|
+
}
|
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import { ControlLabel } from '@elementor/editor-controls';
|
|
3
3
|
import { type Control } from '@elementor/editor-elements';
|
|
4
|
-
|
|
5
|
-
import {
|
|
4
|
+
|
|
5
|
+
import { useElement } from '../contexts/element-context';
|
|
6
6
|
import { Control as BaseControl } from '../controls-registry/control';
|
|
7
|
-
import { ControlType, getControlByType } from '../controls-registry/controls-registry';
|
|
8
7
|
import { ControlTypeContainer } from '../controls-registry/control-type-container';
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
8
|
+
import { type ControlType, getControlByType } from '../controls-registry/controls-registry';
|
|
9
|
+
import { SettingsField } from '../controls-registry/settings-field';
|
|
10
|
+
import { Section } from './section';
|
|
11
|
+
import { SectionsList } from './sections-list';
|
|
11
12
|
|
|
12
13
|
export const SettingsTab = () => {
|
|
13
14
|
const { elementType } = useElement();
|
|
14
15
|
|
|
15
16
|
return (
|
|
16
|
-
<
|
|
17
|
+
<SectionsList>
|
|
17
18
|
{ elementType.controls.map( ( { type, value }, index ) => {
|
|
18
19
|
if ( type === 'control' ) {
|
|
19
20
|
return <Control key={ value.bind } control={ value } />;
|
|
@@ -21,7 +22,7 @@ export const SettingsTab = () => {
|
|
|
21
22
|
|
|
22
23
|
if ( type === 'section' ) {
|
|
23
24
|
return (
|
|
24
|
-
<
|
|
25
|
+
<Section title={ value.label } key={ type + '.' + index } defaultExpanded={ true }>
|
|
25
26
|
{ value.items?.map( ( item ) => {
|
|
26
27
|
if ( item.type === 'control' ) {
|
|
27
28
|
return <Control key={ item.value.bind } control={ item.value } />;
|
|
@@ -30,17 +31,16 @@ export const SettingsTab = () => {
|
|
|
30
31
|
// TODO: Handle 2nd level sections
|
|
31
32
|
return null;
|
|
32
33
|
} ) }
|
|
33
|
-
</
|
|
34
|
+
</Section>
|
|
34
35
|
);
|
|
35
36
|
}
|
|
36
37
|
|
|
37
38
|
return null;
|
|
38
39
|
} ) }
|
|
39
|
-
</
|
|
40
|
+
</SectionsList>
|
|
40
41
|
);
|
|
41
42
|
};
|
|
42
43
|
|
|
43
|
-
// TODO: Create control wrapper by type for different layouts.
|
|
44
44
|
const Control = ( { control }: { control: Control[ 'value' ] } ) => {
|
|
45
45
|
if ( ! getControlByType( control.type as ControlType ) ) {
|
|
46
46
|
return null;
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import { ColorControl, ControlLabel } from '@elementor/editor-controls';
|
|
3
3
|
import { Grid } from '@elementor/ui';
|
|
4
|
+
import { __ } from '@wordpress/i18n';
|
|
5
|
+
|
|
4
6
|
import { StylesField } from '../../../controls-registry/styles-field';
|
|
5
|
-
import { ControlLabel } from '../../control-label';
|
|
6
|
-
import { ColorControl } from '../../../controls';
|
|
7
7
|
|
|
8
8
|
export const BackgroundColorField = () => {
|
|
9
9
|
return (
|
|
10
10
|
<StylesField bind="background-color">
|
|
11
|
-
<Grid container
|
|
11
|
+
<Grid container gap={ 2 } alignItems="center" flexWrap="nowrap">
|
|
12
12
|
<Grid item xs={ 6 }>
|
|
13
13
|
<ControlLabel>{ __( 'Color', 'elementor' ) }</ControlLabel>
|
|
14
14
|
</Grid>
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import { BackgroundOverlayRepeaterControl } from '@elementor/editor-controls';
|
|
3
3
|
import { Stack } from '@elementor/ui';
|
|
4
|
-
|
|
4
|
+
|
|
5
|
+
import { StylesField } from '../../../controls-registry/styles-field';
|
|
5
6
|
import { BackgroundColorField } from './background-color-field';
|
|
6
7
|
|
|
7
8
|
export const BackgroundSection = () => {
|
|
8
9
|
return (
|
|
9
|
-
<
|
|
10
|
-
<
|
|
11
|
-
<
|
|
12
|
-
</
|
|
13
|
-
|
|
10
|
+
<Stack gap={ 1.5 }>
|
|
11
|
+
<StylesField bind="background-image">
|
|
12
|
+
<BackgroundOverlayRepeaterControl />
|
|
13
|
+
</StylesField>
|
|
14
|
+
<BackgroundColorField />
|
|
15
|
+
</Stack>
|
|
14
16
|
);
|
|
15
17
|
};
|