@elementor/editor-components 4.0.0-666 → 4.0.0-668

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 (72) hide show
  1. package/dist/index.js +191 -3870
  2. package/dist/index.js.map +1 -1
  3. package/dist/index.mjs +184 -3904
  4. package/dist/index.mjs.map +1 -1
  5. package/package.json +23 -23
  6. package/src/init.ts +0 -13
  7. package/src/extended/components/component-introduction.tsx +0 -77
  8. package/src/extended/components/component-panel-header/component-badge.tsx +0 -73
  9. package/src/extended/components/component-panel-header/component-panel-header.tsx +0 -98
  10. package/src/extended/components/component-properties-panel/component-properties-panel-content.tsx +0 -176
  11. package/src/extended/components/component-properties-panel/component-properties-panel.tsx +0 -43
  12. package/src/extended/components/component-properties-panel/properties-empty-state.tsx +0 -51
  13. package/src/extended/components/component-properties-panel/properties-group.tsx +0 -196
  14. package/src/extended/components/component-properties-panel/property-item.tsx +0 -124
  15. package/src/extended/components/component-properties-panel/sortable.tsx +0 -92
  16. package/src/extended/components/component-properties-panel/use-current-editable-item.ts +0 -73
  17. package/src/extended/components/component-properties-panel/utils/generate-unique-label.ts +0 -21
  18. package/src/extended/components/component-properties-panel/utils/validate-group-label.ts +0 -24
  19. package/src/extended/components/components-tab/component-item.tsx +0 -180
  20. package/src/extended/components/components-tab/components.tsx +0 -58
  21. package/src/extended/components/components-tab/delete-confirmation-dialog.tsx +0 -26
  22. package/src/extended/components/create-component-form/create-component-form.tsx +0 -281
  23. package/src/extended/components/create-component-form/hooks/use-form.ts +0 -72
  24. package/src/extended/components/create-component-form/utils/get-component-event-data.ts +0 -54
  25. package/src/extended/components/edit-component/component-modal.tsx +0 -133
  26. package/src/extended/components/edit-component/edit-component.tsx +0 -166
  27. package/src/extended/components/edit-component/use-canvas-document.ts +0 -9
  28. package/src/extended/components/edit-component/use-element-rect.ts +0 -81
  29. package/src/extended/components/instance-editing-panel/instance-editing-panel.tsx +0 -60
  30. package/src/extended/components/overridable-props/indicator.tsx +0 -83
  31. package/src/extended/components/overridable-props/overridable-prop-control.tsx +0 -127
  32. package/src/extended/components/overridable-props/overridable-prop-form.tsx +0 -135
  33. package/src/extended/components/overridable-props/overridable-prop-indicator.tsx +0 -138
  34. package/src/extended/components/overridable-props/utils/validate-prop-label.ts +0 -38
  35. package/src/extended/consts.ts +0 -3
  36. package/src/extended/hooks/use-navigate-back.ts +0 -24
  37. package/src/extended/init.ts +0 -108
  38. package/src/extended/mcp/index.ts +0 -14
  39. package/src/extended/mcp/save-as-component-tool.ts +0 -436
  40. package/src/extended/shortcuts/create-component-shortcut.ts +0 -121
  41. package/src/extended/store/actions/add-overridable-group.ts +0 -53
  42. package/src/extended/store/actions/archive-component.ts +0 -18
  43. package/src/extended/store/actions/create-unpublished-component.ts +0 -99
  44. package/src/extended/store/actions/delete-overridable-group.ts +0 -32
  45. package/src/extended/store/actions/delete-overridable-prop.ts +0 -64
  46. package/src/extended/store/actions/rename-component.ts +0 -48
  47. package/src/extended/store/actions/rename-overridable-group.ts +0 -33
  48. package/src/extended/store/actions/reorder-group-props.ts +0 -37
  49. package/src/extended/store/actions/reorder-overridable-groups.ts +0 -24
  50. package/src/extended/store/actions/reset-sanitized-components.ts +0 -5
  51. package/src/extended/store/actions/set-overridable-prop.ts +0 -109
  52. package/src/extended/store/actions/update-component-sanitized-attribute.ts +0 -7
  53. package/src/extended/store/actions/update-current-component.ts +0 -12
  54. package/src/extended/store/actions/update-overridable-prop-params.ts +0 -52
  55. package/src/extended/store/utils/groups-transformers.ts +0 -187
  56. package/src/extended/sync/before-save.ts +0 -52
  57. package/src/extended/sync/cleanup-overridable-props-on-delete.ts +0 -78
  58. package/src/extended/sync/create-components-before-save.ts +0 -111
  59. package/src/extended/sync/handle-component-edit-mode-container.ts +0 -114
  60. package/src/extended/sync/prevent-non-atomic-nesting.ts +0 -198
  61. package/src/extended/sync/revert-overridables-on-copy-or-duplicate.ts +0 -66
  62. package/src/extended/sync/sanitize-overridable-props.ts +0 -32
  63. package/src/extended/sync/set-component-overridable-props-settings-before-save.ts +0 -22
  64. package/src/extended/sync/update-archived-component-before-save.ts +0 -31
  65. package/src/extended/sync/update-component-title-before-save.ts +0 -18
  66. package/src/extended/utils/component-form-schema.ts +0 -32
  67. package/src/extended/utils/component-name-validation.ts +0 -25
  68. package/src/extended/utils/create-component-model.ts +0 -28
  69. package/src/extended/utils/get-container-for-new-element.ts +0 -49
  70. package/src/extended/utils/is-editing-component.ts +0 -5
  71. package/src/extended/utils/replace-element-with-component.ts +0 -11
  72. package/src/extended/utils/revert-overridable-settings.ts +0 -207
@@ -1,133 +0,0 @@
1
- import * as React from 'react';
2
- import { type CSSProperties, useEffect } from 'react';
3
- import { createPortal } from 'react-dom';
4
- import { __ } from '@wordpress/i18n';
5
-
6
- import { useCanvasDocument } from './use-canvas-document';
7
- import { useElementRect } from './use-element-rect';
8
-
9
- type ModalProps = {
10
- topLevelElementDom: HTMLElement | null;
11
- onClose: () => void;
12
- };
13
-
14
- export function ComponentModal( { topLevelElementDom, onClose }: ModalProps ) {
15
- const canvasDocument = useCanvasDocument();
16
-
17
- useEffect( () => {
18
- const handleEsc = ( event: KeyboardEvent ) => {
19
- if ( event.key === 'Escape' ) {
20
- onClose();
21
- }
22
- };
23
-
24
- canvasDocument?.body.addEventListener( 'keydown', handleEsc );
25
-
26
- return () => {
27
- canvasDocument?.body.removeEventListener( 'keydown', handleEsc );
28
- };
29
- }, [ canvasDocument, onClose ] );
30
-
31
- if ( ! canvasDocument?.body ) {
32
- return null;
33
- }
34
-
35
- return createPortal(
36
- <>
37
- <BlockEditPage />
38
- <Backdrop canvas={ canvasDocument } element={ topLevelElementDom } onClose={ onClose } />
39
- </>,
40
- canvasDocument.body
41
- );
42
- }
43
-
44
- function Backdrop( {
45
- canvas,
46
- element,
47
- onClose,
48
- }: {
49
- canvas: HTMLDocument;
50
- element: HTMLElement | null;
51
- onClose: () => void;
52
- } ) {
53
- const rect = useElementRect( element );
54
- const clipPath = element ? getRectPath( rect, canvas.defaultView as Window ) : undefined;
55
- const backdropStyle: CSSProperties = {
56
- position: 'fixed',
57
- top: 0,
58
- left: 0,
59
- width: '100vw',
60
- height: '100vh',
61
- backgroundColor: 'rgba(0, 0, 0, 0.5)',
62
- zIndex: 999,
63
- pointerEvents: 'painted',
64
- cursor: 'pointer',
65
- clipPath,
66
- };
67
-
68
- const handleKeyDown = ( event: React.KeyboardEvent ) => {
69
- if ( event.key === 'Enter' || event.key === ' ' ) {
70
- event.preventDefault();
71
- onClose();
72
- }
73
- };
74
-
75
- return (
76
- <div
77
- style={ backdropStyle }
78
- onClick={ onClose }
79
- onKeyDown={ handleKeyDown }
80
- role="button"
81
- tabIndex={ 0 }
82
- aria-label={ __( 'Exit component editing mode', 'elementor' ) }
83
- />
84
- );
85
- }
86
-
87
- function getRectPath( rect: DOMRect, viewport: Window ) {
88
- const { x, y, width, height } = rect;
89
- const { innerWidth: vw, innerHeight: vh } = viewport;
90
-
91
- const path = `path(evenodd, 'M 0 0
92
- L ${ vw } 0
93
- L ${ vw } ${ vh }
94
- L 0 ${ vh }
95
- Z
96
- M ${ x } ${ y }
97
- L ${ x + width } ${ y }
98
- L ${ x + width } ${ y + height }
99
- L ${ x } ${ y + height }
100
- L ${ x } ${ y }
101
- Z'
102
- )`;
103
-
104
- return path.replace( /\s{2,}/g, ' ' );
105
- }
106
-
107
- /**
108
- * when switching to another document id, we get a document handler when hovering
109
- * this functionality originates in Pro, and is intended for editing templates, e.g. header/footer
110
- * in components we don't want that, so the easy way out is to prevent it of being displayed via a CSS rule
111
- */
112
- function BlockEditPage() {
113
- const blockV3DocumentHandlesStyles = `
114
- .elementor-editor-active {
115
- & .elementor-section-wrap.ui-sortable {
116
- display: contents;
117
- }
118
-
119
- & *[data-editable-elementor-document]:not(.elementor-edit-mode):hover {
120
- & .elementor-document-handle:not(.elementor-document-save-back-handle) {
121
- display: none;
122
-
123
- &::before,
124
- & .elementor-document-handle__inner {
125
- display: none;
126
- }
127
- }
128
- }
129
- }
130
- `;
131
-
132
- return <style data-e-style-id="e-block-v3-document-handles-styles">{ blockV3DocumentHandlesStyles }</style>;
133
- }
@@ -1,166 +0,0 @@
1
- import * as React from 'react';
2
- import { useEffect, useState } from 'react';
3
- import { getV1DocumentsManager, type V1Document } from '@elementor/editor-documents';
4
- import { type V1Element } from '@elementor/editor-elements';
5
- import { __privateListenTo as listenTo, commandEndEvent } from '@elementor/editor-v1-adapters';
6
- import { __useSelector as useSelector } from '@elementor/store';
7
- import { throttle } from '@elementor/utils';
8
-
9
- import { apiClient } from '../../../api';
10
- import { type ComponentsPathItem, selectPath, useCurrentComponentId } from '../../../store/store';
11
- import { COMPONENT_DOCUMENT_TYPE } from '../../consts';
12
- import { useNavigateBack } from '../../hooks/use-navigate-back';
13
- import { resetSanitizedComponents } from '../../store/actions/reset-sanitized-components';
14
- import { updateCurrentComponent } from '../../store/actions/update-current-component';
15
- import { ComponentModal } from './component-modal';
16
-
17
- export function EditComponent() {
18
- const currentComponentId = useCurrentComponentId();
19
-
20
- useHandleDocumentSwitches();
21
-
22
- const navigateBack = useNavigateBack();
23
-
24
- const onClose = throttle( navigateBack, 100 );
25
-
26
- const topLevelElementDom = useComponentDOMElement( currentComponentId ?? undefined );
27
-
28
- if ( ! currentComponentId ) {
29
- return null;
30
- }
31
-
32
- return <ComponentModal topLevelElementDom={ topLevelElementDom } onClose={ onClose } />;
33
- }
34
-
35
- function useHandleDocumentSwitches() {
36
- const documentsManager = getV1DocumentsManager();
37
- const currentComponentId = useCurrentComponentId();
38
- const path = useSelector( selectPath );
39
-
40
- useEffect( () => {
41
- return listenTo( commandEndEvent( 'editor/documents/open' ), () => {
42
- const nextDocument = documentsManager.getCurrent();
43
-
44
- if ( nextDocument.id === currentComponentId ) {
45
- return;
46
- }
47
-
48
- if ( currentComponentId ) {
49
- apiClient.unlockComponent( currentComponentId );
50
- }
51
-
52
- resetSanitizedComponents();
53
-
54
- const isComponent = nextDocument.config.type === COMPONENT_DOCUMENT_TYPE;
55
-
56
- if ( ! isComponent ) {
57
- updateCurrentComponent( { path: [], currentComponentId: null } );
58
- return;
59
- }
60
-
61
- updateCurrentComponent( {
62
- path: getUpdatedComponentPath( path, nextDocument ),
63
- currentComponentId: nextDocument.id,
64
- } );
65
- } );
66
- }, [ path, documentsManager, currentComponentId ] );
67
- }
68
-
69
- function getUpdatedComponentPath( path: ComponentsPathItem[], nextDocument: V1Document ): ComponentsPathItem[] {
70
- const componentIndex = path.findIndex( ( { componentId } ) => componentId === nextDocument.id );
71
-
72
- if ( componentIndex >= 0 ) {
73
- // When exiting the editing of a nested component - we in fact go back a step
74
- // so we need to make sure the path is cleaned up of any newer items
75
- // By doing it with the slice and not a simple pop() - we could jump to any component in the path and make sure it becomes the current one
76
- return path.slice( 0, componentIndex + 1 );
77
- }
78
-
79
- const instanceId = nextDocument?.container.view?.el?.dataset.id;
80
- const instanceTitle = getInstanceTitle( instanceId, path );
81
-
82
- return [
83
- ...path,
84
- {
85
- instanceId,
86
- instanceTitle,
87
- componentId: nextDocument.id,
88
- },
89
- ];
90
- }
91
-
92
- function getInstanceTitle( instanceId: string | undefined, path: ComponentsPathItem[] ): string | undefined {
93
- if ( ! instanceId ) {
94
- return undefined;
95
- }
96
-
97
- const documentsManager = getV1DocumentsManager();
98
- const parentDocId = path.at( -1 )?.componentId ?? documentsManager.getInitialId();
99
- const parentDoc = documentsManager.get( parentDocId );
100
-
101
- type EditorSettings = { title?: string };
102
- type ContainerWithChildren = V1Element & {
103
- children?: {
104
- findRecursive?: ( predicate: ( child: V1Element ) => boolean ) => V1Element | undefined;
105
- };
106
- };
107
-
108
- const parentContainer = parentDoc?.container as unknown as ContainerWithChildren | undefined;
109
- const widget = parentContainer?.children?.findRecursive?.(
110
- ( container: V1Element ) => container.id === instanceId
111
- );
112
-
113
- const editorSettings = widget?.model?.get?.( 'editor_settings' ) as EditorSettings | undefined;
114
-
115
- return editorSettings?.title;
116
- }
117
-
118
- function useComponentDOMElement( id: V1Document[ 'id' ] | undefined ) {
119
- const { componentContainerDomElement, topLevelElementDom } = getComponentDOMElements( id );
120
-
121
- const [ currentElementDom, setCurrentElementDom ] = useState< HTMLElement | null >( topLevelElementDom );
122
-
123
- useEffect( () => {
124
- setCurrentElementDom( topLevelElementDom );
125
- }, [ topLevelElementDom ] );
126
-
127
- useEffect( () => {
128
- if ( ! componentContainerDomElement ) {
129
- return;
130
- }
131
-
132
- const mutationObserver = new MutationObserver( () => {
133
- const newElementDom = componentContainerDomElement.children[ 0 ] as HTMLElement | null;
134
- setCurrentElementDom( newElementDom );
135
- } );
136
-
137
- mutationObserver.observe( componentContainerDomElement, { childList: true } );
138
-
139
- return () => {
140
- mutationObserver.disconnect();
141
- };
142
- }, [ componentContainerDomElement ] );
143
-
144
- return currentElementDom;
145
- }
146
-
147
- type ComponentDOMElements = {
148
- componentContainerDomElement: HTMLElement | null;
149
- topLevelElementDom: HTMLElement | null;
150
- };
151
-
152
- function getComponentDOMElements( id: V1Document[ 'id' ] | undefined ): ComponentDOMElements {
153
- if ( ! id ) {
154
- return { componentContainerDomElement: null, topLevelElementDom: null };
155
- }
156
-
157
- const documentsManager = getV1DocumentsManager();
158
-
159
- const currentComponent = documentsManager.get( id );
160
-
161
- const componentContainer = currentComponent?.container as V1Element;
162
- const componentContainerDomElement = ( componentContainer?.view?.el?.children?.[ 0 ] as HTMLElement ) ?? null;
163
- const topLevelElementDom = ( componentContainerDomElement?.children[ 0 ] as HTMLElement ) ?? null;
164
-
165
- return { componentContainerDomElement, topLevelElementDom };
166
- }
@@ -1,9 +0,0 @@
1
- import {
2
- __privateUseListenTo as useListenTo,
3
- commandEndEvent,
4
- getCanvasIframeDocument,
5
- } from '@elementor/editor-v1-adapters';
6
-
7
- export function useCanvasDocument() {
8
- return useListenTo( commandEndEvent( 'editor/documents/attach-preview' ), () => getCanvasIframeDocument() );
9
- }
@@ -1,81 +0,0 @@
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
- }
@@ -1,60 +0,0 @@
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,83 +0,0 @@
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
71
- role="button"
72
- ref={ ref }
73
- { ...props }
74
- className={ isOpen || isOverridable ? 'enlarged' : '' }
75
- aria-label={
76
- isOverridable ? __( 'Overridable property', 'elementor' ) : __( 'Make prop overridable', 'elementor' )
77
- }
78
- >
79
- <IconContainer className="icon">
80
- { isOverridable ? <CheckIcon fontSize={ SIZE } /> : <PlusIcon fontSize={ SIZE } /> }
81
- </IconContainer>
82
- </Content>
83
- ) );
@@ -1,127 +0,0 @@
1
- import * as React from 'react';
2
- import { type ComponentType } from 'react';
3
- import {
4
- ControlReplacementsProvider,
5
- getControlReplacements,
6
- PropKeyProvider,
7
- PropProvider,
8
- useBoundProp,
9
- useControlReplacement,
10
- } from '@elementor/editor-controls';
11
- import { createTopLevelObjectType, useElement } from '@elementor/editor-editing-panel';
12
- import { type PropValue } from '@elementor/editor-props';
13
-
14
- import { type ComponentInstanceOverridePropValue } from '../../../prop-types/component-instance-override-prop-type';
15
- import {
16
- componentOverridablePropTypeUtil,
17
- type ComponentOverridablePropValue,
18
- } from '../../../prop-types/component-overridable-prop-type';
19
- import { OverridablePropProvider } from '../../../provider/overridable-prop-context';
20
- import { updateOverridableProp } from '../../../store/actions/update-overridable-prop';
21
- import { useCurrentComponentId, useOverridableProps } from '../../../store/store';
22
- import { getPropTypeForComponentOverride } from '../../../utils/get-prop-type-for-component-override';
23
- import { OVERRIDABLE_PROP_REPLACEMENT_ID } from '../../consts';
24
-
25
- export function OverridablePropControl< T extends object >( {
26
- OriginalControl,
27
- ...props
28
- }: T & { OriginalControl: ComponentType< T > } ) {
29
- const { elementType } = useElement();
30
-
31
- const { value, bind, setValue, placeholder, ...propContext } = useBoundProp( componentOverridablePropTypeUtil );
32
- const componentId = useCurrentComponentId();
33
- const overridableProps = useOverridableProps( componentId );
34
- const filteredReplacements = getControlReplacements().filter(
35
- ( r ) => ! r.id || r.id !== OVERRIDABLE_PROP_REPLACEMENT_ID
36
- );
37
-
38
- if ( ! componentId ) {
39
- return null;
40
- }
41
-
42
- if ( ! value?.override_key ) {
43
- throw new Error( 'Override key is required' );
44
- }
45
-
46
- const isComponentInstance = elementType.key === 'e-component';
47
- const overridablePropData = overridableProps?.props?.[ value.override_key ];
48
-
49
- const setOverridableValue = ( newValue: Record< typeof bind, PropValue | null > ) => {
50
- const propValue = {
51
- ...value,
52
- origin_value: newValue[ bind ],
53
- } as ComponentOverridablePropValue;
54
-
55
- setValue( propValue );
56
-
57
- if ( ! isComponentInstance ) {
58
- updateOverridableProp( componentId, propValue, overridablePropData?.originPropFields );
59
- }
60
- };
61
-
62
- const defaultPropType = elementType.propsSchema[ bind ];
63
- const overridePropType = overridablePropData ? getPropTypeForComponentOverride( overridablePropData ) : undefined;
64
-
65
- const resolvedPropType = overridePropType ?? defaultPropType;
66
-
67
- if ( ! resolvedPropType ) {
68
- return null;
69
- }
70
-
71
- const propType = createTopLevelObjectType( {
72
- schema: {
73
- [ bind ]: resolvedPropType,
74
- },
75
- } );
76
-
77
- const propValue = (
78
- isComponentInstance
79
- ? ( value.origin_value?.value as ComponentInstanceOverridePropValue ).override_value
80
- : value.origin_value
81
- ) as PropValue;
82
-
83
- const objectPlaceholder: Record< string, PropValue > | undefined = placeholder
84
- ? { [ bind ]: placeholder }
85
- : undefined;
86
-
87
- return (
88
- <OverridablePropProvider value={ value }>
89
- <PropProvider
90
- { ...propContext }
91
- propType={ propType }
92
- setValue={ setOverridableValue }
93
- value={ {
94
- [ bind ]: propValue,
95
- } }
96
- placeholder={ objectPlaceholder }
97
- >
98
- <PropKeyProvider bind={ bind }>
99
- <ControlReplacementsProvider replacements={ filteredReplacements }>
100
- <ControlWithReplacements OriginalControl={ OriginalControl } props={ props as T } />
101
- </ControlReplacementsProvider>
102
- </PropKeyProvider>
103
- </PropProvider>
104
- </OverridablePropProvider>
105
- );
106
- }
107
-
108
- type ControlComponentType = ComponentType< object & { OriginalControl: ComponentType } >;
109
-
110
- function ControlWithReplacements< T extends object >( {
111
- OriginalControl,
112
- props,
113
- }: {
114
- OriginalControl: ComponentType< T >;
115
- props: T;
116
- } ) {
117
- const { ControlToRender, isReplaced } = useControlReplacement( OriginalControl as ControlComponentType );
118
-
119
- if ( isReplaced ) {
120
- const ReplacementControl = ControlToRender as unknown as ComponentType<
121
- T & { OriginalControl: ComponentType< T > }
122
- >;
123
- return <ReplacementControl { ...props } OriginalControl={ OriginalControl } />;
124
- }
125
-
126
- return <OriginalControl { ...props } />;
127
- }