@elementor/editor-components 4.0.0-607 → 4.0.0-609

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 (26) hide show
  1. package/dist/index.js +2341 -2192
  2. package/dist/index.js.map +1 -1
  3. package/dist/index.mjs +2814 -2672
  4. package/dist/index.mjs.map +1 -1
  5. package/package.json +23 -23
  6. package/src/components/components-tab/components-item.tsx +84 -215
  7. package/src/components/components-tab/components-list.tsx +3 -3
  8. package/src/components/components-tab/components.tsx +1 -2
  9. package/src/components/instance-editing-panel/empty-state.tsx +2 -2
  10. package/src/components/instance-editing-panel/instance-editing-panel.tsx +19 -78
  11. package/src/components/instance-editing-panel/instance-panel-body.tsx +36 -0
  12. package/src/components/instance-editing-panel/instance-panel-header.tsx +40 -0
  13. package/src/components/instance-editing-panel/use-instance-panel-data.ts +57 -0
  14. package/src/extended/components/components-tab/component-item.tsx +180 -0
  15. package/src/extended/components/components-tab/components.tsx +58 -0
  16. package/src/extended/components/create-component-form/create-component-form.tsx +1 -1
  17. package/src/extended/components/instance-editing-panel/instance-editing-panel.tsx +60 -0
  18. package/src/extended/init.ts +22 -1
  19. package/src/{store → extended/store}/actions/archive-component.ts +1 -1
  20. package/src/{store → extended/store}/actions/rename-component.ts +2 -2
  21. package/src/{utils → extended/utils}/component-name-validation.ts +1 -1
  22. package/src/extended/utils/replace-element-with-component.ts +1 -1
  23. /package/src/{components → extended/components}/components-tab/delete-confirmation-dialog.tsx +0 -0
  24. /package/src/{utils → extended/utils}/component-form-schema.ts +0 -0
  25. /package/src/{utils → extended/utils}/create-component-model.ts +0 -0
  26. /package/src/{utils → extended/utils}/get-container-for-new-element.ts +0 -0
@@ -0,0 +1,57 @@
1
+ import { useElement } from '@elementor/editor-editing-panel';
2
+ import { useElementSetting, useSelectedElement } from '@elementor/editor-elements';
3
+
4
+ import { useSanitizeOverridableProps } from '../../hooks/use-sanitize-overridable-props';
5
+ import { type ComponentInstanceOverridesPropValue } from '../../prop-types/component-instance-overrides-prop-type';
6
+ import {
7
+ componentInstancePropTypeUtil,
8
+ type ComponentInstancePropValue,
9
+ } from '../../prop-types/component-instance-prop-type';
10
+ import { useComponent } from '../../store/store';
11
+ import { type Component, type OverridablePropsGroup } from '../../types';
12
+
13
+ type InstancePanelData = {
14
+ componentId: number;
15
+ component: Component;
16
+ overrides: ComponentInstanceOverridesPropValue;
17
+ overridableProps: NonNullable< ReturnType< typeof useSanitizeOverridableProps > >;
18
+ groups: OverridablePropsGroup[];
19
+ isEmpty: boolean;
20
+ componentInstanceId: string | undefined;
21
+ };
22
+
23
+ export function useInstancePanelData(): InstancePanelData | null {
24
+ const settings = useComponentInstanceSettings();
25
+
26
+ const componentId = settings?.component_id?.value;
27
+
28
+ const overrides = settings?.overrides?.value;
29
+
30
+ const component = useComponent( componentId ?? null );
31
+
32
+ const componentInstanceId = useSelectedElement()?.element?.id;
33
+
34
+ const overridableProps = useSanitizeOverridableProps( componentId ?? null, componentInstanceId );
35
+
36
+ if ( ! componentId || ! overridableProps || ! component ) {
37
+ return null;
38
+ }
39
+
40
+ const isNonEmptyGroup = ( group: OverridablePropsGroup | null ) => group !== null && group.props.length > 0;
41
+
42
+ const groups = overridableProps.groups.order
43
+ .map( ( groupId ) => overridableProps.groups.items[ groupId ] ?? null )
44
+ .filter( isNonEmptyGroup );
45
+
46
+ const isEmpty = groups.length === 0 || Object.keys( overridableProps.props ).length === 0;
47
+
48
+ return { componentId, component, overrides, overridableProps, groups, isEmpty, componentInstanceId };
49
+ }
50
+
51
+ function useComponentInstanceSettings() {
52
+ const { element } = useElement();
53
+
54
+ const settings = useElementSetting< ComponentInstancePropValue >( element.id, 'component_instance' );
55
+
56
+ return componentInstancePropTypeUtil.extract( settings );
57
+ }
@@ -0,0 +1,180 @@
1
+ import * as React from 'react';
2
+ import { useRef, useState } from 'react';
3
+ import { endDragElementFromPanel, startDragElementFromPanel } from '@elementor/editor-canvas';
4
+ import { dropElement, type DropElementParams, type V1ElementData } from '@elementor/editor-elements';
5
+ import { MenuListItem, useEditable, WarningInfotip } from '@elementor/editor-ui';
6
+ import { DotsVerticalIcon } from '@elementor/icons';
7
+ import { bindMenu, bindTrigger, IconButton, Menu, Stack, usePopupState } from '@elementor/ui';
8
+ import { __ } from '@wordpress/i18n';
9
+
10
+ import {
11
+ ComponentItem as CoreComponentItem,
12
+ type ComponentItemProps,
13
+ ComponentName,
14
+ } from '../../../components/components-tab/components-item';
15
+ import { useComponentsPermissions } from '../../../hooks/use-components-permissions';
16
+ import { loadComponentsAssets } from '../../../store/actions/load-components-assets';
17
+ import { archiveComponent } from '../../store/actions/archive-component';
18
+ import { renameComponent } from '../../store/actions/rename-component';
19
+ import { validateComponentName } from '../../utils/component-name-validation';
20
+ import { createComponentModel } from '../../utils/create-component-model';
21
+ import { getContainerForNewElement } from '../../utils/get-container-for-new-element';
22
+ import { DeleteConfirmationDialog } from './delete-confirmation-dialog';
23
+
24
+ export function ComponentItem( { component }: ComponentItemProps ) {
25
+ const itemRef = useRef< HTMLElement >( null );
26
+ const [ isDeleteDialogOpen, setIsDeleteDialogOpen ] = useState( false );
27
+ const { canRename, canDelete } = useComponentsPermissions();
28
+
29
+ const shouldShowActions = canRename || canDelete;
30
+
31
+ const {
32
+ ref: editableRef,
33
+ isEditing,
34
+ openEditMode,
35
+ error,
36
+ getProps: getEditableProps,
37
+ } = useEditable( {
38
+ value: component.name,
39
+ onSubmit: ( newName: string ) => renameComponent( component.uid, newName ),
40
+ validation: validateComponentTitle,
41
+ } );
42
+
43
+ const componentModel = createComponentModel( component );
44
+
45
+ const popupState = usePopupState( {
46
+ variant: 'popover',
47
+ disableAutoFocus: true,
48
+ } );
49
+
50
+ const handleClick = () => {
51
+ addComponentToPage( componentModel );
52
+ };
53
+
54
+ const handleDragEnd = () => {
55
+ loadComponentsAssets( [ componentModel as V1ElementData ] );
56
+
57
+ endDragElementFromPanel();
58
+ };
59
+
60
+ const handleDeleteClick = () => {
61
+ setIsDeleteDialogOpen( true );
62
+ popupState.close();
63
+ };
64
+
65
+ const handleDeleteConfirm = () => {
66
+ if ( ! component.id ) {
67
+ throw new Error( 'Component ID is required' );
68
+ }
69
+
70
+ setIsDeleteDialogOpen( false );
71
+ archiveComponent( component.id, component.name );
72
+ };
73
+
74
+ const handleDeleteDialogClose = () => {
75
+ setIsDeleteDialogOpen( false );
76
+ };
77
+
78
+ return (
79
+ <Stack>
80
+ <WarningInfotip
81
+ open={ Boolean( error ) }
82
+ text={ error ?? '' }
83
+ placement="bottom"
84
+ width={ itemRef.current?.getBoundingClientRect().width }
85
+ offset={ [ 0, -15 ] }
86
+ >
87
+ <CoreComponentItem
88
+ ref={ itemRef }
89
+ component={ component }
90
+ disabled={ false }
91
+ draggable
92
+ onDragStart={ ( event: React.DragEvent ) => startDragElementFromPanel( componentModel, event ) }
93
+ onDragEnd={ handleDragEnd }
94
+ onClick={ handleClick }
95
+ isEditing={ isEditing }
96
+ error={ error }
97
+ nameSlot={
98
+ <ComponentName
99
+ name={ component.name }
100
+ editable={ { ref: editableRef, isEditing, getProps: getEditableProps } }
101
+ />
102
+ }
103
+ endSlot={
104
+ shouldShowActions ? (
105
+ <IconButton size="tiny" { ...bindTrigger( popupState ) } aria-label="More actions">
106
+ <DotsVerticalIcon fontSize="tiny" />
107
+ </IconButton>
108
+ ) : undefined
109
+ }
110
+ />
111
+ </WarningInfotip>
112
+ { shouldShowActions && (
113
+ <Menu
114
+ { ...bindMenu( popupState ) }
115
+ anchorOrigin={ {
116
+ vertical: 'bottom',
117
+ horizontal: 'right',
118
+ } }
119
+ transformOrigin={ {
120
+ vertical: 'top',
121
+ horizontal: 'right',
122
+ } }
123
+ >
124
+ { canRename && (
125
+ <MenuListItem
126
+ sx={ { minWidth: '160px' } }
127
+ primaryTypographyProps={ { variant: 'caption', color: 'text.primary' } }
128
+ onClick={ () => {
129
+ popupState.close();
130
+ openEditMode();
131
+ } }
132
+ >
133
+ { __( 'Rename', 'elementor' ) }
134
+ </MenuListItem>
135
+ ) }
136
+ { canDelete && (
137
+ <MenuListItem
138
+ sx={ { minWidth: '160px' } }
139
+ primaryTypographyProps={ { variant: 'caption', color: 'error.light' } }
140
+ onClick={ handleDeleteClick }
141
+ >
142
+ { __( 'Delete', 'elementor' ) }
143
+ </MenuListItem>
144
+ ) }
145
+ </Menu>
146
+ ) }
147
+ <DeleteConfirmationDialog
148
+ open={ isDeleteDialogOpen }
149
+ onClose={ handleDeleteDialogClose }
150
+ onConfirm={ handleDeleteConfirm }
151
+ />
152
+ </Stack>
153
+ );
154
+ }
155
+
156
+ const addComponentToPage = ( model: DropElementParams[ 'model' ] ) => {
157
+ const { container, options } = getContainerForNewElement();
158
+
159
+ if ( ! container ) {
160
+ throw new Error( `Can't find container to drop new component instance at` );
161
+ }
162
+
163
+ loadComponentsAssets( [ model as V1ElementData ] );
164
+
165
+ dropElement( {
166
+ containerId: container.id,
167
+ model,
168
+ options: { ...options, useHistory: false, scrollIntoView: true },
169
+ } );
170
+ };
171
+
172
+ const validateComponentTitle = ( newTitle: string ) => {
173
+ const result = validateComponentName( newTitle );
174
+
175
+ if ( ! result.errorMessage ) {
176
+ return null;
177
+ }
178
+
179
+ return result.errorMessage;
180
+ };
@@ -0,0 +1,58 @@
1
+ import * as React from 'react';
2
+ import { ThemeProvider } from '@elementor/editor-ui';
3
+ import { List } from '@elementor/ui';
4
+
5
+ import { ComponentSearch } from '../../../components/components-tab/component-search';
6
+ import {
7
+ EmptySearchResult,
8
+ EmptyState,
9
+ useFilteredComponents,
10
+ } from '../../../components/components-tab/components-list';
11
+ import { LoadingComponents } from '../../../components/components-tab/loading-components';
12
+ import { SearchProvider } from '../../../components/components-tab/search-provider';
13
+ import { useComponents } from '../../../hooks/use-components';
14
+ import { ComponentItem } from './component-item';
15
+
16
+ const ExtendedComponentsList = () => {
17
+ const { components, isLoading, searchValue } = useFilteredComponents();
18
+
19
+ if ( isLoading ) {
20
+ return <LoadingComponents />;
21
+ }
22
+
23
+ const isEmpty = ! components?.length;
24
+
25
+ if ( isEmpty ) {
26
+ return searchValue.length ? <EmptySearchResult /> : <EmptyState />;
27
+ }
28
+
29
+ return (
30
+ <List sx={ { display: 'flex', flexDirection: 'column', gap: 1, px: 2 } }>
31
+ { components.map( ( component ) => (
32
+ <ComponentItem key={ component.uid } component={ component } />
33
+ ) ) }
34
+ </List>
35
+ );
36
+ };
37
+
38
+ const ExtendedComponentsContent = () => {
39
+ const { components, isLoading } = useComponents();
40
+ const hasComponents = ! isLoading && components.length > 0;
41
+
42
+ return (
43
+ <>
44
+ { hasComponents && <ComponentSearch /> }
45
+ <ExtendedComponentsList />
46
+ </>
47
+ );
48
+ };
49
+
50
+ export const ExtendedComponents = () => {
51
+ return (
52
+ <ThemeProvider>
53
+ <SearchProvider localStorageKey="elementor-components-search">
54
+ <ExtendedComponentsContent />
55
+ </SearchProvider>
56
+ </ThemeProvider>
57
+ );
58
+ };
@@ -11,11 +11,11 @@ import { __ } from '@wordpress/i18n';
11
11
  import { useComponents } from '../../../hooks/use-components';
12
12
  import { selectComponentByUid } from '../../../store/store';
13
13
  import { type ComponentFormValues, type PublishedComponent } from '../../../types';
14
- import { createBaseComponentSchema, createSubmitComponentSchema } from '../../../utils/component-form-schema';
15
14
  import { switchToComponent } from '../../../utils/switch-to-component';
16
15
  import { trackComponentEvent } from '../../../utils/tracking';
17
16
  import { createUnpublishedComponent } from '../../store/actions/create-unpublished-component';
18
17
  import { findNonAtomicElementsInElement } from '../../sync/prevent-non-atomic-nesting';
18
+ import { createBaseComponentSchema, createSubmitComponentSchema } from '../../utils/component-form-schema';
19
19
  import { useForm } from './hooks/use-form';
20
20
  import {
21
21
  type ComponentEventData,
@@ -0,0 +1,60 @@
1
+ import * as React from 'react';
2
+ import { PencilIcon } from '@elementor/icons';
3
+ import { Box } from '@elementor/ui';
4
+ import { __ } from '@wordpress/i18n';
5
+
6
+ import { EmptyState } from '../../../components/instance-editing-panel/empty-state';
7
+ import { InstancePanelBody } from '../../../components/instance-editing-panel/instance-panel-body';
8
+ import {
9
+ EditComponentAction,
10
+ InstancePanelHeader,
11
+ } from '../../../components/instance-editing-panel/instance-panel-header';
12
+ import { useInstancePanelData } from '../../../components/instance-editing-panel/use-instance-panel-data';
13
+ import { useComponentsPermissions } from '../../../hooks/use-components-permissions';
14
+ import { ComponentInstanceProvider } from '../../../provider/component-instance-context';
15
+ import { switchToComponent } from '../../../utils/switch-to-component';
16
+
17
+ export function ExtendedInstanceEditingPanel() {
18
+ const { canEdit } = useComponentsPermissions();
19
+ const data = useInstancePanelData();
20
+
21
+ if ( ! data ) {
22
+ return null;
23
+ }
24
+
25
+ const { componentId, component, overrides, overridableProps, groups, isEmpty, componentInstanceId } = data;
26
+
27
+ /* translators: %s: component name. */
28
+ const panelTitle = __( 'Edit %s', 'elementor' ).replace( '%s', component.name );
29
+
30
+ const handleEditComponent = () => switchToComponent( componentId, componentInstanceId );
31
+
32
+ return (
33
+ <Box data-testid="instance-editing-panel">
34
+ <ComponentInstanceProvider
35
+ componentId={ componentId }
36
+ overrides={ overrides }
37
+ overridableProps={ overridableProps }
38
+ >
39
+ <InstancePanelHeader
40
+ componentName={ component.name }
41
+ actions={
42
+ canEdit ? (
43
+ <EditComponentAction
44
+ label={ panelTitle }
45
+ onClick={ handleEditComponent }
46
+ icon={ PencilIcon }
47
+ />
48
+ ) : undefined
49
+ }
50
+ />
51
+ <InstancePanelBody
52
+ groups={ groups }
53
+ isEmpty={ isEmpty }
54
+ emptyState={ <EmptyState onEditComponent={ canEdit ? handleEditComponent : undefined } /> }
55
+ componentInstanceId={ componentInstanceId }
56
+ />
57
+ </ComponentInstanceProvider>
58
+ </Box>
59
+ );
60
+ }
@@ -1,17 +1,26 @@
1
1
  import { injectIntoLogic, injectIntoTop } from '@elementor/editor';
2
2
  import { registerControlReplacement } from '@elementor/editor-controls';
3
3
  import { getV1CurrentDocument } from '@elementor/editor-documents';
4
- import { FIELD_TYPE, injectIntoPanelHeaderTop, registerFieldIndicator } from '@elementor/editor-editing-panel';
4
+ import {
5
+ FIELD_TYPE,
6
+ injectIntoPanelHeaderTop,
7
+ registerEditingPanelReplacement,
8
+ registerFieldIndicator,
9
+ } from '@elementor/editor-editing-panel';
10
+ import { registerTab } from '@elementor/editor-elements-panel';
5
11
  import { __registerPanel as registerPanel } from '@elementor/editor-panels';
6
12
  import { registerDataHook } from '@elementor/editor-v1-adapters';
13
+ import { __ } from '@wordpress/i18n';
7
14
 
8
15
  import { componentOverridablePropTypeUtil } from '../prop-types/component-overridable-prop-type';
9
16
  import { type ExtendedWindow } from '../types';
10
17
  import { onElementDrop } from '../utils/tracking';
11
18
  import { ComponentPanelHeader } from './components/component-panel-header/component-panel-header';
12
19
  import { panel as componentPropertiesPanel } from './components/component-properties-panel/component-properties-panel';
20
+ import { ExtendedComponents } from './components/components-tab/components';
13
21
  import { CreateComponentForm } from './components/create-component-form/create-component-form';
14
22
  import { EditComponent } from './components/edit-component/edit-component';
23
+ import { ExtendedInstanceEditingPanel } from './components/instance-editing-panel/instance-editing-panel';
15
24
  import { OverridablePropControl } from './components/overridable-props/overridable-prop-control';
16
25
  import { OverridablePropIndicator } from './components/overridable-props/overridable-prop-indicator';
17
26
  import { COMPONENT_DOCUMENT_TYPE, OVERRIDABLE_PROP_REPLACEMENT_ID } from './consts';
@@ -24,6 +33,18 @@ import { initRevertOverridablesOnCopyOrDuplicate } from './sync/revert-overridab
24
33
  import { SanitizeOverridableProps } from './sync/sanitize-overridable-props';
25
34
 
26
35
  export function initExtended() {
36
+ registerEditingPanelReplacement( {
37
+ id: 'component-instance-edit-panel',
38
+ condition: ( _, elementType ) => elementType.key === 'e-component',
39
+ component: ExtendedInstanceEditingPanel,
40
+ } );
41
+
42
+ registerTab( {
43
+ id: 'components',
44
+ label: __( 'Components', 'elementor' ),
45
+ component: ExtendedComponents,
46
+ } );
47
+
27
48
  registerPanel( componentPropertiesPanel );
28
49
 
29
50
  registerDataHook( 'dependency', 'editor/documents/close', ( args ) => {
@@ -3,7 +3,7 @@ import { type NotificationData, notify } from '@elementor/editor-notifications';
3
3
  import { __dispatch as dispatch } from '@elementor/store';
4
4
  import { __ } from '@wordpress/i18n';
5
5
 
6
- import { slice } from '../store';
6
+ import { slice } from '../../../store/store';
7
7
 
8
8
  const successNotification = ( componentId: number, componentName: string ): NotificationData => ( {
9
9
  type: 'success',
@@ -2,8 +2,8 @@ import { getV1DocumentsManager, setDocumentModifiedStatus } from '@elementor/edi
2
2
  import { getAllDescendants, type V1Element } from '@elementor/editor-elements';
3
3
  import { __dispatch as dispatch } from '@elementor/store';
4
4
 
5
- import { COMPONENT_WIDGET_TYPE } from '../../create-component-type';
6
- import { slice } from '../store';
5
+ import { COMPONENT_WIDGET_TYPE } from '../../../create-component-type';
6
+ import { slice } from '../../../store/store';
7
7
 
8
8
  const TITLE_EXTERNAL_CHANGE_COMMAND = 'title_external_change';
9
9
 
@@ -1,6 +1,6 @@
1
1
  import { __getState as getState } from '@elementor/store';
2
2
 
3
- import { selectComponents } from '../store/store';
3
+ import { selectComponents } from '../../store/store';
4
4
  import { createSubmitComponentSchema } from './component-form-schema';
5
5
 
6
6
  type ValidationResult = { isValid: true; errorMessage: null } | { isValid: false; errorMessage: string };
@@ -1,6 +1,6 @@
1
1
  import { replaceElement, type V1ElementData } from '@elementor/editor-elements';
2
2
 
3
- import { type ComponentInstanceParams, createComponentModel } from '../../utils/create-component-model';
3
+ import { type ComponentInstanceParams, createComponentModel } from './create-component-model';
4
4
 
5
5
  export const replaceElementWithComponent = async ( element: V1ElementData, component: ComponentInstanceParams ) => {
6
6
  return await replaceElement( {