@elementor/editor-canvas 3.33.0-99 → 3.34.2
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.d.mts +133 -10
- package/dist/index.d.ts +133 -10
- package/dist/index.js +1413 -212
- package/dist/index.mjs +1399 -180
- package/package.json +18 -14
- package/src/__tests__/settings-props-resolver.test.ts +0 -40
- package/src/__tests__/styles-prop-resolver.test.ts +13 -0
- package/src/components/__tests__/__snapshots__/style-renderer.test.tsx.snap +2 -6
- package/src/components/__tests__/elements-overlays.test.tsx +96 -12
- package/src/components/__tests__/inline-editor-overlay.test.tsx +245 -0
- package/src/components/__tests__/style-renderer.test.tsx +2 -2
- package/src/components/elements-overlays.tsx +33 -10
- package/src/components/inline-editor-overlay.tsx +79 -0
- package/src/components/interactions-renderer.tsx +33 -0
- package/src/components/{element-overlay.tsx → outline-overlay.tsx} +8 -7
- package/src/components/style-renderer.tsx +2 -4
- package/src/hooks/__tests__/use-has-overlapping.test.ts +187 -0
- package/src/hooks/use-floating-on-element.ts +11 -8
- package/src/hooks/use-has-overlapping.ts +21 -0
- package/src/hooks/use-interactions-items.ts +108 -0
- package/src/hooks/use-style-items.ts +34 -8
- package/src/index.ts +9 -0
- package/src/init-settings-transformers.ts +4 -0
- package/src/init.tsx +18 -0
- package/src/legacy/create-templated-element-type.ts +67 -42
- package/src/legacy/init-legacy-views.ts +27 -5
- package/src/legacy/types.ts +44 -4
- package/src/mcp/canvas-mcp.ts +17 -0
- package/src/mcp/mcp-description.ts +40 -0
- package/src/mcp/resources/widgets-schema-resource.ts +173 -0
- package/src/mcp/tools/build-composition/prompt.ts +128 -0
- package/src/mcp/tools/build-composition/schema.ts +31 -0
- package/src/mcp/tools/build-composition/tool.ts +163 -0
- package/src/mcp/tools/configure-element/prompt.ts +93 -0
- package/src/mcp/tools/configure-element/schema.ts +25 -0
- package/src/mcp/tools/configure-element/tool.ts +67 -0
- package/src/mcp/tools/get-element-config/tool.ts +69 -0
- package/src/mcp/utils/do-update-element-property.ts +129 -0
- package/src/mcp/utils/generate-available-tags.ts +23 -0
- package/src/renderers/__tests__/__snapshots__/create-styles-renderer.test.ts.snap +2 -0
- package/src/renderers/__tests__/create-styles-renderer.test.ts +25 -0
- package/src/renderers/create-props-resolver.ts +8 -1
- package/src/renderers/create-styles-renderer.ts +20 -9
- package/src/renderers/errors.ts +6 -0
- package/src/sync/drag-element-from-panel.ts +49 -0
- package/src/sync/types.ts +32 -1
- package/src/transformers/settings/__tests__/attributes-transformer.test.ts +15 -0
- package/src/transformers/settings/__tests__/classes-transformer.test.ts +83 -0
- package/src/transformers/settings/attributes-transformer.ts +1 -23
- package/src/transformers/settings/classes-transformer.ts +21 -21
- package/src/transformers/settings/date-time-transformer.ts +12 -0
- package/src/transformers/settings/query-transformer.ts +10 -0
- package/src/transformers/styles/__tests__/transform-origin-transformer.test.ts +24 -0
- package/src/transformers/styles/__tests__/transition-transformer.test.ts +52 -0
- package/src/transformers/styles/background-transformer.ts +3 -1
- package/src/transformers/styles/transform-origin-transformer.ts +12 -2
- package/src/transformers/styles/transition-transformer.ts +34 -4
- package/src/types/element-overlay.ts +18 -0
- package/src/utils/inline-editing-utils.ts +43 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { InlineEditor } from '@elementor/editor-controls';
|
|
3
|
+
import { getContainer, updateElementSettings, useElementSetting } from '@elementor/editor-elements';
|
|
4
|
+
import { htmlPropTypeUtil } from '@elementor/editor-props';
|
|
5
|
+
import { Box } from '@elementor/ui';
|
|
6
|
+
import { debounce } from '@elementor/utils';
|
|
7
|
+
import { FloatingPortal } from '@floating-ui/react';
|
|
8
|
+
|
|
9
|
+
import { useFloatingOnElement } from '../hooks/use-floating-on-element';
|
|
10
|
+
import type { ElementOverlayProps } from '../types/element-overlay';
|
|
11
|
+
import { getInlineEditablePropertyName } from '../utils/inline-editing-utils';
|
|
12
|
+
import { CANVAS_WRAPPER_ID } from './outline-overlay';
|
|
13
|
+
|
|
14
|
+
const OVERLAY_Z_INDEX = 1000;
|
|
15
|
+
const DEBOUNCE_DELAY = 100;
|
|
16
|
+
|
|
17
|
+
export const InlineEditorOverlay = ( { element, isSelected, id }: ElementOverlayProps ): React.ReactElement | null => {
|
|
18
|
+
const { floating, isVisible } = useFloatingOnElement( { element, isSelected } );
|
|
19
|
+
|
|
20
|
+
const propertyName = React.useMemo( () => {
|
|
21
|
+
const container = getContainer( id );
|
|
22
|
+
return getInlineEditablePropertyName( container );
|
|
23
|
+
}, [ id ] );
|
|
24
|
+
|
|
25
|
+
const contentProp = useElementSetting( id, propertyName );
|
|
26
|
+
const value = React.useMemo( () => htmlPropTypeUtil.extract( contentProp ) || '', [ contentProp ] );
|
|
27
|
+
|
|
28
|
+
const debouncedUpdateRef = React.useRef< ReturnType< typeof debounce > | null >( null );
|
|
29
|
+
const lastValueRef = React.useRef< string >( '' );
|
|
30
|
+
|
|
31
|
+
React.useEffect( () => {
|
|
32
|
+
debouncedUpdateRef.current = debounce( ( newValue: string ) => {
|
|
33
|
+
const textContent = newValue.replace( /<[^>]*>/g, '' ).trim();
|
|
34
|
+
const valueToSave = textContent === '' ? ' ' : newValue;
|
|
35
|
+
|
|
36
|
+
updateElementSettings( {
|
|
37
|
+
id,
|
|
38
|
+
props: {
|
|
39
|
+
[ propertyName ]: htmlPropTypeUtil.create( valueToSave ),
|
|
40
|
+
},
|
|
41
|
+
withHistory: true,
|
|
42
|
+
} );
|
|
43
|
+
}, DEBOUNCE_DELAY );
|
|
44
|
+
|
|
45
|
+
return () => {
|
|
46
|
+
debouncedUpdateRef.current?.cancel?.();
|
|
47
|
+
};
|
|
48
|
+
}, [ id, propertyName ] );
|
|
49
|
+
|
|
50
|
+
const handleValueChange = React.useCallback( ( newValue: string ) => {
|
|
51
|
+
lastValueRef.current = newValue;
|
|
52
|
+
debouncedUpdateRef.current?.( newValue );
|
|
53
|
+
}, [] );
|
|
54
|
+
|
|
55
|
+
React.useEffect( () => {
|
|
56
|
+
if ( ! isVisible && debouncedUpdateRef.current?.pending?.() ) {
|
|
57
|
+
debouncedUpdateRef.current.flush( lastValueRef.current );
|
|
58
|
+
}
|
|
59
|
+
}, [ isVisible ] );
|
|
60
|
+
|
|
61
|
+
if ( ! isVisible ) {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return (
|
|
66
|
+
<FloatingPortal id={ CANVAS_WRAPPER_ID }>
|
|
67
|
+
<Box
|
|
68
|
+
ref={ floating.setRef }
|
|
69
|
+
style={ {
|
|
70
|
+
...floating.styles,
|
|
71
|
+
zIndex: OVERLAY_Z_INDEX,
|
|
72
|
+
pointerEvents: 'auto',
|
|
73
|
+
} }
|
|
74
|
+
>
|
|
75
|
+
<InlineEditor value={ value } setValue={ handleValueChange } showToolbar={ isSelected } />
|
|
76
|
+
</Box>
|
|
77
|
+
</FloatingPortal>
|
|
78
|
+
);
|
|
79
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { __privateUseListenTo as useListenTo, commandEndEvent } from '@elementor/editor-v1-adapters';
|
|
3
|
+
import { Portal } from '@elementor/ui';
|
|
4
|
+
|
|
5
|
+
import { useInteractionsItems } from '../hooks/use-interactions-items';
|
|
6
|
+
import { getCanvasIframeDocument } from '../sync/get-canvas-iframe-document';
|
|
7
|
+
|
|
8
|
+
export function InteractionsRenderer() {
|
|
9
|
+
const container = usePortalContainer();
|
|
10
|
+
const interactionItems = useInteractionsItems();
|
|
11
|
+
|
|
12
|
+
if ( ! container ) {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const interactionsData = JSON.stringify( Array.isArray( interactionItems ) ? interactionItems : [] );
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<Portal container={ container }>
|
|
20
|
+
<script
|
|
21
|
+
type="application/json"
|
|
22
|
+
data-e-interactions="true"
|
|
23
|
+
dangerouslySetInnerHTML={ {
|
|
24
|
+
__html: interactionsData,
|
|
25
|
+
} }
|
|
26
|
+
/>
|
|
27
|
+
</Portal>
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function usePortalContainer() {
|
|
32
|
+
return useListenTo( commandEndEvent( 'editor/documents/attach-preview' ), () => getCanvasIframeDocument()?.head );
|
|
33
|
+
}
|
|
@@ -4,13 +4,12 @@ import { FloatingPortal, useHover, useInteractions } from '@floating-ui/react';
|
|
|
4
4
|
|
|
5
5
|
import { useBindReactPropsToElement } from '../hooks/use-bind-react-props-to-element';
|
|
6
6
|
import { useFloatingOnElement } from '../hooks/use-floating-on-element';
|
|
7
|
+
import { useHasOverlapping } from '../hooks/use-has-overlapping';
|
|
8
|
+
import type { ElementOverlayProps } from '../types/element-overlay';
|
|
7
9
|
|
|
8
10
|
export const CANVAS_WRAPPER_ID = 'elementor-preview-responsive-wrapper';
|
|
9
11
|
|
|
10
|
-
type Props = {
|
|
11
|
-
element: HTMLElement;
|
|
12
|
-
isSelected: boolean;
|
|
13
|
-
id: string;
|
|
12
|
+
type Props = ElementOverlayProps & {
|
|
14
13
|
isSmallerOffset?: boolean;
|
|
15
14
|
};
|
|
16
15
|
|
|
@@ -22,15 +21,17 @@ const OverlayBox = styled( Box, {
|
|
|
22
21
|
pointerEvents: 'none',
|
|
23
22
|
} ) );
|
|
24
23
|
|
|
25
|
-
export
|
|
24
|
+
export const OutlineOverlay = ( { element, isSelected, id }: Props ): React.ReactElement | false => {
|
|
26
25
|
const { context, floating, isVisible } = useFloatingOnElement( { element, isSelected } );
|
|
27
26
|
const { getFloatingProps, getReferenceProps } = useInteractions( [ useHover( context ) ] );
|
|
27
|
+
const hasOverlapping = useHasOverlapping();
|
|
28
28
|
|
|
29
29
|
useBindReactPropsToElement( element, getReferenceProps );
|
|
30
30
|
const isSmallerOffset = element.offsetHeight <= 1;
|
|
31
31
|
|
|
32
32
|
return (
|
|
33
|
-
isVisible &&
|
|
33
|
+
isVisible &&
|
|
34
|
+
! hasOverlapping && (
|
|
34
35
|
<FloatingPortal id={ CANVAS_WRAPPER_ID }>
|
|
35
36
|
<OverlayBox
|
|
36
37
|
ref={ floating.setRef }
|
|
@@ -44,4 +45,4 @@ export function ElementOverlay( { element, isSelected, id }: Props ) {
|
|
|
44
45
|
</FloatingPortal>
|
|
45
46
|
)
|
|
46
47
|
);
|
|
47
|
-
}
|
|
48
|
+
};
|
|
@@ -18,10 +18,8 @@ export function StyleRenderer() {
|
|
|
18
18
|
|
|
19
19
|
return (
|
|
20
20
|
<Portal container={ container }>
|
|
21
|
-
{ styleItems.map( ( item ) => (
|
|
22
|
-
<style
|
|
23
|
-
{ item.value }
|
|
24
|
-
</style>
|
|
21
|
+
{ styleItems.map( ( item, i ) => (
|
|
22
|
+
<style key={ `${ item.id }-${ i }-${ item.breakpoint }` }>{ item.value }</style>
|
|
25
23
|
) ) }
|
|
26
24
|
{ linksAttrs.map( ( attrs ) => (
|
|
27
25
|
<link { ...attrs } key={ attrs.id } />
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { createDOMElement } from 'test-utils';
|
|
2
|
+
import { renderHook } from '@testing-library/react';
|
|
3
|
+
|
|
4
|
+
import { type CanvasExtendedWindow } from '../../sync/types';
|
|
5
|
+
import { useHasOverlapping } from '../use-has-overlapping';
|
|
6
|
+
|
|
7
|
+
const OFF_CANVAS_CLASS = 'e-off-canvas';
|
|
8
|
+
|
|
9
|
+
describe( 'useHasOverlapping', () => {
|
|
10
|
+
let mockPreviewFrame: HTMLIFrameElement;
|
|
11
|
+
let mockDocument: Document;
|
|
12
|
+
|
|
13
|
+
const setupPreviewFrame = ( hasOffCanvas: boolean, isVisible: boolean = true ) => {
|
|
14
|
+
// Arrange - Create mock iframe and document
|
|
15
|
+
mockPreviewFrame = createDOMElement( { tag: 'iframe' } ) as HTMLIFrameElement;
|
|
16
|
+
mockDocument = document.implementation.createHTMLDocument( 'Preview' );
|
|
17
|
+
|
|
18
|
+
if ( hasOffCanvas ) {
|
|
19
|
+
const offCanvasElement = createDOMElement( {
|
|
20
|
+
tag: 'div',
|
|
21
|
+
attrs: { class: OFF_CANVAS_CLASS },
|
|
22
|
+
} );
|
|
23
|
+
|
|
24
|
+
// Mock checkVisibility method
|
|
25
|
+
offCanvasElement.checkVisibility = jest.fn().mockReturnValue( isVisible );
|
|
26
|
+
|
|
27
|
+
mockDocument.body.appendChild( offCanvasElement );
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Setup the content window with the mock document
|
|
31
|
+
Object.defineProperty( mockPreviewFrame, 'contentWindow', {
|
|
32
|
+
value: {
|
|
33
|
+
document: mockDocument,
|
|
34
|
+
},
|
|
35
|
+
writable: true,
|
|
36
|
+
} );
|
|
37
|
+
|
|
38
|
+
// Setup window.elementor.$preview
|
|
39
|
+
( window as unknown as CanvasExtendedWindow ).elementor = {
|
|
40
|
+
$preview: [ mockPreviewFrame ],
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const cleanupPreviewFrame = () => {
|
|
45
|
+
delete ( window as unknown as CanvasExtendedWindow ).elementor;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
afterEach( () => {
|
|
49
|
+
cleanupPreviewFrame();
|
|
50
|
+
} );
|
|
51
|
+
|
|
52
|
+
it( 'should return false when preview frame is not available', () => {
|
|
53
|
+
// Arrange - No preview frame setup
|
|
54
|
+
|
|
55
|
+
// Act
|
|
56
|
+
const { result } = renderHook( () => useHasOverlapping() );
|
|
57
|
+
|
|
58
|
+
// Assert
|
|
59
|
+
expect( result.current ).toBe( false );
|
|
60
|
+
} );
|
|
61
|
+
|
|
62
|
+
it( 'should return false when off-canvas element does not exist', () => {
|
|
63
|
+
// Arrange
|
|
64
|
+
setupPreviewFrame( false );
|
|
65
|
+
|
|
66
|
+
// Act
|
|
67
|
+
const { result } = renderHook( () => useHasOverlapping() );
|
|
68
|
+
|
|
69
|
+
// Assert
|
|
70
|
+
expect( result.current ).toBe( false );
|
|
71
|
+
} );
|
|
72
|
+
|
|
73
|
+
it( 'should return true when off-canvas element exists and is visible', () => {
|
|
74
|
+
// Arrange
|
|
75
|
+
setupPreviewFrame( true, true );
|
|
76
|
+
|
|
77
|
+
// Act
|
|
78
|
+
const { result } = renderHook( () => useHasOverlapping() );
|
|
79
|
+
|
|
80
|
+
// Assert
|
|
81
|
+
expect( result.current ).toBe( true );
|
|
82
|
+
} );
|
|
83
|
+
|
|
84
|
+
it( 'should return false when off-canvas element exists but is not visible', () => {
|
|
85
|
+
// Arrange
|
|
86
|
+
setupPreviewFrame( true, false );
|
|
87
|
+
|
|
88
|
+
// Act
|
|
89
|
+
const { result } = renderHook( () => useHasOverlapping() );
|
|
90
|
+
|
|
91
|
+
// Assert
|
|
92
|
+
expect( result.current ).toBe( false );
|
|
93
|
+
} );
|
|
94
|
+
|
|
95
|
+
it( 'should check visibility with correct options', () => {
|
|
96
|
+
// Arrange
|
|
97
|
+
setupPreviewFrame( true, true );
|
|
98
|
+
|
|
99
|
+
// Act
|
|
100
|
+
renderHook( () => useHasOverlapping() );
|
|
101
|
+
|
|
102
|
+
// Assert
|
|
103
|
+
// eslint-disable-next-line testing-library/no-node-access
|
|
104
|
+
const offCanvasElement = mockDocument.querySelector( `.${ OFF_CANVAS_CLASS }` );
|
|
105
|
+
expect( offCanvasElement?.checkVisibility ).toHaveBeenCalledWith( {
|
|
106
|
+
opacityProperty: true,
|
|
107
|
+
visibilityProperty: true,
|
|
108
|
+
contentVisibilityAuto: true,
|
|
109
|
+
} );
|
|
110
|
+
} );
|
|
111
|
+
|
|
112
|
+
it( 'should return true when multiple off-canvas elements exist and at least one is visible', () => {
|
|
113
|
+
// Arrange
|
|
114
|
+
mockPreviewFrame = createDOMElement( { tag: 'iframe' } ) as HTMLIFrameElement;
|
|
115
|
+
mockDocument = document.implementation.createHTMLDocument( 'Preview' );
|
|
116
|
+
|
|
117
|
+
const offCanvasElement1 = createDOMElement( {
|
|
118
|
+
tag: 'div',
|
|
119
|
+
attrs: { class: OFF_CANVAS_CLASS },
|
|
120
|
+
} );
|
|
121
|
+
offCanvasElement1.checkVisibility = jest.fn().mockReturnValue( false );
|
|
122
|
+
|
|
123
|
+
const offCanvasElement2 = createDOMElement( {
|
|
124
|
+
tag: 'div',
|
|
125
|
+
attrs: { class: OFF_CANVAS_CLASS },
|
|
126
|
+
} );
|
|
127
|
+
offCanvasElement2.checkVisibility = jest.fn().mockReturnValue( true );
|
|
128
|
+
|
|
129
|
+
mockDocument.body.appendChild( offCanvasElement1 );
|
|
130
|
+
mockDocument.body.appendChild( offCanvasElement2 );
|
|
131
|
+
|
|
132
|
+
Object.defineProperty( mockPreviewFrame, 'contentWindow', {
|
|
133
|
+
value: {
|
|
134
|
+
document: mockDocument,
|
|
135
|
+
},
|
|
136
|
+
writable: true,
|
|
137
|
+
} );
|
|
138
|
+
|
|
139
|
+
( window as unknown as CanvasExtendedWindow ).elementor = {
|
|
140
|
+
$preview: [ mockPreviewFrame ],
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
// Act
|
|
144
|
+
const { result } = renderHook( () => useHasOverlapping() );
|
|
145
|
+
|
|
146
|
+
// Assert
|
|
147
|
+
expect( result.current ).toBe( true );
|
|
148
|
+
} );
|
|
149
|
+
|
|
150
|
+
it( 'should return false when multiple off-canvas elements exist but none are visible', () => {
|
|
151
|
+
// Arrange
|
|
152
|
+
mockPreviewFrame = createDOMElement( { tag: 'iframe' } ) as HTMLIFrameElement;
|
|
153
|
+
mockDocument = document.implementation.createHTMLDocument( 'Preview' );
|
|
154
|
+
|
|
155
|
+
const offCanvasElement1 = createDOMElement( {
|
|
156
|
+
tag: 'div',
|
|
157
|
+
attrs: { class: OFF_CANVAS_CLASS },
|
|
158
|
+
} );
|
|
159
|
+
offCanvasElement1.checkVisibility = jest.fn().mockReturnValue( false );
|
|
160
|
+
|
|
161
|
+
const offCanvasElement2 = createDOMElement( {
|
|
162
|
+
tag: 'div',
|
|
163
|
+
attrs: { class: OFF_CANVAS_CLASS },
|
|
164
|
+
} );
|
|
165
|
+
offCanvasElement2.checkVisibility = jest.fn().mockReturnValue( false );
|
|
166
|
+
|
|
167
|
+
mockDocument.body.appendChild( offCanvasElement1 );
|
|
168
|
+
mockDocument.body.appendChild( offCanvasElement2 );
|
|
169
|
+
|
|
170
|
+
Object.defineProperty( mockPreviewFrame, 'contentWindow', {
|
|
171
|
+
value: {
|
|
172
|
+
document: mockDocument,
|
|
173
|
+
},
|
|
174
|
+
writable: true,
|
|
175
|
+
} );
|
|
176
|
+
|
|
177
|
+
( window as unknown as CanvasExtendedWindow ).elementor = {
|
|
178
|
+
$preview: [ mockPreviewFrame ],
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
// Act
|
|
182
|
+
const { result } = renderHook( () => useHasOverlapping() );
|
|
183
|
+
|
|
184
|
+
// Assert
|
|
185
|
+
expect( result.current ).toBe( false );
|
|
186
|
+
} );
|
|
187
|
+
} );
|
|
@@ -8,6 +8,7 @@ type Options = {
|
|
|
8
8
|
|
|
9
9
|
export function useFloatingOnElement( { element, isSelected }: Options ) {
|
|
10
10
|
const [ isOpen, setIsOpen ] = useState( false );
|
|
11
|
+
const sizeModifier = 2;
|
|
11
12
|
|
|
12
13
|
const { refs, floatingStyles, context } = useFloating( {
|
|
13
14
|
// Must be controlled for interactions (like hover) to work.
|
|
@@ -18,15 +19,17 @@ export function useFloatingOnElement( { element, isSelected }: Options ) {
|
|
|
18
19
|
|
|
19
20
|
middleware: [
|
|
20
21
|
// Match the floating element's size to the reference element.
|
|
21
|
-
size( {
|
|
22
|
-
apply( { elements, rects } ) {
|
|
23
|
-
Object.assign( elements.floating.style, {
|
|
24
|
-
width: `${ rects.reference.width + 2 }px`,
|
|
25
|
-
height: `${ rects.reference.height + 2 }px`,
|
|
26
|
-
} );
|
|
27
|
-
},
|
|
28
|
-
} ),
|
|
29
22
|
|
|
23
|
+
size( () => {
|
|
24
|
+
return {
|
|
25
|
+
apply( { elements, rects } ) {
|
|
26
|
+
Object.assign( elements.floating.style, {
|
|
27
|
+
width: `${ rects.reference.width + sizeModifier }px`,
|
|
28
|
+
height: `${ rects.reference.height + sizeModifier }px`,
|
|
29
|
+
} );
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
} ),
|
|
30
33
|
// Center the floating element on the reference element.
|
|
31
34
|
offset( ( { rects } ) => -rects.reference.height / 2 - rects.floating.height / 2 ),
|
|
32
35
|
],
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type CanvasExtendedWindow } from '../sync/types';
|
|
2
|
+
|
|
3
|
+
const possibleOverlappingSelectors = [ '.e-off-canvas' ]; // can add more selectors here if needed, make sure to loop through them to check classList
|
|
4
|
+
|
|
5
|
+
export const useHasOverlapping = () => {
|
|
6
|
+
const preview = ( window as unknown as CanvasExtendedWindow ).elementor?.$preview?.[ 0 ];
|
|
7
|
+
if ( ! preview ) {
|
|
8
|
+
return false;
|
|
9
|
+
}
|
|
10
|
+
const hasOverlapping = possibleOverlappingSelectors
|
|
11
|
+
.map( ( selector ) => Array.from( preview?.contentWindow?.document.body.querySelectorAll( selector ) ?? [] ) )
|
|
12
|
+
.flat()
|
|
13
|
+
.some( ( elem ) =>
|
|
14
|
+
elem.checkVisibility( {
|
|
15
|
+
opacityProperty: true,
|
|
16
|
+
visibilityProperty: true,
|
|
17
|
+
contentVisibilityAuto: true,
|
|
18
|
+
} )
|
|
19
|
+
);
|
|
20
|
+
return hasOverlapping;
|
|
21
|
+
};
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { type Dispatch, type SetStateAction, useEffect, useMemo, useState } from 'react';
|
|
2
|
+
import { type InteractionItem, interactionsRepository } from '@elementor/editor-interactions';
|
|
3
|
+
import { registerDataHook } from '@elementor/editor-v1-adapters';
|
|
4
|
+
|
|
5
|
+
import { useOnMount } from './use-on-mount';
|
|
6
|
+
|
|
7
|
+
type ProviderAndInteractionItems = {
|
|
8
|
+
provider: ReturnType< typeof interactionsRepository.getProviders >[ 0 ];
|
|
9
|
+
items: InteractionItem[];
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
type ProviderAndSubscriber = {
|
|
13
|
+
provider: ReturnType< typeof interactionsRepository.getProviders >[ 0 ];
|
|
14
|
+
subscriber: () => void;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
type ProviderAndInteractionItemsMap = Record< string, ProviderAndInteractionItems >;
|
|
18
|
+
|
|
19
|
+
export function useInteractionsItems() {
|
|
20
|
+
const [ interactionItems, setInteractionItems ] = useState< ProviderAndInteractionItemsMap >( {} );
|
|
21
|
+
|
|
22
|
+
const providerAndSubscribers = useMemo( () => {
|
|
23
|
+
try {
|
|
24
|
+
const providers = interactionsRepository.getProviders();
|
|
25
|
+
const mapped = providers.map( ( provider ): ProviderAndSubscriber => {
|
|
26
|
+
return {
|
|
27
|
+
provider,
|
|
28
|
+
subscriber: createProviderSubscriber( {
|
|
29
|
+
provider,
|
|
30
|
+
setInteractionItems,
|
|
31
|
+
} ),
|
|
32
|
+
};
|
|
33
|
+
} );
|
|
34
|
+
return mapped;
|
|
35
|
+
} catch {
|
|
36
|
+
return [];
|
|
37
|
+
}
|
|
38
|
+
}, [] );
|
|
39
|
+
|
|
40
|
+
useEffect( () => {
|
|
41
|
+
if ( providerAndSubscribers.length === 0 ) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const unsubscribes = providerAndSubscribers.map( ( { provider, subscriber } ) => {
|
|
46
|
+
const safeSubscriber = () => {
|
|
47
|
+
try {
|
|
48
|
+
subscriber();
|
|
49
|
+
} catch {}
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const unsubscribe = provider.subscribe( safeSubscriber );
|
|
53
|
+
return unsubscribe;
|
|
54
|
+
} );
|
|
55
|
+
|
|
56
|
+
return () => {
|
|
57
|
+
unsubscribes.forEach( ( unsubscribe ) => unsubscribe() );
|
|
58
|
+
};
|
|
59
|
+
}, [ providerAndSubscribers ] );
|
|
60
|
+
|
|
61
|
+
useOnMount( () => {
|
|
62
|
+
if ( providerAndSubscribers.length === 0 ) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
registerDataHook( 'after', 'editor/documents/attach-preview', async () => {
|
|
67
|
+
providerAndSubscribers.forEach( ( { subscriber } ) => {
|
|
68
|
+
try {
|
|
69
|
+
subscriber();
|
|
70
|
+
} catch {}
|
|
71
|
+
} );
|
|
72
|
+
} );
|
|
73
|
+
} );
|
|
74
|
+
|
|
75
|
+
return useMemo( () => {
|
|
76
|
+
const result = Object.values( interactionItems )
|
|
77
|
+
.sort( sortByProviderPriority )
|
|
78
|
+
.flatMap( ( { items } ) => items );
|
|
79
|
+
|
|
80
|
+
return result;
|
|
81
|
+
}, [ interactionItems ] );
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function sortByProviderPriority(
|
|
85
|
+
{ provider: providerA }: ProviderAndInteractionItems,
|
|
86
|
+
{ provider: providerB }: ProviderAndInteractionItems
|
|
87
|
+
) {
|
|
88
|
+
return providerA.priority - providerB.priority;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
type CreateProviderSubscriberArgs = {
|
|
92
|
+
provider: ReturnType< typeof interactionsRepository.getProviders >[ 0 ];
|
|
93
|
+
setInteractionItems: Dispatch< SetStateAction< ProviderAndInteractionItemsMap > >;
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
function createProviderSubscriber( { provider, setInteractionItems }: CreateProviderSubscriberArgs ) {
|
|
97
|
+
return () => {
|
|
98
|
+
try {
|
|
99
|
+
const items = provider.actions.all();
|
|
100
|
+
const providerKey = provider.getKey();
|
|
101
|
+
|
|
102
|
+
setInteractionItems( ( prev ) => ( {
|
|
103
|
+
...prev,
|
|
104
|
+
[ providerKey ]: { provider, items },
|
|
105
|
+
} ) );
|
|
106
|
+
} catch {}
|
|
107
|
+
};
|
|
108
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type Dispatch, type SetStateAction, useEffect, useMemo, useState } from 'react';
|
|
2
2
|
import { type BreakpointId, getBreakpoints } from '@elementor/editor-responsive';
|
|
3
|
+
import { isClassState, type StyleDefinitionClassState } from '@elementor/editor-styles';
|
|
3
4
|
import { type StylesProvider, stylesRepository } from '@elementor/editor-styles-repository';
|
|
4
5
|
import { registerDataHook } from '@elementor/editor-v1-adapters';
|
|
5
6
|
|
|
@@ -58,19 +59,44 @@ export function useStyleItems() {
|
|
|
58
59
|
return useMemo(
|
|
59
60
|
() =>
|
|
60
61
|
Object.values( styleItems )
|
|
61
|
-
.sort(
|
|
62
|
+
.sort( sortByProviderPriority )
|
|
62
63
|
.flatMap( ( { items } ) => items )
|
|
63
|
-
.sort(
|
|
64
|
-
|
|
65
|
-
breakpointsOrder.indexOf( breakpointA as BreakpointId ) -
|
|
66
|
-
breakpointsOrder.indexOf( breakpointB as BreakpointId )
|
|
67
|
-
);
|
|
68
|
-
} ),
|
|
69
|
-
// eslint-disable-next-line
|
|
64
|
+
.sort( sortByStateType )
|
|
65
|
+
.sort( sortByBreakpoint( breakpointsOrder ) ),
|
|
70
66
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
71
67
|
[ styleItems, breakpointsOrder.join( '-' ) ]
|
|
72
68
|
);
|
|
73
69
|
}
|
|
70
|
+
function sortByProviderPriority(
|
|
71
|
+
{ provider: providerA }: ProviderAndStyleItems,
|
|
72
|
+
{ provider: providerB }: ProviderAndStyleItems
|
|
73
|
+
) {
|
|
74
|
+
return providerA.priority - providerB.priority;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function sortByBreakpoint( breakpointsOrder: BreakpointId[] ) {
|
|
78
|
+
return ( { breakpoint: breakpointA }: StyleItem, { breakpoint: breakpointB }: StyleItem ) =>
|
|
79
|
+
breakpointsOrder.indexOf( breakpointA as BreakpointId ) -
|
|
80
|
+
breakpointsOrder.indexOf( breakpointB as BreakpointId );
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function sortByStateType( { state: stateA }: StyleItem, { state: stateB }: StyleItem ) {
|
|
84
|
+
if (
|
|
85
|
+
isClassState( stateA as StyleDefinitionClassState ) &&
|
|
86
|
+
! isClassState( stateB as StyleDefinitionClassState )
|
|
87
|
+
) {
|
|
88
|
+
return -1;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (
|
|
92
|
+
! isClassState( stateA as StyleDefinitionClassState ) &&
|
|
93
|
+
isClassState( stateB as StyleDefinitionClassState )
|
|
94
|
+
) {
|
|
95
|
+
return 1;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return 0;
|
|
99
|
+
}
|
|
74
100
|
|
|
75
101
|
type CreateProviderSubscriberArgs = {
|
|
76
102
|
provider: StylesProvider;
|
package/src/index.ts
CHANGED
|
@@ -4,4 +4,13 @@ export { styleTransformersRegistry } from './style-transformers-registry';
|
|
|
4
4
|
export { settingsTransformersRegistry } from './settings-transformers-registry';
|
|
5
5
|
export { createTransformer } from './transformers/create-transformer';
|
|
6
6
|
export { createTransformersRegistry } from './transformers/create-transformers-registry';
|
|
7
|
+
export { type AnyTransformer } from './transformers/types';
|
|
7
8
|
export { createPropsResolver, type PropsResolver } from './renderers/create-props-resolver';
|
|
9
|
+
export { startDragElementFromPanel, endDragElementFromPanel } from './sync/drag-element-from-panel';
|
|
10
|
+
export { registerElementType } from './legacy/init-legacy-views';
|
|
11
|
+
export {
|
|
12
|
+
createTemplatedElementView,
|
|
13
|
+
type CreateTemplatedElementTypeOptions,
|
|
14
|
+
} from './legacy/create-templated-element-type';
|
|
15
|
+
export { getCanvasIframeDocument } from './sync/get-canvas-iframe-document';
|
|
16
|
+
export * from './legacy/types';
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { settingsTransformersRegistry } from './settings-transformers-registry';
|
|
2
2
|
import { attributesTransformer } from './transformers/settings/attributes-transformer';
|
|
3
3
|
import { createClassesTransformer } from './transformers/settings/classes-transformer';
|
|
4
|
+
import { dateTimeTransformer } from './transformers/settings/date-time-transformer';
|
|
4
5
|
import { linkTransformer } from './transformers/settings/link-transformer';
|
|
6
|
+
import { queryTransformer } from './transformers/settings/query-transformer';
|
|
5
7
|
import { imageSrcTransformer } from './transformers/shared/image-src-transformer';
|
|
6
8
|
import { imageTransformer } from './transformers/shared/image-transformer';
|
|
7
9
|
import { plainTransformer } from './transformers/shared/plain-transformer';
|
|
@@ -10,8 +12,10 @@ export function initSettingsTransformers() {
|
|
|
10
12
|
settingsTransformersRegistry
|
|
11
13
|
.register( 'classes', createClassesTransformer() )
|
|
12
14
|
.register( 'link', linkTransformer )
|
|
15
|
+
.register( 'query', queryTransformer )
|
|
13
16
|
.register( 'image', imageTransformer )
|
|
14
17
|
.register( 'image-src', imageSrcTransformer )
|
|
15
18
|
.register( 'attributes', attributesTransformer )
|
|
19
|
+
.register( 'date-time', dateTimeTransformer )
|
|
16
20
|
.registerFallback( plainTransformer );
|
|
17
21
|
}
|
package/src/init.tsx
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
import { injectIntoLogic, injectIntoTop } from '@elementor/editor';
|
|
2
|
+
import { init as initInteractionsRepository } from '@elementor/editor-interactions';
|
|
3
|
+
import { getMCPByDomain } from '@elementor/editor-mcp';
|
|
2
4
|
|
|
3
5
|
import { ClassesRename } from './components/classes-rename';
|
|
4
6
|
import { ElementsOverlays } from './components/elements-overlays';
|
|
7
|
+
import { InteractionsRenderer } from './components/interactions-renderer';
|
|
5
8
|
import { StyleRenderer } from './components/style-renderer';
|
|
6
9
|
import { initSettingsTransformers } from './init-settings-transformers';
|
|
7
10
|
import { initStyleTransformers } from './init-style-transformers';
|
|
8
11
|
import { initLegacyViews } from './legacy/init-legacy-views';
|
|
12
|
+
import { initCanvasMcp } from './mcp/canvas-mcp';
|
|
13
|
+
import { mcpDescription } from './mcp/mcp-description';
|
|
9
14
|
import { initLinkInLinkPrevention } from './prevent-link-in-link-commands';
|
|
10
15
|
import { initStyleCommands } from './style-commands/init-style-commands';
|
|
11
16
|
|
|
@@ -19,6 +24,8 @@ export function init() {
|
|
|
19
24
|
|
|
20
25
|
initSettingsTransformers();
|
|
21
26
|
|
|
27
|
+
initInteractionsRepository();
|
|
28
|
+
|
|
22
29
|
injectIntoTop( {
|
|
23
30
|
id: 'elements-overlays',
|
|
24
31
|
component: ElementsOverlays,
|
|
@@ -29,8 +36,19 @@ export function init() {
|
|
|
29
36
|
component: StyleRenderer,
|
|
30
37
|
} );
|
|
31
38
|
|
|
39
|
+
injectIntoTop( {
|
|
40
|
+
id: 'canvas-interactions-render',
|
|
41
|
+
component: InteractionsRenderer,
|
|
42
|
+
} );
|
|
43
|
+
|
|
32
44
|
injectIntoLogic( {
|
|
33
45
|
id: 'classes-rename',
|
|
34
46
|
component: ClassesRename,
|
|
35
47
|
} );
|
|
48
|
+
|
|
49
|
+
initCanvasMcp(
|
|
50
|
+
getMCPByDomain( 'canvas', {
|
|
51
|
+
instructions: mcpDescription,
|
|
52
|
+
} )
|
|
53
|
+
);
|
|
36
54
|
}
|