@elementor/editor-editing-panel 0.5.0 → 0.6.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 +20 -0
- package/dist/index.js +174 -72
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +168 -66
- package/dist/index.mjs.map +1 -1
- package/package.json +15 -7
- package/src/components/controls/collapsible-section.tsx +25 -0
- package/src/components/controls/control-types/text-area-control.tsx +5 -3
- package/src/components/controls/control-types/text-control.tsx +11 -0
- package/src/components/controls/get-control-by-type.ts +13 -0
- package/src/components/controls/settings-control.tsx +38 -13
- package/src/components/controls/style-control.tsx +35 -0
- package/src/components/{editing-panel-hooks.tsx → editing-panel/editing-panel-hooks.tsx} +1 -1
- package/src/components/editing-panel/editing-panel-tabs.tsx +26 -0
- package/src/components/editing-panel/editing-panel.tsx +35 -0
- package/src/components/editing-panel/settings-tab.tsx +64 -0
- package/src/components/editing-panel/style-tab.tsx +17 -0
- package/src/contexts/control-context.tsx +0 -25
- package/src/contexts/style-context.tsx +34 -0
- package/src/hooks/use-element-style-prop.ts +44 -0
- package/src/hooks/use-element-styles.ts +13 -0
- package/src/hooks/use-widget-settings.ts +1 -1
- package/src/init.ts +1 -1
- package/src/panel.ts +1 -1
- package/src/sync/get-element-styles.ts +8 -0
- package/src/sync/types.ts +3 -2
- package/src/sync/update-style.ts +23 -0
- package/src/types.ts +49 -3
- package/src/__tests__/utils.ts +0 -28
- package/src/components/__tests__/editing-panel.test.tsx +0 -92
- package/src/components/controls/__tests__/settings-control.test.tsx +0 -71
- package/src/components/controls/control-types/__tests__/select-control.test.tsx +0 -67
- package/src/components/controls/control-types/__tests__/text-area-control.test.tsx +0 -29
- package/src/components/editing-panel.tsx +0 -66
- package/src/contexts/__tests__/control-context.test.tsx +0 -14
- package/src/hooks/__tests__/use-widget-settings.test.ts +0 -83
- package/src/sync/__tests__/get-container.test.ts +0 -40
- package/src/sync/__tests__/should-use-v2-panel.test.ts +0 -95
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { PropKey, PropValue } from '../../types';
|
|
3
|
+
import { ControlContext } from '../../contexts/control-context';
|
|
4
|
+
import { updateStyle } from '../../sync/update-style';
|
|
5
|
+
import { useElementStyleProp } from '../../hooks/use-element-style-prop';
|
|
6
|
+
import { useElementContext } from '../../contexts/element-context';
|
|
7
|
+
import { useStyleContext } from '../../contexts/style-context';
|
|
8
|
+
|
|
9
|
+
export type StyleControlProps = {
|
|
10
|
+
bind: PropKey;
|
|
11
|
+
children: React.ReactNode;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export const StyleControl = ( { bind, children }: StyleControlProps ) => {
|
|
15
|
+
const { element } = useElementContext();
|
|
16
|
+
const { selectedStyleDef, selectedMeta } = useStyleContext();
|
|
17
|
+
|
|
18
|
+
const setValue = ( newValue: PropValue ) => {
|
|
19
|
+
updateStyle( {
|
|
20
|
+
elementID: element.id,
|
|
21
|
+
styleDefID: selectedStyleDef?.id,
|
|
22
|
+
props: { [ bind ]: newValue },
|
|
23
|
+
meta: selectedMeta,
|
|
24
|
+
} );
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const value = useElementStyleProp( {
|
|
28
|
+
elementID: element.id,
|
|
29
|
+
styleDefID: selectedStyleDef?.id,
|
|
30
|
+
meta: selectedMeta,
|
|
31
|
+
propName: bind,
|
|
32
|
+
} );
|
|
33
|
+
|
|
34
|
+
return <ControlContext.Provider value={ { bind, value, setValue } }>{ children }</ControlContext.Provider>;
|
|
35
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Stack, Tabs, Tab, TabPanel, useTabs } from '@elementor/ui';
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
import { __ } from '@wordpress/i18n';
|
|
4
|
+
import { SettingsTab } from './settings-tab';
|
|
5
|
+
import { StyleTab } from './style-tab';
|
|
6
|
+
|
|
7
|
+
type TabValue = 'settings' | 'style';
|
|
8
|
+
|
|
9
|
+
export const EditingPanelTabs = () => {
|
|
10
|
+
const { getTabProps, getTabPanelProps, getTabsProps } = useTabs< TabValue >( 'settings' );
|
|
11
|
+
|
|
12
|
+
return (
|
|
13
|
+
<Stack direction="column" sx={ { width: '100%' } }>
|
|
14
|
+
<Tabs variant="fullWidth" indicatorColor="secondary" textColor="inherit" { ...getTabsProps() }>
|
|
15
|
+
<Tab label={ __( 'General', 'elementor' ) } { ...getTabProps( 'settings' ) } />
|
|
16
|
+
<Tab label={ __( 'Style', 'elementor' ) } { ...getTabProps( 'style' ) } />
|
|
17
|
+
</Tabs>
|
|
18
|
+
<TabPanel { ...getTabPanelProps( 'settings' ) } disablePadding>
|
|
19
|
+
<SettingsTab />
|
|
20
|
+
</TabPanel>
|
|
21
|
+
<TabPanel { ...getTabPanelProps( 'style' ) } disablePadding>
|
|
22
|
+
<StyleTab />
|
|
23
|
+
</TabPanel>
|
|
24
|
+
</Stack>
|
|
25
|
+
);
|
|
26
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { __ } from '@wordpress/i18n';
|
|
3
|
+
import useSelectedElements from '../../hooks/use-selected-elements';
|
|
4
|
+
import useElementType from '../../hooks/use-element-type';
|
|
5
|
+
import { Panel, PanelBody, PanelHeader, PanelHeaderTitle } from '@elementor/editor-panels';
|
|
6
|
+
import { ElementContext } from '../../contexts/element-context';
|
|
7
|
+
import { EditingPanelTabs } from './editing-panel-tabs';
|
|
8
|
+
|
|
9
|
+
export const EditingPanel = () => {
|
|
10
|
+
const elements = useSelectedElements();
|
|
11
|
+
|
|
12
|
+
const [ selectedElement ] = elements;
|
|
13
|
+
|
|
14
|
+
const elementType = useElementType( selectedElement?.type );
|
|
15
|
+
|
|
16
|
+
if ( elements.length !== 1 || ! elementType ) {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/* translators: %s: Element type title. */
|
|
21
|
+
const panelTitle = __( 'Edit %s', 'elementor' ).replace( '%s', elementType.title );
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<Panel>
|
|
25
|
+
<PanelHeader>
|
|
26
|
+
<PanelHeaderTitle>{ panelTitle }</PanelHeaderTitle>
|
|
27
|
+
</PanelHeader>
|
|
28
|
+
<PanelBody>
|
|
29
|
+
<ElementContext element={ selectedElement }>
|
|
30
|
+
<EditingPanelTabs />
|
|
31
|
+
</ElementContext>
|
|
32
|
+
</PanelBody>
|
|
33
|
+
</Panel>
|
|
34
|
+
);
|
|
35
|
+
};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { Stack } from '@elementor/ui';
|
|
3
|
+
import { SettingsControl } from '../controls/settings-control';
|
|
4
|
+
import { useElementContext } from '../../contexts/element-context';
|
|
5
|
+
import useElementType from '../../hooks/use-element-type';
|
|
6
|
+
import type { Control } from '../../types';
|
|
7
|
+
import { CollapsibleSection } from '../controls/collapsible-section';
|
|
8
|
+
import { getControlByType } from '../controls/get-control-by-type';
|
|
9
|
+
|
|
10
|
+
export const SettingsTab = () => {
|
|
11
|
+
const { element } = useElementContext();
|
|
12
|
+
|
|
13
|
+
const elementType = useElementType( element?.type );
|
|
14
|
+
|
|
15
|
+
if ( ! elementType ) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<Stack>
|
|
21
|
+
{ elementType.controls.map( ( { type, value }, index ) => {
|
|
22
|
+
if ( type === 'control' ) {
|
|
23
|
+
return <Control key={ value.bind } control={ value } />;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if ( type === 'section' ) {
|
|
27
|
+
return (
|
|
28
|
+
<CollapsibleSection key={ type + '.' + index } title={ value.label }>
|
|
29
|
+
{ value.items?.map( ( item ) => {
|
|
30
|
+
if ( item.type === 'control' ) {
|
|
31
|
+
return <Control key={ item.value.bind } control={ item.value } />;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// TODO: Handle 2nd level sections
|
|
35
|
+
return null;
|
|
36
|
+
} ) }
|
|
37
|
+
</CollapsibleSection>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return null;
|
|
42
|
+
} ) }
|
|
43
|
+
</Stack>
|
|
44
|
+
);
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
// TODO: Create control wrapper by type for different layouts.
|
|
48
|
+
const Control = ( { control }: { control: Control[ 'value' ] } ) => {
|
|
49
|
+
const ControlComponent = getControlByType( control.type );
|
|
50
|
+
|
|
51
|
+
if ( ! ControlComponent ) {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<SettingsControl key={ control.bind } bind={ control.bind }>
|
|
57
|
+
<SettingsControl.Label>{ control.label }</SettingsControl.Label>
|
|
58
|
+
<ControlComponent
|
|
59
|
+
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
|
|
60
|
+
{ ...( control.props as any ) }
|
|
61
|
+
/>
|
|
62
|
+
</SettingsControl>
|
|
63
|
+
);
|
|
64
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { StyleContext } from '../../contexts/style-context';
|
|
3
|
+
import { useElementContext } from '../../contexts/element-context';
|
|
4
|
+
import { useElementStyles } from '../../hooks/use-element-styles';
|
|
5
|
+
|
|
6
|
+
export const StyleTab = () => {
|
|
7
|
+
const selectedElementID = useElementContext().element.id;
|
|
8
|
+
const elementStyles = useElementStyles( selectedElementID );
|
|
9
|
+
// TODO: Handle selected style state.
|
|
10
|
+
const selectedStyleDef = elementStyles ? Object.values( elementStyles )[ 0 ] : null;
|
|
11
|
+
|
|
12
|
+
return (
|
|
13
|
+
<StyleContext selectedStyleDef={ selectedStyleDef }>
|
|
14
|
+
<div>Style Tab</div>
|
|
15
|
+
</StyleContext>
|
|
16
|
+
);
|
|
17
|
+
};
|
|
@@ -1,9 +1,5 @@
|
|
|
1
|
-
import * as React from 'react';
|
|
2
1
|
import { createContext, useContext } from 'react';
|
|
3
2
|
import { PropKey, PropValue } from '../types';
|
|
4
|
-
import { useElementContext } from './element-context';
|
|
5
|
-
import { useWidgetSettings } from '../hooks/use-widget-settings';
|
|
6
|
-
import { updateSettings } from '../sync/update-settings';
|
|
7
3
|
|
|
8
4
|
export type ControlContext< T extends PropValue > = null | {
|
|
9
5
|
bind: PropKey;
|
|
@@ -22,24 +18,3 @@ export function useControl< T extends PropValue >( defaultValue?: T ) {
|
|
|
22
18
|
|
|
23
19
|
return { ...controlContext, value: controlContext.value ?? defaultValue };
|
|
24
20
|
}
|
|
25
|
-
|
|
26
|
-
type Props = {
|
|
27
|
-
bind: PropKey;
|
|
28
|
-
children: React.ReactNode;
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
export const ControlContextProvider = ( { bind, children }: Props ) => {
|
|
32
|
-
const { element } = useElementContext();
|
|
33
|
-
const value = useWidgetSettings( { id: element.id, bind } );
|
|
34
|
-
|
|
35
|
-
const setValue = ( newValue: PropValue ) => {
|
|
36
|
-
updateSettings( {
|
|
37
|
-
id: element.id,
|
|
38
|
-
props: {
|
|
39
|
-
[ bind ]: newValue,
|
|
40
|
-
},
|
|
41
|
-
} );
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
return <ControlContext.Provider value={ { setValue, value, bind } }>{ children }</ControlContext.Provider>;
|
|
45
|
-
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { createContext, ReactNode, useContext } from 'react';
|
|
3
|
+
import { StyleDefinition, StyleDefMeta } from '../types';
|
|
4
|
+
import { useActiveBreakpoint } from '@elementor/editor-responsive';
|
|
5
|
+
|
|
6
|
+
type ContextValue = {
|
|
7
|
+
selectedStyleDef: StyleDefinition | null;
|
|
8
|
+
selectedMeta: StyleDefMeta;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const Context = createContext< ContextValue | null >( null );
|
|
12
|
+
|
|
13
|
+
type Props = {
|
|
14
|
+
children: ReactNode;
|
|
15
|
+
selectedStyleDef: StyleDefinition | null;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export function StyleContext( { children, selectedStyleDef }: Props ) {
|
|
19
|
+
const breakpoint = useActiveBreakpoint();
|
|
20
|
+
// TODO: Handle state when we support it.
|
|
21
|
+
const selectedMeta = { breakpoint, state: null } as const;
|
|
22
|
+
|
|
23
|
+
return <Context.Provider value={ { selectedStyleDef, selectedMeta } }>{ children }</Context.Provider>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function useStyleContext() {
|
|
27
|
+
const context = useContext( Context );
|
|
28
|
+
|
|
29
|
+
if ( ! context ) {
|
|
30
|
+
throw new Error( 'UseStyleContext must be used within a StyleContextProvider' );
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return context;
|
|
34
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { commandEndEvent, __privateUseListenTo as useListenTo } from '@elementor/editor-v1-adapters';
|
|
2
|
+
import { ElementID, PropKey, PropValue, StyleDefID, StyleDefinition, StyleDefMeta } from '../types';
|
|
3
|
+
import { getElementStyles } from '../sync/get-element-styles';
|
|
4
|
+
|
|
5
|
+
export type UseElementStylePropArgs = {
|
|
6
|
+
elementID: ElementID;
|
|
7
|
+
styleDefID?: StyleDefID;
|
|
8
|
+
meta: StyleDefMeta;
|
|
9
|
+
propName: PropKey;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const useElementStyleProp = < T extends PropValue >( {
|
|
13
|
+
elementID,
|
|
14
|
+
styleDefID,
|
|
15
|
+
meta,
|
|
16
|
+
propName,
|
|
17
|
+
}: UseElementStylePropArgs ): T | null => {
|
|
18
|
+
return useListenTo(
|
|
19
|
+
commandEndEvent( 'document/atomic-widgets/styles' ),
|
|
20
|
+
() => {
|
|
21
|
+
// TODO: return default value for style prop
|
|
22
|
+
if ( ! styleDefID ) {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const styleDef = getElementStyles( elementID )?.[ styleDefID ];
|
|
27
|
+
|
|
28
|
+
if ( ! styleDef ) {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const variant = getVariantByMeta( styleDef, meta );
|
|
33
|
+
|
|
34
|
+
return variant?.props[ propName ] ?? null;
|
|
35
|
+
},
|
|
36
|
+
[ elementID, styleDefID, propName, meta ]
|
|
37
|
+
) as T;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
function getVariantByMeta( styleDef: StyleDefinition, meta: StyleDefMeta ) {
|
|
41
|
+
return styleDef.variants.find( ( variant ) => {
|
|
42
|
+
return variant.meta.breakpoint === meta.breakpoint && variant.meta.state === meta.state;
|
|
43
|
+
} );
|
|
44
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { __privateUseListenTo as useListenTo, commandEndEvent } from '@elementor/editor-v1-adapters';
|
|
2
|
+
import { getElementStyles } from '../sync/get-element-styles';
|
|
3
|
+
import { ElementID } from '../types';
|
|
4
|
+
|
|
5
|
+
export const useElementStyles = ( elementID: ElementID ) => {
|
|
6
|
+
return useListenTo(
|
|
7
|
+
commandEndEvent( 'document/atomic-widgets/styles' ),
|
|
8
|
+
() => {
|
|
9
|
+
return getElementStyles( elementID );
|
|
10
|
+
},
|
|
11
|
+
[ elementID ]
|
|
12
|
+
);
|
|
13
|
+
};
|
package/src/init.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { panel } from './panel';
|
|
2
2
|
import { injectIntoLogic } from '@elementor/editor';
|
|
3
3
|
import { shouldUseV2Panel } from './sync/should-use-v2-panel';
|
|
4
|
-
import { EditingPanelHooks } from './components/editing-panel-hooks';
|
|
4
|
+
import { EditingPanelHooks } from './components/editing-panel/editing-panel-hooks';
|
|
5
5
|
import { __registerPanel as registerPanel } from '@elementor/editor-panels';
|
|
6
6
|
import { __privateBlockDataCommand as blockDataCommand } from '@elementor/editor-v1-adapters';
|
|
7
7
|
|
package/src/panel.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { __createPanel as createPanel } from '@elementor/editor-panels';
|
|
2
|
-
import { EditingPanel } from './components/editing-panel';
|
|
2
|
+
import { EditingPanel } from './components/editing-panel/editing-panel';
|
|
3
3
|
|
|
4
4
|
export const { panel, usePanelActions, usePanelStatus } = createPanel( {
|
|
5
5
|
id: 'editing-panel',
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import getContainer from './get-container';
|
|
2
|
+
import { ElementID, StyleDefinition } from '../types';
|
|
3
|
+
|
|
4
|
+
export const getElementStyles = ( elementID: ElementID ): Record< string, StyleDefinition > | null => {
|
|
5
|
+
const container = getContainer( elementID );
|
|
6
|
+
|
|
7
|
+
return container?.model.get( 'styles' ) || null;
|
|
8
|
+
};
|
package/src/sync/types.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ControlItem, PropValue, StyleDefID, StyleDefinition } from '../types';
|
|
2
2
|
|
|
3
3
|
export type ExtendedWindow = Window & {
|
|
4
4
|
elementor?: {
|
|
@@ -8,7 +8,7 @@ export type ExtendedWindow = Window & {
|
|
|
8
8
|
widgetsCache?: Record<
|
|
9
9
|
string,
|
|
10
10
|
{
|
|
11
|
-
atomic_controls?:
|
|
11
|
+
atomic_controls?: ControlItem[];
|
|
12
12
|
controls: object;
|
|
13
13
|
title: string;
|
|
14
14
|
}
|
|
@@ -26,6 +26,7 @@ export type V1ElementModelProps = {
|
|
|
26
26
|
widgetType?: string;
|
|
27
27
|
elType: string;
|
|
28
28
|
id: string;
|
|
29
|
+
styles?: Record< StyleDefID, StyleDefinition >;
|
|
29
30
|
};
|
|
30
31
|
|
|
31
32
|
export type V1ElementSettingsProps = Record< string, PropValue >;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { __privateRunCommand as runCommand } from '@elementor/editor-v1-adapters';
|
|
2
|
+
import { ElementID, PropKey, Props, StyleDefID, StyleDefMeta } from '../types';
|
|
3
|
+
import getContainer from './get-container';
|
|
4
|
+
|
|
5
|
+
export type UpdateStyleProps = {
|
|
6
|
+
elementID: ElementID;
|
|
7
|
+
styleDefID?: StyleDefID;
|
|
8
|
+
meta: StyleDefMeta;
|
|
9
|
+
props: Props;
|
|
10
|
+
bind?: PropKey;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export const updateStyle = ( { elementID, styleDefID, meta, props, bind = 'classes' }: UpdateStyleProps ) => {
|
|
14
|
+
const container = getContainer( elementID );
|
|
15
|
+
|
|
16
|
+
runCommand( 'document/atomic-widgets/styles', {
|
|
17
|
+
container,
|
|
18
|
+
styleDefId: styleDefID,
|
|
19
|
+
bind,
|
|
20
|
+
meta,
|
|
21
|
+
props,
|
|
22
|
+
} );
|
|
23
|
+
};
|
package/src/types.ts
CHANGED
|
@@ -1,15 +1,59 @@
|
|
|
1
|
+
export type ElementID = string;
|
|
2
|
+
|
|
1
3
|
export type Element = {
|
|
2
|
-
id:
|
|
4
|
+
id: ElementID;
|
|
3
5
|
type: string;
|
|
4
6
|
};
|
|
5
7
|
|
|
8
|
+
export type Breakpoint = null | string;
|
|
9
|
+
|
|
10
|
+
export type StyleDefState =
|
|
11
|
+
| null
|
|
12
|
+
| 'hover'
|
|
13
|
+
| 'focus'
|
|
14
|
+
| 'active'
|
|
15
|
+
| 'visited'
|
|
16
|
+
| 'disabled'
|
|
17
|
+
| 'checked'
|
|
18
|
+
| 'selected'
|
|
19
|
+
| 'hidden'
|
|
20
|
+
| 'visible';
|
|
21
|
+
|
|
22
|
+
export type StyleDefMeta = {
|
|
23
|
+
breakpoint: Breakpoint;
|
|
24
|
+
state: StyleDefState;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export type StyleDefVariant = {
|
|
28
|
+
meta: StyleDefMeta;
|
|
29
|
+
props: Props;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export type StyleDefID = string;
|
|
33
|
+
|
|
34
|
+
export type StyleDefinition = {
|
|
35
|
+
id: StyleDefID;
|
|
36
|
+
variants: StyleDefVariant[];
|
|
37
|
+
label?: string;
|
|
38
|
+
type: 'class';
|
|
39
|
+
};
|
|
40
|
+
|
|
6
41
|
export type ElementType = {
|
|
7
42
|
key: string;
|
|
8
|
-
controls:
|
|
43
|
+
controls: ControlItem[];
|
|
9
44
|
title: string;
|
|
10
45
|
};
|
|
11
46
|
|
|
12
|
-
export type
|
|
47
|
+
export type ControlsSection = {
|
|
48
|
+
type: 'section';
|
|
49
|
+
value: {
|
|
50
|
+
description?: string;
|
|
51
|
+
label: string;
|
|
52
|
+
items: ControlItem[];
|
|
53
|
+
};
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export type Control = {
|
|
13
57
|
type: 'control';
|
|
14
58
|
value: {
|
|
15
59
|
bind: string;
|
|
@@ -20,6 +64,8 @@ export type AtomicControl = {
|
|
|
20
64
|
};
|
|
21
65
|
};
|
|
22
66
|
|
|
67
|
+
export type ControlItem = ControlsSection | Control;
|
|
68
|
+
|
|
23
69
|
type MaybeArray< T > = T | T[];
|
|
24
70
|
|
|
25
71
|
type TransformablePropValue = {
|
package/src/__tests__/utils.ts
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { V1Element, V1ElementModelProps, V1ElementSettingsProps } from '../sync/types';
|
|
2
|
-
|
|
3
|
-
export function mockV1Element( {
|
|
4
|
-
model: partialModel = {},
|
|
5
|
-
settings: partialSettings = {},
|
|
6
|
-
}: {
|
|
7
|
-
model?: Partial< V1ElementModelProps >;
|
|
8
|
-
settings?: Partial< V1ElementSettingsProps >;
|
|
9
|
-
} ): V1Element {
|
|
10
|
-
const model = {
|
|
11
|
-
elType: 'widget',
|
|
12
|
-
id: '1',
|
|
13
|
-
...partialModel,
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
return {
|
|
17
|
-
model: {
|
|
18
|
-
get: ( key ) => {
|
|
19
|
-
return model[ key ];
|
|
20
|
-
},
|
|
21
|
-
},
|
|
22
|
-
settings: {
|
|
23
|
-
get: ( key ) => {
|
|
24
|
-
return partialSettings[ key ];
|
|
25
|
-
},
|
|
26
|
-
},
|
|
27
|
-
};
|
|
28
|
-
}
|
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
import * as React from 'react';
|
|
2
|
-
import { act, render, screen } from '@testing-library/react';
|
|
3
|
-
import { EditingPanel } from '../editing-panel';
|
|
4
|
-
import { V1Element, ExtendedWindow } from '../../sync/types';
|
|
5
|
-
import { mockV1Element } from '../../__tests__/utils';
|
|
6
|
-
import { dispatchCommandAfter } from 'test-utils';
|
|
7
|
-
|
|
8
|
-
describe( '<EditingPanel />', () => {
|
|
9
|
-
const getElements = jest.fn();
|
|
10
|
-
|
|
11
|
-
function selectElements( elements: V1Element[] ) {
|
|
12
|
-
act( () => {
|
|
13
|
-
getElements.mockReturnValue( elements );
|
|
14
|
-
dispatchCommandAfter( 'document/elements/select' );
|
|
15
|
-
} );
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function deselectElements() {
|
|
19
|
-
act( () => {
|
|
20
|
-
getElements.mockReturnValue( [] );
|
|
21
|
-
dispatchCommandAfter( 'document/elements/deselect' );
|
|
22
|
-
} );
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
beforeEach( () => {
|
|
26
|
-
const extendedWindow = window as unknown as ExtendedWindow;
|
|
27
|
-
|
|
28
|
-
extendedWindow.elementor = {
|
|
29
|
-
selection: {
|
|
30
|
-
getElements,
|
|
31
|
-
},
|
|
32
|
-
widgetsCache: {
|
|
33
|
-
'atomic-heading': {
|
|
34
|
-
controls: {},
|
|
35
|
-
atomic_controls: [],
|
|
36
|
-
title: 'Atomic Heading',
|
|
37
|
-
},
|
|
38
|
-
heading: {
|
|
39
|
-
controls: {},
|
|
40
|
-
title: 'Heading',
|
|
41
|
-
},
|
|
42
|
-
},
|
|
43
|
-
};
|
|
44
|
-
} );
|
|
45
|
-
|
|
46
|
-
it( 'should render the selected element editing panel', () => {
|
|
47
|
-
// Arrange + Act.
|
|
48
|
-
render( <EditingPanel /> );
|
|
49
|
-
|
|
50
|
-
// Assert.
|
|
51
|
-
expect( screen.queryByText( 'Edit Atomic Heading' ) ).not.toBeInTheDocument();
|
|
52
|
-
|
|
53
|
-
// Act.
|
|
54
|
-
selectElements( [ mockV1Element( { model: { widgetType: 'atomic-heading' } } ) ] );
|
|
55
|
-
|
|
56
|
-
// Assert.
|
|
57
|
-
expect( screen.getByText( 'Edit Atomic Heading' ) ).toBeInTheDocument();
|
|
58
|
-
|
|
59
|
-
// Act.
|
|
60
|
-
deselectElements();
|
|
61
|
-
|
|
62
|
-
// Assert.
|
|
63
|
-
expect( screen.queryByText( 'Edit Atomic Heading' ) ).not.toBeInTheDocument();
|
|
64
|
-
} );
|
|
65
|
-
|
|
66
|
-
it.each( [
|
|
67
|
-
{
|
|
68
|
-
title: 'multiple elements are selected',
|
|
69
|
-
selected: [
|
|
70
|
-
mockV1Element( { model: { widgetType: 'atomic-heading' } } ),
|
|
71
|
-
mockV1Element( { model: { widgetType: 'atomic-heading' } } ),
|
|
72
|
-
],
|
|
73
|
-
},
|
|
74
|
-
{
|
|
75
|
-
title: 'the element type does not exist',
|
|
76
|
-
selected: [ mockV1Element( { model: { widgetType: 'atomic-button' } } ) ],
|
|
77
|
-
},
|
|
78
|
-
{
|
|
79
|
-
title: 'the element does not have atomic controls',
|
|
80
|
-
selected: [ mockV1Element( { model: { widgetType: 'heading' } } ) ],
|
|
81
|
-
},
|
|
82
|
-
] )( 'should not render panel when $title', ( { selected } ) => {
|
|
83
|
-
// Arrange.
|
|
84
|
-
selectElements( selected );
|
|
85
|
-
|
|
86
|
-
// Act.
|
|
87
|
-
render( <EditingPanel /> );
|
|
88
|
-
|
|
89
|
-
// Assert.
|
|
90
|
-
expect( screen.queryByText( 'Edit Atomic Heading' ) ).not.toBeInTheDocument();
|
|
91
|
-
} );
|
|
92
|
-
} );
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
import * as React from 'react';
|
|
2
|
-
import { fireEvent, render, screen } from '@testing-library/react';
|
|
3
|
-
import { SettingsControl } from '../settings-control';
|
|
4
|
-
import { useControl } from '../../../contexts/control-context';
|
|
5
|
-
import { updateSettings } from '../../../sync/update-settings';
|
|
6
|
-
import { useWidgetSettings } from '../../../hooks/use-widget-settings';
|
|
7
|
-
import { ElementContext } from '../../../contexts/element-context';
|
|
8
|
-
import { Element } from '../../../types';
|
|
9
|
-
|
|
10
|
-
jest.mock( '../../../sync/update-settings' );
|
|
11
|
-
jest.mock( '../../../hooks/use-widget-settings' );
|
|
12
|
-
|
|
13
|
-
describe( 'SettingsControl', () => {
|
|
14
|
-
beforeEach( () => {
|
|
15
|
-
jest.mocked( useWidgetSettings ).mockReturnValue( 'Hello, World!' );
|
|
16
|
-
} );
|
|
17
|
-
|
|
18
|
-
it( 'should set the initial value', () => {
|
|
19
|
-
// Arrange.
|
|
20
|
-
const element = { id: '1-heading' } as Element;
|
|
21
|
-
const bind = 'text';
|
|
22
|
-
|
|
23
|
-
// Act.
|
|
24
|
-
render(
|
|
25
|
-
<ElementContext element={ element }>
|
|
26
|
-
<SettingsControl bind={ bind }>
|
|
27
|
-
<MockControl />
|
|
28
|
-
</SettingsControl>
|
|
29
|
-
</ElementContext>
|
|
30
|
-
);
|
|
31
|
-
|
|
32
|
-
// Assert.
|
|
33
|
-
expect( screen.getByRole( 'textbox', { name: bind } ) ).toHaveValue( 'Hello, World!' );
|
|
34
|
-
} );
|
|
35
|
-
|
|
36
|
-
it( 'should pass the updated payload when input value changes', () => {
|
|
37
|
-
// Arrange.
|
|
38
|
-
const element = { id: '1-heading' } as Element;
|
|
39
|
-
const bind = 'text';
|
|
40
|
-
|
|
41
|
-
// Act.
|
|
42
|
-
render(
|
|
43
|
-
<ElementContext element={ element }>
|
|
44
|
-
<SettingsControl bind={ bind }>
|
|
45
|
-
<MockControl />
|
|
46
|
-
</SettingsControl>
|
|
47
|
-
</ElementContext>
|
|
48
|
-
);
|
|
49
|
-
|
|
50
|
-
const input = screen.getByRole( 'textbox', { name: bind } );
|
|
51
|
-
const newValue = 'Goodbye, World!';
|
|
52
|
-
|
|
53
|
-
fireEvent.change( input, { target: { value: newValue } } );
|
|
54
|
-
|
|
55
|
-
// Assert.
|
|
56
|
-
expect( jest.mocked( updateSettings ) ).toHaveBeenCalledWith( {
|
|
57
|
-
id: element.id,
|
|
58
|
-
props: { [ bind ]: newValue },
|
|
59
|
-
} );
|
|
60
|
-
} );
|
|
61
|
-
} );
|
|
62
|
-
|
|
63
|
-
const MockControl = () => {
|
|
64
|
-
const { value, setValue, bind } = useControl< string >( '' );
|
|
65
|
-
|
|
66
|
-
const handleChange = ( event: React.ChangeEvent< HTMLInputElement > ) => {
|
|
67
|
-
setValue( event.target.value );
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
return <input type="text" aria-label={ bind } value={ value } onChange={ handleChange } />;
|
|
71
|
-
};
|