@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.
Files changed (63) hide show
  1. package/dist/index.js +2225 -128
  2. package/dist/index.js.map +1 -1
  3. package/dist/index.mjs +2236 -111
  4. package/dist/index.mjs.map +1 -1
  5. package/package.json +23 -12
  6. package/src/api.ts +71 -11
  7. package/src/component-instance-transformer.ts +24 -0
  8. package/src/component-overridable-transformer.ts +28 -0
  9. package/src/components/component-panel-header/component-badge.tsx +62 -0
  10. package/src/components/component-panel-header/component-panel-header.tsx +58 -0
  11. package/src/components/component-panel-header/use-overridable-props.ts +14 -0
  12. package/src/components/components-tab/component-search.tsx +32 -0
  13. package/src/components/components-tab/components-item.tsx +115 -0
  14. package/src/components/components-tab/components-list.tsx +141 -0
  15. package/src/components/components-tab/components.tsx +17 -0
  16. package/src/components/components-tab/loading-components.tsx +43 -0
  17. package/src/components/components-tab/search-provider.tsx +38 -0
  18. package/src/components/consts.ts +1 -0
  19. package/src/components/create-component-form/create-component-form.tsx +109 -100
  20. package/src/components/create-component-form/utils/get-component-event-data.ts +54 -0
  21. package/src/components/create-component-form/utils/replace-element-with-component.ts +28 -10
  22. package/src/components/edit-component/component-modal.tsx +134 -0
  23. package/src/components/edit-component/edit-component.tsx +96 -0
  24. package/src/components/in-edit-mode.tsx +43 -0
  25. package/src/components/overridable-props/indicator.tsx +80 -0
  26. package/src/components/overridable-props/overridable-prop-control.tsx +67 -0
  27. package/src/components/overridable-props/overridable-prop-form.tsx +98 -0
  28. package/src/components/overridable-props/overridable-prop-indicator.tsx +124 -0
  29. package/src/components/overridable-props/utils/get-overridable-prop.ts +20 -0
  30. package/src/create-component-type.ts +194 -0
  31. package/src/hooks/use-canvas-document.ts +6 -0
  32. package/src/hooks/use-components.ts +6 -9
  33. package/src/hooks/use-element-rect.ts +81 -0
  34. package/src/hooks/use-navigate-back.ts +34 -0
  35. package/src/init.ts +100 -3
  36. package/src/mcp/index.ts +14 -0
  37. package/src/mcp/save-as-component-tool.ts +92 -0
  38. package/src/populate-store.ts +12 -0
  39. package/src/prop-types/component-overridable-prop-type.ts +17 -0
  40. package/src/store/actions/archive-component.ts +16 -0
  41. package/src/store/actions/create-unpublished-component.ts +40 -0
  42. package/src/store/actions/load-components-assets.ts +29 -0
  43. package/src/store/actions/load-components-overridable-props.ts +33 -0
  44. package/src/store/actions/load-components-styles.ts +44 -0
  45. package/src/store/actions/remove-component-styles.ts +9 -0
  46. package/src/store/actions/set-overridable-prop.ts +200 -0
  47. package/src/store/actions/update-current-component.ts +33 -0
  48. package/src/store/actions/update-overridable-prop-origin-value.ts +37 -0
  49. package/src/store/components-styles-provider.ts +24 -0
  50. package/src/store/store.ts +193 -0
  51. package/src/store/thunks.ts +10 -0
  52. package/src/sync/before-save.ts +31 -0
  53. package/src/sync/create-components-before-save.ts +102 -0
  54. package/src/sync/set-component-overridable-props-settings-before-save.ts +23 -0
  55. package/src/sync/update-archived-component-before-save.ts +44 -0
  56. package/src/sync/update-components-before-save.ts +35 -0
  57. package/src/types.ts +83 -0
  58. package/src/utils/component-document-data.ts +19 -0
  59. package/src/utils/get-component-ids.ts +36 -0
  60. package/src/utils/get-container-for-new-element.ts +49 -0
  61. package/src/utils/tracking.ts +47 -0
  62. package/src/components/components-tab.tsx +0 -6
  63. 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 { useQuery } from '@elementor/query';
1
+ import { __useSelector as useSelector } from '@elementor/store';
2
2
 
3
- import { apiClient } from '../api';
4
-
5
- export const COMPONENTS_QUERY_KEY = 'components';
3
+ import { selectComponents, selectLoadIsPending } from '../store/store';
6
4
 
7
5
  export const useComponents = () => {
8
- return useQuery( {
9
- queryKey: [ COMPONENTS_QUERY_KEY ],
10
- queryFn: apiClient.get,
11
- staleTime: Infinity,
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
+ }