@elementor/editor-components 3.33.0-99 → 3.35.0-324
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/dist/index.js +2225 -128
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2236 -111
- package/dist/index.mjs.map +1 -1
- package/package.json +23 -12
- package/src/api.ts +71 -11
- package/src/component-instance-transformer.ts +24 -0
- package/src/component-overridable-transformer.ts +28 -0
- package/src/components/component-panel-header/component-badge.tsx +62 -0
- package/src/components/component-panel-header/component-panel-header.tsx +58 -0
- package/src/components/component-panel-header/use-overridable-props.ts +14 -0
- package/src/components/components-tab/component-search.tsx +32 -0
- package/src/components/components-tab/components-item.tsx +115 -0
- package/src/components/components-tab/components-list.tsx +141 -0
- package/src/components/components-tab/components.tsx +17 -0
- package/src/components/components-tab/loading-components.tsx +43 -0
- package/src/components/components-tab/search-provider.tsx +38 -0
- package/src/components/consts.ts +1 -0
- package/src/components/create-component-form/create-component-form.tsx +109 -100
- package/src/components/create-component-form/utils/get-component-event-data.ts +54 -0
- package/src/components/create-component-form/utils/replace-element-with-component.ts +28 -10
- package/src/components/edit-component/component-modal.tsx +134 -0
- package/src/components/edit-component/edit-component.tsx +96 -0
- package/src/components/in-edit-mode.tsx +43 -0
- package/src/components/overridable-props/indicator.tsx +80 -0
- package/src/components/overridable-props/overridable-prop-control.tsx +67 -0
- package/src/components/overridable-props/overridable-prop-form.tsx +98 -0
- package/src/components/overridable-props/overridable-prop-indicator.tsx +124 -0
- package/src/components/overridable-props/utils/get-overridable-prop.ts +20 -0
- package/src/create-component-type.ts +194 -0
- package/src/hooks/use-canvas-document.ts +6 -0
- package/src/hooks/use-components.ts +6 -9
- package/src/hooks/use-element-rect.ts +81 -0
- package/src/hooks/use-navigate-back.ts +34 -0
- package/src/init.ts +100 -3
- package/src/mcp/index.ts +14 -0
- package/src/mcp/save-as-component-tool.ts +92 -0
- package/src/populate-store.ts +12 -0
- package/src/prop-types/component-overridable-prop-type.ts +17 -0
- package/src/store/actions/archive-component.ts +16 -0
- package/src/store/actions/create-unpublished-component.ts +40 -0
- package/src/store/actions/load-components-assets.ts +29 -0
- package/src/store/actions/load-components-overridable-props.ts +33 -0
- package/src/store/actions/load-components-styles.ts +44 -0
- package/src/store/actions/remove-component-styles.ts +9 -0
- package/src/store/actions/set-overridable-prop.ts +200 -0
- package/src/store/actions/update-current-component.ts +33 -0
- package/src/store/actions/update-overridable-prop-origin-value.ts +37 -0
- package/src/store/components-styles-provider.ts +24 -0
- package/src/store/store.ts +193 -0
- package/src/store/thunks.ts +10 -0
- package/src/sync/before-save.ts +31 -0
- package/src/sync/create-components-before-save.ts +102 -0
- package/src/sync/set-component-overridable-props-settings-before-save.ts +23 -0
- package/src/sync/update-archived-component-before-save.ts +44 -0
- package/src/sync/update-components-before-save.ts +35 -0
- package/src/types.ts +83 -0
- package/src/utils/component-document-data.ts +19 -0
- package/src/utils/get-component-ids.ts +36 -0
- package/src/utils/get-container-for-new-element.ts +49 -0
- package/src/utils/tracking.ts +47 -0
- package/src/components/components-tab.tsx +0 -6
- package/src/hooks/use-create-component.ts +0 -13
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { forwardRef } from 'react';
|
|
3
|
+
import { CheckIcon, PlusIcon } from '@elementor/icons';
|
|
4
|
+
import { Box, styled } from '@elementor/ui';
|
|
5
|
+
import { __ } from '@wordpress/i18n';
|
|
6
|
+
|
|
7
|
+
const SIZE = 'tiny';
|
|
8
|
+
|
|
9
|
+
const IconContainer = styled( Box )`
|
|
10
|
+
pointer-events: none;
|
|
11
|
+
opacity: 0;
|
|
12
|
+
transition: opacity 0.2s ease-in-out;
|
|
13
|
+
|
|
14
|
+
& > svg {
|
|
15
|
+
position: absolute;
|
|
16
|
+
top: 50%;
|
|
17
|
+
left: 50%;
|
|
18
|
+
transform: translate( -50%, -50% );
|
|
19
|
+
width: 10px;
|
|
20
|
+
height: 10px;
|
|
21
|
+
fill: ${ ( { theme } ) => theme.palette.primary.contrastText };
|
|
22
|
+
stroke: ${ ( { theme } ) => theme.palette.primary.contrastText };
|
|
23
|
+
stroke-width: 2px;
|
|
24
|
+
}
|
|
25
|
+
`;
|
|
26
|
+
|
|
27
|
+
const Content = styled( Box )`
|
|
28
|
+
position: relative;
|
|
29
|
+
display: flex;
|
|
30
|
+
align-items: center;
|
|
31
|
+
justify-content: center;
|
|
32
|
+
cursor: pointer;
|
|
33
|
+
width: 16px;
|
|
34
|
+
height: 16px;
|
|
35
|
+
margin-inline: ${ ( { theme } ) => theme.spacing( 0.5 ) };
|
|
36
|
+
|
|
37
|
+
&:before {
|
|
38
|
+
content: '';
|
|
39
|
+
display: block;
|
|
40
|
+
position: absolute;
|
|
41
|
+
top: 50%;
|
|
42
|
+
left: 50%;
|
|
43
|
+
transform: translate( -50%, -50% ) rotate( 45deg );
|
|
44
|
+
width: 5px;
|
|
45
|
+
height: 5px;
|
|
46
|
+
border-radius: 1px;
|
|
47
|
+
background-color: ${ ( { theme } ) => theme.palette.primary.main };
|
|
48
|
+
transition: all 0.1s ease-in-out;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
&:hover,
|
|
52
|
+
&.enlarged {
|
|
53
|
+
&:before {
|
|
54
|
+
width: 12px;
|
|
55
|
+
height: 12px;
|
|
56
|
+
border-radius: 2px;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.icon {
|
|
60
|
+
opacity: 1;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
`;
|
|
64
|
+
|
|
65
|
+
type Props = {
|
|
66
|
+
isOverridable: boolean;
|
|
67
|
+
isOpen: boolean;
|
|
68
|
+
};
|
|
69
|
+
export const Indicator = forwardRef< HTMLDivElement, Props >( ( { isOpen, isOverridable, ...props }, ref ) => (
|
|
70
|
+
<Content ref={ ref } { ...props } className={ isOpen || isOverridable ? 'enlarged' : '' }>
|
|
71
|
+
<IconContainer
|
|
72
|
+
className="icon"
|
|
73
|
+
aria-label={
|
|
74
|
+
isOverridable ? __( 'Overridable property', 'elementor' ) : __( 'Make prop overridable', 'elementor' )
|
|
75
|
+
}
|
|
76
|
+
>
|
|
77
|
+
{ isOverridable ? <CheckIcon fontSize={ SIZE } /> : <PlusIcon fontSize={ SIZE } /> }
|
|
78
|
+
</IconContainer>
|
|
79
|
+
</Content>
|
|
80
|
+
) );
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { type ComponentType } from 'react';
|
|
3
|
+
import { ControlReplacementsProvider, PropKeyProvider, PropProvider, useBoundProp } from '@elementor/editor-controls';
|
|
4
|
+
import { createTopLevelObjectType, useElement } from '@elementor/editor-editing-panel';
|
|
5
|
+
import { type PropValue } from '@elementor/editor-props';
|
|
6
|
+
import { __getState as getState } from '@elementor/store';
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
componentOverridablePropTypeUtil,
|
|
10
|
+
type ComponentOverridablePropValue,
|
|
11
|
+
} from '../../prop-types/component-overridable-prop-type';
|
|
12
|
+
import { updateOverridablePropOriginValue } from '../../store/actions/update-overridable-prop-origin-value';
|
|
13
|
+
import { selectCurrentComponentId } from '../../store/store';
|
|
14
|
+
|
|
15
|
+
export function OverridablePropControl< T extends object >( {
|
|
16
|
+
OriginalControl,
|
|
17
|
+
...props
|
|
18
|
+
}: T & { OriginalControl: ComponentType< T > } ) {
|
|
19
|
+
const { elementType } = useElement();
|
|
20
|
+
|
|
21
|
+
const { value, bind, setValue, placeholder, ...propContext } = useBoundProp( componentOverridablePropTypeUtil );
|
|
22
|
+
const componentId = selectCurrentComponentId( getState() );
|
|
23
|
+
|
|
24
|
+
if ( ! componentId ) {
|
|
25
|
+
throw new Error( 'Component ID is required' );
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if ( ! value?.override_key ) {
|
|
29
|
+
throw new Error( 'Override key is required' );
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const setOverridableValue = ( newValue: Record< typeof bind, PropValue | null > ) => {
|
|
33
|
+
const propValue = {
|
|
34
|
+
...value,
|
|
35
|
+
origin_value: newValue[ bind ],
|
|
36
|
+
} as ComponentOverridablePropValue;
|
|
37
|
+
|
|
38
|
+
setValue( propValue );
|
|
39
|
+
updateOverridablePropOriginValue( componentId, propValue );
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const propType = createTopLevelObjectType( {
|
|
43
|
+
schema: {
|
|
44
|
+
[ bind ]: elementType.propsSchema[ bind ],
|
|
45
|
+
},
|
|
46
|
+
} );
|
|
47
|
+
|
|
48
|
+
const objectPlaceholder: Record< string, PropValue > | undefined = placeholder
|
|
49
|
+
? { [ bind ]: placeholder }
|
|
50
|
+
: undefined;
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
<PropProvider
|
|
54
|
+
{ ...propContext }
|
|
55
|
+
propType={ propType }
|
|
56
|
+
setValue={ setOverridableValue }
|
|
57
|
+
value={ { [ bind ]: value.origin_value } }
|
|
58
|
+
placeholder={ objectPlaceholder }
|
|
59
|
+
>
|
|
60
|
+
<PropKeyProvider bind={ bind }>
|
|
61
|
+
<ControlReplacementsProvider replacements={ [] }>
|
|
62
|
+
<OriginalControl { ...( props as T ) } />
|
|
63
|
+
</ControlReplacementsProvider>
|
|
64
|
+
</PropKeyProvider>
|
|
65
|
+
</PropProvider>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { Form, MenuListItem } from '@elementor/editor-ui';
|
|
4
|
+
import { Button, FormLabel, Grid, Select, Stack, TextField, Typography } from '@elementor/ui';
|
|
5
|
+
import { __ } from '@wordpress/i18n';
|
|
6
|
+
|
|
7
|
+
import { type OverridableProp } from '../../types';
|
|
8
|
+
|
|
9
|
+
const SIZE = 'tiny';
|
|
10
|
+
|
|
11
|
+
const DEFAULT_GROUP = { value: null, label: __( 'Default', 'elementor' ) };
|
|
12
|
+
|
|
13
|
+
type Props = {
|
|
14
|
+
onSubmit: ( data: { label: string; group: string | null } ) => void;
|
|
15
|
+
currentValue?: OverridableProp;
|
|
16
|
+
groups?: { value: string; label: string }[];
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export function OverridablePropForm( { onSubmit, groups, currentValue }: Props ) {
|
|
20
|
+
const [ propLabel, setPropLabel ] = useState< string | null >( currentValue?.label ?? null );
|
|
21
|
+
const [ group, setGroup ] = useState< string | null >( currentValue?.groupId ?? groups?.[ 0 ]?.value ?? null );
|
|
22
|
+
|
|
23
|
+
const name = __( 'Name', 'elementor' );
|
|
24
|
+
const groupName = __( 'Group Name', 'elementor' );
|
|
25
|
+
|
|
26
|
+
const isCreate = currentValue === undefined;
|
|
27
|
+
|
|
28
|
+
const title = isCreate ? __( 'Create new property', 'elementor' ) : __( 'Update property', 'elementor' );
|
|
29
|
+
const ctaLabel = isCreate ? __( 'Create', 'elementor' ) : __( 'Update', 'elementor' );
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<Form onSubmit={ () => onSubmit( { label: propLabel ?? '', group } ) }>
|
|
33
|
+
<Stack alignItems="start" width="268px">
|
|
34
|
+
<Stack
|
|
35
|
+
direction="row"
|
|
36
|
+
alignItems="center"
|
|
37
|
+
py={ 1 }
|
|
38
|
+
px={ 1.5 }
|
|
39
|
+
sx={ { columnGap: 0.5, borderBottom: '1px solid', borderColor: 'divider', width: '100%', mb: 1.5 } }
|
|
40
|
+
>
|
|
41
|
+
<Typography variant="caption" sx={ { color: 'text.primary', fontWeight: '500', lineHeight: 1 } }>
|
|
42
|
+
{ title }
|
|
43
|
+
</Typography>
|
|
44
|
+
</Stack>
|
|
45
|
+
<Grid container gap={ 0.75 } alignItems="start" px={ 1.5 } mb={ 1.5 }>
|
|
46
|
+
<Grid item xs={ 12 }>
|
|
47
|
+
<FormLabel size="tiny">{ name }</FormLabel>
|
|
48
|
+
</Grid>
|
|
49
|
+
<Grid item xs={ 12 }>
|
|
50
|
+
<TextField
|
|
51
|
+
name={ name }
|
|
52
|
+
size={ SIZE }
|
|
53
|
+
fullWidth
|
|
54
|
+
placeholder={ __( 'Enter value', 'elementor' ) }
|
|
55
|
+
value={ propLabel ?? '' }
|
|
56
|
+
onChange={ ( e: React.ChangeEvent< HTMLInputElement > ) => setPropLabel( e.target.value ) }
|
|
57
|
+
/>
|
|
58
|
+
</Grid>
|
|
59
|
+
</Grid>
|
|
60
|
+
<Grid container gap={ 0.75 } alignItems="start" px={ 1.5 } mb={ 1.5 }>
|
|
61
|
+
<Grid item xs={ 12 }>
|
|
62
|
+
<FormLabel size="tiny">{ groupName }</FormLabel>
|
|
63
|
+
</Grid>
|
|
64
|
+
<Grid item xs={ 12 }>
|
|
65
|
+
<Select
|
|
66
|
+
name={ groupName }
|
|
67
|
+
size={ SIZE }
|
|
68
|
+
fullWidth
|
|
69
|
+
value={ group ?? null }
|
|
70
|
+
onChange={ setGroup }
|
|
71
|
+
displayEmpty
|
|
72
|
+
renderValue={ ( selectedValue: string | null ) => {
|
|
73
|
+
if ( ! selectedValue || selectedValue === '' ) {
|
|
74
|
+
const [ firstGroup = DEFAULT_GROUP ] = groups ?? [];
|
|
75
|
+
|
|
76
|
+
return firstGroup.label;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return groups?.find( ( { value } ) => value === selectedValue )?.label ?? selectedValue;
|
|
80
|
+
} }
|
|
81
|
+
>
|
|
82
|
+
{ ( groups ?? [ DEFAULT_GROUP ] ).map( ( { label: groupLabel, ...props } ) => (
|
|
83
|
+
<MenuListItem key={ props.value } { ...props } value={ props.value ?? '' }>
|
|
84
|
+
{ groupLabel }
|
|
85
|
+
</MenuListItem>
|
|
86
|
+
) ) }
|
|
87
|
+
</Select>
|
|
88
|
+
</Grid>
|
|
89
|
+
</Grid>
|
|
90
|
+
<Stack direction="row" justifyContent="flex-end" alignSelf="end" mt={ 1.5 } py={ 1 } px={ 1.5 }>
|
|
91
|
+
<Button type="submit" disabled={ ! propLabel } variant="contained" color="primary" size="small">
|
|
92
|
+
{ ctaLabel }
|
|
93
|
+
</Button>
|
|
94
|
+
</Stack>
|
|
95
|
+
</Stack>
|
|
96
|
+
</Form>
|
|
97
|
+
);
|
|
98
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { useBoundProp } from '@elementor/editor-controls';
|
|
3
|
+
import { getV1CurrentDocument } from '@elementor/editor-documents';
|
|
4
|
+
import { useElement } from '@elementor/editor-editing-panel';
|
|
5
|
+
import { getWidgetsCache } from '@elementor/editor-elements';
|
|
6
|
+
import { type TransformablePropValue } from '@elementor/editor-props';
|
|
7
|
+
import { __getState as getState } from '@elementor/store';
|
|
8
|
+
import { bindPopover, bindTrigger, Popover, Tooltip, usePopupState } from '@elementor/ui';
|
|
9
|
+
import { __ } from '@wordpress/i18n';
|
|
10
|
+
|
|
11
|
+
import { componentOverridablePropTypeUtil } from '../../prop-types/component-overridable-prop-type';
|
|
12
|
+
import { setOverridableProp } from '../../store/actions/set-overridable-prop';
|
|
13
|
+
import { selectOverridableProps } from '../../store/store';
|
|
14
|
+
import { type OverridableProps } from '../../types';
|
|
15
|
+
import { COMPONENT_DOCUMENT_TYPE } from '../consts';
|
|
16
|
+
import { Indicator } from './indicator';
|
|
17
|
+
import { OverridablePropForm } from './overridable-prop-form';
|
|
18
|
+
import { getOverridableProp } from './utils/get-overridable-prop';
|
|
19
|
+
|
|
20
|
+
const FORBIDDEN_KEYS = [ '_cssid', 'attributes' ];
|
|
21
|
+
|
|
22
|
+
export function OverridablePropIndicator() {
|
|
23
|
+
const { bind } = useBoundProp();
|
|
24
|
+
const currentDocument = getV1CurrentDocument();
|
|
25
|
+
|
|
26
|
+
if ( currentDocument.config.type !== COMPONENT_DOCUMENT_TYPE || ! currentDocument.id ) {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if ( ! isPropAllowed( bind ) ) {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const overridableProps = selectOverridableProps( getState(), currentDocument.id );
|
|
35
|
+
|
|
36
|
+
return <Content componentId={ currentDocument.id } overridableProps={ overridableProps } />;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
type Props = {
|
|
40
|
+
componentId: number;
|
|
41
|
+
overridableProps?: OverridableProps;
|
|
42
|
+
};
|
|
43
|
+
export function Content( { componentId, overridableProps }: Props ) {
|
|
44
|
+
const {
|
|
45
|
+
element: { id: elementId },
|
|
46
|
+
elementType,
|
|
47
|
+
} = useElement();
|
|
48
|
+
const { value, bind, propType } = useBoundProp();
|
|
49
|
+
const { value: overridableValue, setValue: setOverridableValue } = useBoundProp( componentOverridablePropTypeUtil );
|
|
50
|
+
|
|
51
|
+
const popupState = usePopupState( {
|
|
52
|
+
variant: 'popover',
|
|
53
|
+
} );
|
|
54
|
+
|
|
55
|
+
const triggerProps = bindTrigger( popupState );
|
|
56
|
+
const popoverProps = bindPopover( popupState );
|
|
57
|
+
|
|
58
|
+
const { elType } = getWidgetsCache()?.[ elementType.key ] ?? { elType: 'widget' };
|
|
59
|
+
|
|
60
|
+
const handleSubmit = ( { label, group }: { label: string; group: string | null } ) => {
|
|
61
|
+
const originValue = ! overridableValue ? value ?? propType.default : overridableValue?.origin_value ?? {};
|
|
62
|
+
|
|
63
|
+
const overridablePropConfig = setOverridableProp( {
|
|
64
|
+
componentId,
|
|
65
|
+
overrideKey: overridableValue?.override_key ?? null,
|
|
66
|
+
elementId,
|
|
67
|
+
label,
|
|
68
|
+
groupId: group,
|
|
69
|
+
propKey: bind,
|
|
70
|
+
elType: elType ?? 'widget',
|
|
71
|
+
widgetType: elementType.key,
|
|
72
|
+
originValue,
|
|
73
|
+
} );
|
|
74
|
+
|
|
75
|
+
if ( ! overridableValue && overridablePropConfig ) {
|
|
76
|
+
setOverridableValue( {
|
|
77
|
+
override_key: overridablePropConfig.overrideKey,
|
|
78
|
+
origin_value: originValue as TransformablePropValue< string, unknown >,
|
|
79
|
+
} );
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
popupState.close();
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const overridableConfig = overridableValue
|
|
86
|
+
? getOverridableProp( { componentId, overrideKey: overridableValue.override_key } )
|
|
87
|
+
: undefined;
|
|
88
|
+
|
|
89
|
+
return (
|
|
90
|
+
<>
|
|
91
|
+
<Tooltip placement="top" title={ __( 'Override Property', 'elementor' ) }>
|
|
92
|
+
<Indicator { ...triggerProps } isOpen={ !! popoverProps.open } isOverridable={ !! overridableValue } />
|
|
93
|
+
</Tooltip>
|
|
94
|
+
<Popover
|
|
95
|
+
disableScrollLock
|
|
96
|
+
anchorOrigin={ {
|
|
97
|
+
vertical: 'bottom',
|
|
98
|
+
horizontal: 'right',
|
|
99
|
+
} }
|
|
100
|
+
transformOrigin={ {
|
|
101
|
+
vertical: 'top',
|
|
102
|
+
horizontal: 'right',
|
|
103
|
+
} }
|
|
104
|
+
PaperProps={ {
|
|
105
|
+
sx: { my: 2.5 },
|
|
106
|
+
} }
|
|
107
|
+
{ ...popoverProps }
|
|
108
|
+
>
|
|
109
|
+
<OverridablePropForm
|
|
110
|
+
onSubmit={ handleSubmit }
|
|
111
|
+
groups={ overridableProps?.groups.order.map( ( groupId ) => ( {
|
|
112
|
+
value: groupId,
|
|
113
|
+
label: overridableProps.groups.items[ groupId ].label,
|
|
114
|
+
} ) ) }
|
|
115
|
+
currentValue={ overridableConfig }
|
|
116
|
+
/>
|
|
117
|
+
</Popover>
|
|
118
|
+
</>
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function isPropAllowed( bind: string ) {
|
|
123
|
+
return ! FORBIDDEN_KEYS.includes( bind );
|
|
124
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { __getState as getState } from '@elementor/store';
|
|
2
|
+
|
|
3
|
+
import { selectOverridableProps } from '../../../store/store';
|
|
4
|
+
import { type OverridableProp } from '../../../types';
|
|
5
|
+
|
|
6
|
+
export function getOverridableProp( {
|
|
7
|
+
componentId,
|
|
8
|
+
overrideKey,
|
|
9
|
+
}: {
|
|
10
|
+
componentId: number;
|
|
11
|
+
overrideKey: string;
|
|
12
|
+
} ): OverridableProp | undefined {
|
|
13
|
+
const overridableProps = selectOverridableProps( getState(), componentId );
|
|
14
|
+
|
|
15
|
+
if ( ! overridableProps ) {
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return overridableProps.props[ overrideKey ];
|
|
20
|
+
}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type BackboneModel,
|
|
3
|
+
type CreateTemplatedElementTypeOptions,
|
|
4
|
+
createTemplatedElementView,
|
|
5
|
+
type ElementModel,
|
|
6
|
+
type ElementType,
|
|
7
|
+
type ElementView,
|
|
8
|
+
type LegacyWindow,
|
|
9
|
+
} from '@elementor/editor-canvas';
|
|
10
|
+
import { getCurrentDocument } from '@elementor/editor-documents';
|
|
11
|
+
import { __privateRunCommand as runCommand } from '@elementor/editor-v1-adapters';
|
|
12
|
+
import { __ } from '@wordpress/i18n';
|
|
13
|
+
|
|
14
|
+
import { apiClient } from './api';
|
|
15
|
+
import { type ComponentInstancePropValue, type ExtendedWindow } from './types';
|
|
16
|
+
import { trackComponentEvent } from './utils/tracking';
|
|
17
|
+
|
|
18
|
+
type ContextMenuEventData = { location: string; secondaryLocation: string; trigger: string };
|
|
19
|
+
|
|
20
|
+
export const TYPE = 'e-component';
|
|
21
|
+
|
|
22
|
+
export function createComponentType(
|
|
23
|
+
options: CreateTemplatedElementTypeOptions & { showLockedByModal?: ( lockedBy: string ) => void }
|
|
24
|
+
): typeof ElementType {
|
|
25
|
+
const legacyWindow = window as unknown as LegacyWindow;
|
|
26
|
+
|
|
27
|
+
return class extends legacyWindow.elementor.modules.elements.types.Widget {
|
|
28
|
+
getType() {
|
|
29
|
+
return options.type;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
getView() {
|
|
33
|
+
return createComponentView( options );
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function createComponentView(
|
|
39
|
+
options: CreateTemplatedElementTypeOptions & { showLockedByModal?: ( lockedBy: string ) => void }
|
|
40
|
+
): typeof ElementView {
|
|
41
|
+
return class extends createTemplatedElementView( options ) {
|
|
42
|
+
legacyWindow = window as unknown as LegacyWindow & ExtendedWindow;
|
|
43
|
+
eventsManagerConfig = this.legacyWindow.elementorCommon.eventsManager.config;
|
|
44
|
+
|
|
45
|
+
isComponentCurrentlyEdited() {
|
|
46
|
+
const currentDocument = getCurrentDocument();
|
|
47
|
+
|
|
48
|
+
return currentDocument?.id === this.getComponentId();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
afterSettingsResolve( settings: { [ key: string ]: unknown } ) {
|
|
52
|
+
if ( settings.component_instance ) {
|
|
53
|
+
this.collection = this.legacyWindow.elementor.createBackboneElementsCollection(
|
|
54
|
+
settings.component_instance
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
this.collection.models.forEach( setInactiveRecursively );
|
|
58
|
+
|
|
59
|
+
settings.component_instance = '<template data-children-placeholder></template>';
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return settings;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
getDomElement() {
|
|
66
|
+
// Component does not have a DOM element, so we return the first child's DOM element.
|
|
67
|
+
return this.children.findByIndex( 0 )?.getDomElement() ?? this.$el;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
attachBuffer( collectionView: this, buffer: DocumentFragment ): void {
|
|
71
|
+
const childrenPlaceholder = collectionView.$el.find( '[data-children-placeholder]' ).get( 0 );
|
|
72
|
+
|
|
73
|
+
if ( ! childrenPlaceholder ) {
|
|
74
|
+
super.attachBuffer( collectionView, buffer );
|
|
75
|
+
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
childrenPlaceholder.replaceWith( buffer );
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
getComponentId() {
|
|
83
|
+
const componentInstance = (
|
|
84
|
+
this.options?.model?.get( 'settings' )?.get( 'component_instance' ) as ComponentInstancePropValue
|
|
85
|
+
)?.value;
|
|
86
|
+
|
|
87
|
+
return componentInstance.component_id;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
getContextMenuGroups() {
|
|
91
|
+
const filteredGroups = super.getContextMenuGroups().filter( ( group ) => group.name !== 'save' );
|
|
92
|
+
const componentId = this.getComponentId();
|
|
93
|
+
if ( ! componentId ) {
|
|
94
|
+
return filteredGroups;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const newGroup = [
|
|
98
|
+
{
|
|
99
|
+
name: 'edit component',
|
|
100
|
+
actions: [
|
|
101
|
+
{
|
|
102
|
+
name: 'edit component',
|
|
103
|
+
icon: 'eicon-edit',
|
|
104
|
+
title: () => __( 'Edit Component', 'elementor' ),
|
|
105
|
+
isEnabled: () => true,
|
|
106
|
+
callback: ( _: unknown, eventData: ContextMenuEventData ) =>
|
|
107
|
+
this.editComponent( eventData ),
|
|
108
|
+
},
|
|
109
|
+
],
|
|
110
|
+
},
|
|
111
|
+
];
|
|
112
|
+
return [ ...filteredGroups, ...newGroup ];
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async switchDocument() {
|
|
116
|
+
//todo: handle unpublished
|
|
117
|
+
const { isAllowedToSwitchDocument, lockedBy } = await apiClient.getComponentLockStatus(
|
|
118
|
+
this.getComponentId() as number
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
if ( ! isAllowedToSwitchDocument ) {
|
|
122
|
+
options.showLockedByModal?.( lockedBy || '' );
|
|
123
|
+
} else {
|
|
124
|
+
runCommand( 'editor/documents/switch', {
|
|
125
|
+
id: this.getComponentId(),
|
|
126
|
+
mode: 'autosave',
|
|
127
|
+
selector: `[data-id="${ this.model.get( 'id' ) }"]`,
|
|
128
|
+
shouldScroll: false,
|
|
129
|
+
} );
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
editComponent( { trigger, location, secondaryLocation }: ContextMenuEventData ) {
|
|
134
|
+
if ( this.isComponentCurrentlyEdited() ) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
this.switchDocument();
|
|
139
|
+
|
|
140
|
+
const editorSettings = this.model.get( 'editor_settings' );
|
|
141
|
+
|
|
142
|
+
trackComponentEvent( {
|
|
143
|
+
action: 'edited',
|
|
144
|
+
component_uid: editorSettings?.component_uid,
|
|
145
|
+
component_name: editorSettings?.title,
|
|
146
|
+
location,
|
|
147
|
+
secondary_location: secondaryLocation,
|
|
148
|
+
trigger,
|
|
149
|
+
} );
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
handleDblClick( e: MouseEvent ) {
|
|
153
|
+
e.stopPropagation();
|
|
154
|
+
|
|
155
|
+
const { triggers, locations, secondaryLocations } = this.eventsManagerConfig;
|
|
156
|
+
|
|
157
|
+
this.editComponent( {
|
|
158
|
+
trigger: triggers.doubleClick,
|
|
159
|
+
location: locations.canvas,
|
|
160
|
+
secondaryLocation: secondaryLocations.canvasElement,
|
|
161
|
+
} );
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
events() {
|
|
165
|
+
return {
|
|
166
|
+
...super.events(),
|
|
167
|
+
dblclick: this.handleDblClick,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
attributes() {
|
|
172
|
+
return {
|
|
173
|
+
...super.attributes(),
|
|
174
|
+
'data-elementor-id': this.getComponentId(),
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function setInactiveRecursively( model: BackboneModel< ElementModel > ) {
|
|
181
|
+
const editSettings = model.get( 'editSettings' );
|
|
182
|
+
|
|
183
|
+
if ( editSettings ) {
|
|
184
|
+
editSettings.set( 'inactive', true );
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const elements = model.get( 'elements' );
|
|
188
|
+
|
|
189
|
+
if ( elements ) {
|
|
190
|
+
elements.forEach( ( childModel ) => {
|
|
191
|
+
setInactiveRecursively( childModel );
|
|
192
|
+
} );
|
|
193
|
+
}
|
|
194
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { getCanvasIframeDocument } from '@elementor/editor-canvas';
|
|
2
|
+
import { __privateUseListenTo as useListenTo, commandEndEvent } from '@elementor/editor-v1-adapters';
|
|
3
|
+
|
|
4
|
+
export function useCanvasDocument() {
|
|
5
|
+
return useListenTo( commandEndEvent( 'editor/documents/attach-preview' ), () => getCanvasIframeDocument() );
|
|
6
|
+
}
|
|
@@ -1,13 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { __useSelector as useSelector } from '@elementor/store';
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
export const COMPONENTS_QUERY_KEY = 'components';
|
|
3
|
+
import { selectComponents, selectLoadIsPending } from '../store/store';
|
|
6
4
|
|
|
7
5
|
export const useComponents = () => {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
} );
|
|
6
|
+
const components = useSelector( selectComponents );
|
|
7
|
+
const isLoading = useSelector( selectLoadIsPending );
|
|
8
|
+
|
|
9
|
+
return { components, isLoading };
|
|
13
10
|
};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
import { throttle } from '@elementor/utils';
|
|
3
|
+
|
|
4
|
+
export function useElementRect( element: HTMLElement | null ) {
|
|
5
|
+
const [ rect, setRect ] = useState< DOMRect >( new DOMRect( 0, 0, 0, 0 ) );
|
|
6
|
+
|
|
7
|
+
const onChange = throttle(
|
|
8
|
+
() => {
|
|
9
|
+
setRect( element?.getBoundingClientRect() ?? new DOMRect( 0, 0, 0, 0 ) );
|
|
10
|
+
},
|
|
11
|
+
20,
|
|
12
|
+
true
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
useScrollListener( { element, onChange } );
|
|
16
|
+
useResizeListener( { element, onChange } );
|
|
17
|
+
useMutationsListener( { element, onChange } );
|
|
18
|
+
|
|
19
|
+
useEffect(
|
|
20
|
+
() => () => {
|
|
21
|
+
onChange.cancel();
|
|
22
|
+
},
|
|
23
|
+
[ onChange ]
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
return rect;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
type ListenerProps = {
|
|
30
|
+
element: HTMLElement | null;
|
|
31
|
+
onChange: () => void;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
function useScrollListener( { element, onChange }: ListenerProps ) {
|
|
35
|
+
useEffect( () => {
|
|
36
|
+
if ( ! element ) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const win = element.ownerDocument?.defaultView;
|
|
41
|
+
win?.addEventListener( 'scroll', onChange, { passive: true } );
|
|
42
|
+
|
|
43
|
+
return () => {
|
|
44
|
+
win?.removeEventListener( 'scroll', onChange );
|
|
45
|
+
};
|
|
46
|
+
}, [ element, onChange ] );
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function useResizeListener( { element, onChange }: ListenerProps ) {
|
|
50
|
+
useEffect( () => {
|
|
51
|
+
if ( ! element ) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const resizeObserver = new ResizeObserver( onChange );
|
|
56
|
+
resizeObserver.observe( element );
|
|
57
|
+
|
|
58
|
+
const win = element.ownerDocument?.defaultView;
|
|
59
|
+
win?.addEventListener( 'resize', onChange, { passive: true } );
|
|
60
|
+
|
|
61
|
+
return () => {
|
|
62
|
+
resizeObserver.disconnect();
|
|
63
|
+
win?.removeEventListener( 'resize', onChange );
|
|
64
|
+
};
|
|
65
|
+
}, [ element, onChange ] );
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function useMutationsListener( { element, onChange }: ListenerProps ) {
|
|
69
|
+
useEffect( () => {
|
|
70
|
+
if ( ! element ) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const mutationObserver = new MutationObserver( onChange );
|
|
75
|
+
mutationObserver.observe( element, { childList: true, subtree: true } );
|
|
76
|
+
|
|
77
|
+
return () => {
|
|
78
|
+
mutationObserver.disconnect();
|
|
79
|
+
};
|
|
80
|
+
}, [ element, onChange ] );
|
|
81
|
+
}
|