@elementor/editor-canvas 4.0.0-573 → 4.0.0-597
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +169 -155
- package/dist/index.mjs +172 -158
- package/package.json +18 -18
- package/src/form-structure/utils.ts +7 -1
- package/src/init-settings-transformers.ts +2 -0
- package/src/legacy/replacements/inline-editing/__tests__/inline-editing-eligibility.test.ts +7 -7
- package/src/legacy/replacements/inline-editing/canvas-inline-editor.tsx +31 -125
- package/src/legacy/replacements/inline-editing/inline-editing-elements.tsx +6 -5
- package/src/legacy/replacements/inline-editing/inline-editing-eligibility.ts +3 -3
- package/src/legacy/replacements/inline-editing/inline-editing-utils.ts +137 -39
- package/src/legacy/tabs-model-extensions.ts +5 -2
- package/src/mcp/tools/build-composition/schema.ts +14 -0
- package/src/mcp/tools/build-composition/tool.ts +10 -11
- package/src/transformers/settings/html-v3-transformer.ts +10 -0
|
@@ -1,26 +1,19 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import { useEffect, useState } from 'react';
|
|
2
|
+
import { useEffect, useLayoutEffect, useState } from 'react';
|
|
3
3
|
import { InlineEditor, InlineEditorToolbar } from '@elementor/editor-controls';
|
|
4
4
|
import { Box, ThemeProvider } from '@elementor/ui';
|
|
5
|
-
import { FloatingPortal,
|
|
5
|
+
import { autoUpdate, flip, FloatingPortal, useFloating } from '@floating-ui/react';
|
|
6
6
|
|
|
7
7
|
import { CANVAS_WRAPPER_ID, OutlineOverlay } from '../../../components/outline-overlay';
|
|
8
|
-
import { useBindReactPropsToElement } from '../../../hooks/use-bind-react-props-to-element';
|
|
9
|
-
import { useFloatingOnElement } from '../../../hooks/use-floating-on-element';
|
|
10
8
|
import {
|
|
11
|
-
calcSelectionCenterOffsets,
|
|
12
9
|
type Editor,
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
10
|
+
getInlineEditorElement,
|
|
11
|
+
horizontalShifterMiddleware as horizontalShifter,
|
|
12
|
+
removeToolbarAnchor,
|
|
13
|
+
useOnClickOutsideIframe,
|
|
14
|
+
useRenderToolbar,
|
|
16
15
|
} from './inline-editing-utils';
|
|
17
16
|
|
|
18
|
-
const TOP_BAR_SELECTOR = '#elementor-editor-wrapper-v2';
|
|
19
|
-
const NAVIGATOR_SELECTOR = '#elementor-navigator';
|
|
20
|
-
const EDITING_PANEL = '#elementor-panel';
|
|
21
|
-
|
|
22
|
-
const EDITOR_ELEMENTS_OUT_OF_IFRAME = [ TOP_BAR_SELECTOR, NAVIGATOR_SELECTOR, EDITING_PANEL ];
|
|
23
|
-
|
|
24
17
|
const EDITOR_WRAPPER_SELECTOR = 'inline-editor-wrapper';
|
|
25
18
|
|
|
26
19
|
export const CanvasInlineEditor = ( {
|
|
@@ -30,7 +23,7 @@ export const CanvasInlineEditor = ( {
|
|
|
30
23
|
rootElement,
|
|
31
24
|
id,
|
|
32
25
|
setValue,
|
|
33
|
-
|
|
26
|
+
...props
|
|
34
27
|
}: {
|
|
35
28
|
elementClasses: string;
|
|
36
29
|
initialValue: string | null;
|
|
@@ -40,13 +33,13 @@ export const CanvasInlineEditor = ( {
|
|
|
40
33
|
setValue: ( value: string | null ) => void;
|
|
41
34
|
onBlur: () => void;
|
|
42
35
|
} ) => {
|
|
43
|
-
const [ selectionOffsets, setSelectionOffsets ] = useState< Offsets | null >( null );
|
|
44
36
|
const [ editor, setEditor ] = useState< Editor | null >( null );
|
|
37
|
+
const { onSelectionEnd, anchor: toolbarAnchor } = useRenderToolbar( rootElement.ownerDocument, id );
|
|
45
38
|
|
|
46
|
-
const
|
|
47
|
-
|
|
39
|
+
const onBlur = () => {
|
|
40
|
+
removeToolbarAnchor( rootElement.ownerDocument, id );
|
|
48
41
|
|
|
49
|
-
|
|
42
|
+
props.onBlur();
|
|
50
43
|
};
|
|
51
44
|
|
|
52
45
|
useOnClickOutsideIframe( onBlur );
|
|
@@ -59,10 +52,10 @@ export const CanvasInlineEditor = ( {
|
|
|
59
52
|
.ProseMirror > * {
|
|
60
53
|
height: 100%;
|
|
61
54
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
55
|
+
.${ EDITOR_WRAPPER_SELECTOR } .ProseMirror > button[contenteditable="true"] {
|
|
56
|
+
height: auto;
|
|
57
|
+
cursor: text;
|
|
58
|
+
}
|
|
66
59
|
` }
|
|
67
60
|
</style>
|
|
68
61
|
<InlineEditor
|
|
@@ -78,18 +71,9 @@ export const CanvasInlineEditor = ( {
|
|
|
78
71
|
onBlur={ onBlur }
|
|
79
72
|
autofocus
|
|
80
73
|
expectedTag={ expectedTag }
|
|
81
|
-
wrapperClassName={ EDITOR_WRAPPER_SELECTOR }
|
|
82
74
|
onSelectionEnd={ onSelectionEnd }
|
|
83
75
|
/>
|
|
84
|
-
{
|
|
85
|
-
<InlineEditingToolbarWrapper
|
|
86
|
-
expectedTag={ expectedTag }
|
|
87
|
-
editor={ editor }
|
|
88
|
-
rootElement={ rootElement }
|
|
89
|
-
id={ id }
|
|
90
|
-
selectionOffsets={ selectionOffsets }
|
|
91
|
-
/>
|
|
92
|
-
) }
|
|
76
|
+
{ toolbarAnchor && editor && <InlineEditingToolbar anchor={ toolbarAnchor } editor={ editor } id={ id } /> }
|
|
93
77
|
</ThemeProvider>
|
|
94
78
|
);
|
|
95
79
|
};
|
|
@@ -113,104 +97,26 @@ const InlineEditingOverlay = ( {
|
|
|
113
97
|
return overlayRefElement ? <OutlineOverlay element={ overlayRefElement } id={ id } isSelected /> : null;
|
|
114
98
|
};
|
|
115
99
|
|
|
116
|
-
const
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
expectedTag: string | null;
|
|
124
|
-
editor: Editor;
|
|
125
|
-
rootElement: HTMLElement;
|
|
126
|
-
id: string;
|
|
127
|
-
selectionOffsets: Offsets;
|
|
128
|
-
} ) => {
|
|
129
|
-
const [ element, setElement ] = useState< HTMLElement | null >( null );
|
|
130
|
-
|
|
131
|
-
useEffect( () => {
|
|
132
|
-
setElement( getInlineEditorElement( rootElement, expectedTag ) );
|
|
133
|
-
}, [ expectedTag, rootElement ] );
|
|
134
|
-
|
|
135
|
-
return element ? (
|
|
136
|
-
<InlineEditingToolbar element={ element } editor={ editor } id={ id } selectionOffsets={ selectionOffsets } />
|
|
137
|
-
) : null;
|
|
138
|
-
};
|
|
139
|
-
|
|
140
|
-
const InlineEditingToolbar = ( {
|
|
141
|
-
element,
|
|
142
|
-
editor,
|
|
143
|
-
id,
|
|
144
|
-
selectionOffsets,
|
|
145
|
-
}: {
|
|
146
|
-
element: HTMLElement;
|
|
147
|
-
editor: Editor;
|
|
148
|
-
id: string;
|
|
149
|
-
selectionOffsets: Offsets;
|
|
150
|
-
} ) => {
|
|
151
|
-
const { floating } = useFloatingOnElement( {
|
|
152
|
-
element,
|
|
153
|
-
isSelected: true,
|
|
100
|
+
const InlineEditingToolbar = ( { anchor, editor, id }: { anchor: HTMLElement; editor: Editor; id: string } ) => {
|
|
101
|
+
const { refs, floatingStyles } = useFloating( {
|
|
102
|
+
placement: 'top',
|
|
103
|
+
strategy: 'fixed',
|
|
104
|
+
transform: false,
|
|
105
|
+
whileElementsMounted: autoUpdate,
|
|
106
|
+
middleware: [ horizontalShifter, flip() ],
|
|
154
107
|
} );
|
|
155
|
-
const { getFloatingProps, getReferenceProps } = useInteractions();
|
|
156
|
-
const style = getComputedStyle( floating.styles, selectionOffsets );
|
|
157
108
|
|
|
158
|
-
|
|
109
|
+
useLayoutEffect( () => {
|
|
110
|
+
refs.setReference( anchor );
|
|
111
|
+
|
|
112
|
+
return () => refs.setReference( null );
|
|
113
|
+
}, [ anchor, refs ] );
|
|
159
114
|
|
|
160
115
|
return (
|
|
161
116
|
<FloatingPortal id={ CANVAS_WRAPPER_ID }>
|
|
162
|
-
<Box
|
|
163
|
-
|
|
164
|
-
style={ {
|
|
165
|
-
...floating.styles,
|
|
166
|
-
pointerEvents: 'none',
|
|
167
|
-
} }
|
|
168
|
-
role="presentation"
|
|
169
|
-
{ ...getFloatingProps( { style } ) }
|
|
170
|
-
>
|
|
171
|
-
{ floating.styles.transform && (
|
|
172
|
-
<Box
|
|
173
|
-
sx={ {
|
|
174
|
-
position: 'relative',
|
|
175
|
-
transform: 'translateY(-100%)',
|
|
176
|
-
height: 'max-content',
|
|
177
|
-
} }
|
|
178
|
-
>
|
|
179
|
-
<InlineEditorToolbar
|
|
180
|
-
editor={ editor }
|
|
181
|
-
elementId={ id }
|
|
182
|
-
sx={ {
|
|
183
|
-
transform: 'translateX(-50%)',
|
|
184
|
-
} }
|
|
185
|
-
/>
|
|
186
|
-
</Box>
|
|
187
|
-
) }
|
|
117
|
+
<Box ref={ refs.setFloating } role="presentation" style={ { ...floatingStyles, pointerEvents: 'none' } }>
|
|
118
|
+
<InlineEditorToolbar editor={ editor } elementId={ id } />
|
|
188
119
|
</Box>
|
|
189
120
|
</FloatingPortal>
|
|
190
121
|
);
|
|
191
122
|
};
|
|
192
|
-
|
|
193
|
-
const getInlineEditorElement = ( elementWrapper: HTMLElement, expectedTag: string | null ) => {
|
|
194
|
-
return ! expectedTag ? null : ( elementWrapper.querySelector( expectedTag ) as HTMLDivElement );
|
|
195
|
-
};
|
|
196
|
-
|
|
197
|
-
// Elements out of iframe and canvas don't trigger "onClickAway" which unmounts the editor
|
|
198
|
-
// since they are not part of the iframes owner document.
|
|
199
|
-
// We need to manually add listeners to these elements to unmount the editor when they are clicked.
|
|
200
|
-
const useOnClickOutsideIframe = ( handleUnmount: () => void ) => {
|
|
201
|
-
const asyncUnmountInlineEditor = React.useCallback( () => queueMicrotask( handleUnmount ), [ handleUnmount ] );
|
|
202
|
-
|
|
203
|
-
useEffect( () => {
|
|
204
|
-
EDITOR_ELEMENTS_OUT_OF_IFRAME.forEach(
|
|
205
|
-
( selector ) =>
|
|
206
|
-
document?.querySelector( selector )?.addEventListener( 'mousedown', asyncUnmountInlineEditor )
|
|
207
|
-
);
|
|
208
|
-
|
|
209
|
-
return () =>
|
|
210
|
-
EDITOR_ELEMENTS_OUT_OF_IFRAME.forEach(
|
|
211
|
-
( selector ) =>
|
|
212
|
-
document?.querySelector( selector )?.removeEventListener( 'mousedown', asyncUnmountInlineEditor )
|
|
213
|
-
);
|
|
214
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
215
|
-
}, [] );
|
|
216
|
-
};
|
|
@@ -2,7 +2,7 @@ import * as React from 'react';
|
|
|
2
2
|
import { createRoot, type Root } from 'react-dom/client';
|
|
3
3
|
import { getContainer, getElementLabel, getElementType } from '@elementor/editor-elements';
|
|
4
4
|
import {
|
|
5
|
-
|
|
5
|
+
htmlV3PropTypeUtil,
|
|
6
6
|
parseHtmlChildren,
|
|
7
7
|
type PropType,
|
|
8
8
|
type PropValue,
|
|
@@ -127,8 +127,9 @@ export default class InlineEditingReplacement extends ReplacementBase {
|
|
|
127
127
|
|
|
128
128
|
getExtractedContentValue() {
|
|
129
129
|
const propValue = this.getInlineEditablePropValue();
|
|
130
|
+
const extracted = htmlV3PropTypeUtil.extract( propValue );
|
|
130
131
|
|
|
131
|
-
return
|
|
132
|
+
return stringPropTypeUtil.extract( extracted?.content ?? null ) ?? '';
|
|
132
133
|
}
|
|
133
134
|
|
|
134
135
|
setContentValue( value: string | null ) {
|
|
@@ -136,8 +137,8 @@ export default class InlineEditingReplacement extends ReplacementBase {
|
|
|
136
137
|
const html = value || '';
|
|
137
138
|
const parsed = parseHtmlChildren( html );
|
|
138
139
|
|
|
139
|
-
const valueToSave =
|
|
140
|
-
content: parsed.content
|
|
140
|
+
const valueToSave = htmlV3PropTypeUtil.create( {
|
|
141
|
+
content: parsed.content ? stringPropTypeUtil.create( parsed.content ) : null,
|
|
141
142
|
children: parsed.children,
|
|
142
143
|
} );
|
|
143
144
|
|
|
@@ -174,7 +175,7 @@ export default class InlineEditingReplacement extends ReplacementBase {
|
|
|
174
175
|
}
|
|
175
176
|
|
|
176
177
|
if ( propType.kind === 'union' ) {
|
|
177
|
-
const textKeys = [
|
|
178
|
+
const textKeys = [ htmlV3PropTypeUtil.key, stringPropTypeUtil.key ];
|
|
178
179
|
|
|
179
180
|
for ( const key of textKeys ) {
|
|
180
181
|
if ( propType.prop_types[ key ] ) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { htmlV3PropTypeUtil, type PropType, stringPropTypeUtil } from '@elementor/editor-props';
|
|
2
2
|
|
|
3
3
|
type InlineEditingEligibilityArgs = {
|
|
4
4
|
rawValue: unknown;
|
|
@@ -9,7 +9,7 @@ const hasKey = ( propType: PropType ): propType is PropType & { key: unknown } =
|
|
|
9
9
|
return 'key' in propType;
|
|
10
10
|
};
|
|
11
11
|
|
|
12
|
-
const TEXT_PROP_TYPE_KEYS = new Set( [
|
|
12
|
+
const TEXT_PROP_TYPE_KEYS = new Set( [ htmlV3PropTypeUtil.key, stringPropTypeUtil.key ] );
|
|
13
13
|
|
|
14
14
|
const isCoreTextPropTypeKey = ( key: unknown ): boolean => {
|
|
15
15
|
return ( TEXT_PROP_TYPE_KEYS as Set< unknown > ).has( key );
|
|
@@ -36,5 +36,5 @@ export const isInlineEditingAllowed = ( { rawValue, propTypeFromSchema }: Inline
|
|
|
36
36
|
return isAllowedBySchema( propTypeFromSchema );
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
return
|
|
39
|
+
return htmlV3PropTypeUtil.isValid( rawValue ) || stringPropTypeUtil.isValid( rawValue );
|
|
40
40
|
};
|
|
@@ -1,9 +1,35 @@
|
|
|
1
|
-
import { type CSSProperties } from 'react';
|
|
1
|
+
import { type CSSProperties, useCallback, useEffect, useState } from 'react';
|
|
2
2
|
import { type InlineEditorToolbarProps } from '@elementor/editor-controls';
|
|
3
3
|
import { type V1Element } from '@elementor/editor-elements';
|
|
4
|
+
import { type MiddlewareReturn, type MiddlewareState } from '@floating-ui/react';
|
|
4
5
|
|
|
5
6
|
import { type LegacyWindow } from '../../types';
|
|
6
7
|
|
|
8
|
+
const TOP_BAR_SELECTOR = '#elementor-editor-wrapper-v2';
|
|
9
|
+
const NAVIGATOR_SELECTOR = '#elementor-navigator';
|
|
10
|
+
const EDITING_PANEL = '#elementor-panel';
|
|
11
|
+
|
|
12
|
+
const EDITOR_ELEMENTS_OUT_OF_IFRAME = [ TOP_BAR_SELECTOR, NAVIGATOR_SELECTOR, EDITING_PANEL ];
|
|
13
|
+
|
|
14
|
+
export const EDITOR_WRAPPER_SELECTOR = 'inline-editor-wrapper';
|
|
15
|
+
|
|
16
|
+
const TOOLBAR_ANCHOR_ID_PREFIX = 'inline-editing-toolbar-anchor';
|
|
17
|
+
|
|
18
|
+
const TOOLBAR_ANCHOR_STATIC_STYLES: CSSProperties = {
|
|
19
|
+
backgroundColor: 'transparent',
|
|
20
|
+
border: 'none',
|
|
21
|
+
outline: 'none',
|
|
22
|
+
boxShadow: 'none',
|
|
23
|
+
padding: '0',
|
|
24
|
+
margin: '0',
|
|
25
|
+
borderRadius: '0',
|
|
26
|
+
overflow: 'hidden',
|
|
27
|
+
opacity: '0',
|
|
28
|
+
pointerEvents: 'none',
|
|
29
|
+
position: 'absolute',
|
|
30
|
+
display: 'block',
|
|
31
|
+
};
|
|
32
|
+
|
|
7
33
|
export type Editor = InlineEditorToolbarProps[ 'editor' ];
|
|
8
34
|
export type EditorView = Editor[ 'view' ];
|
|
9
35
|
|
|
@@ -25,62 +51,134 @@ export const getWidgetType = ( container: V1Element | null ) => {
|
|
|
25
51
|
return container?.model?.get( 'widgetType' ) ?? container?.model?.get( 'elType' ) ?? null;
|
|
26
52
|
};
|
|
27
53
|
|
|
28
|
-
export const
|
|
29
|
-
|
|
54
|
+
export const getInlineEditorElement = ( elementWrapper: HTMLElement, expectedTag: string | null ) => {
|
|
55
|
+
return ! expectedTag ? null : ( elementWrapper.querySelector( expectedTag ) as HTMLDivElement );
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
// Elements out of iframe and canvas don't trigger "onClickAway" which unmounts the editor
|
|
59
|
+
// since they are not part of the iframes owner document.
|
|
60
|
+
// We need to manually add listeners to these elements to unmount the editor when they are clicked.
|
|
61
|
+
export const useOnClickOutsideIframe = ( handleUnmount: () => void ) => {
|
|
62
|
+
const asyncUnmountInlineEditor = useCallback( () => queueMicrotask( handleUnmount ), [ handleUnmount ] );
|
|
63
|
+
|
|
64
|
+
useEffect( () => {
|
|
65
|
+
EDITOR_ELEMENTS_OUT_OF_IFRAME.forEach(
|
|
66
|
+
( selector ) =>
|
|
67
|
+
document?.querySelector( selector )?.addEventListener( 'mousedown', asyncUnmountInlineEditor )
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
return () =>
|
|
71
|
+
EDITOR_ELEMENTS_OUT_OF_IFRAME.forEach(
|
|
72
|
+
( selector ) =>
|
|
73
|
+
document?.querySelector( selector )?.removeEventListener( 'mousedown', asyncUnmountInlineEditor )
|
|
74
|
+
);
|
|
75
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
76
|
+
}, [] );
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
export const useRenderToolbar = ( ownerDocument: Document, id: string ) => {
|
|
80
|
+
const [ anchor, setAnchor ] = useState< HTMLElement | null >( null );
|
|
81
|
+
|
|
82
|
+
const onSelectionEnd = ( view: EditorView ) => {
|
|
83
|
+
const hasSelection = ! view.state.selection.empty;
|
|
84
|
+
|
|
85
|
+
removeToolbarAnchor( ownerDocument, id );
|
|
86
|
+
|
|
87
|
+
if ( hasSelection ) {
|
|
88
|
+
setAnchor( createAnchorBasedOnSelection( ownerDocument, id ) );
|
|
89
|
+
} else {
|
|
90
|
+
setAnchor( null );
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
return { onSelectionEnd, anchor };
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const createAnchorBasedOnSelection = ( ownerDocument: Document, id: string ): HTMLElement | null => {
|
|
98
|
+
const frameWindow = ownerDocument.defaultView;
|
|
30
99
|
const selection = frameWindow?.getSelection();
|
|
31
|
-
const editorContainer = view.dom;
|
|
32
100
|
|
|
33
|
-
if ( ! selection
|
|
101
|
+
if ( ! selection ) {
|
|
34
102
|
return null;
|
|
35
103
|
}
|
|
36
104
|
|
|
37
105
|
const range = selection.getRangeAt( 0 );
|
|
38
106
|
const selectionRect = range.getBoundingClientRect();
|
|
39
|
-
const
|
|
107
|
+
const bodyRect = ownerDocument.body.getBoundingClientRect();
|
|
108
|
+
const toolbarAnchor = ownerDocument.createElement( 'span' );
|
|
40
109
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}
|
|
110
|
+
styleToolbarAnchor( toolbarAnchor, selectionRect, bodyRect );
|
|
111
|
+
toolbarAnchor.setAttribute( 'id', getToolbarAnchorId( id ) );
|
|
44
112
|
|
|
45
|
-
|
|
113
|
+
ownerDocument.body.appendChild( toolbarAnchor );
|
|
46
114
|
|
|
47
|
-
|
|
48
|
-
|
|
115
|
+
return toolbarAnchor;
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
export const removeToolbarAnchor = ( ownerDocument: Document, id: string ) => {
|
|
119
|
+
const toolbarAnchor = getToolbarAnchor( ownerDocument, id );
|
|
49
120
|
|
|
50
|
-
|
|
121
|
+
if ( toolbarAnchor ) {
|
|
122
|
+
ownerDocument.body.removeChild( toolbarAnchor );
|
|
123
|
+
}
|
|
51
124
|
};
|
|
52
125
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
: {
|
|
64
|
-
|
|
65
|
-
|
|
126
|
+
const getToolbarAnchorId = ( id: string ) => `${ TOOLBAR_ANCHOR_ID_PREFIX }-${ id }`;
|
|
127
|
+
|
|
128
|
+
export const getToolbarAnchor = ( ownerDocument: Document, id: string ) =>
|
|
129
|
+
ownerDocument.getElementById( getToolbarAnchorId( id ) ) as HTMLElement | null;
|
|
130
|
+
|
|
131
|
+
const styleToolbarAnchor = ( anchor: HTMLElement, selectionRect: DOMRect, bodyRect: DOMRect ) => {
|
|
132
|
+
const { width, height } = selectionRect;
|
|
133
|
+
|
|
134
|
+
Object.assign( anchor.style, {
|
|
135
|
+
...TOOLBAR_ANCHOR_STATIC_STYLES,
|
|
136
|
+
top: `${ selectionRect.top - bodyRect.top }px`,
|
|
137
|
+
left: `${ selectionRect.left - bodyRect.left }px`,
|
|
138
|
+
width: `${ width }px`,
|
|
139
|
+
height: `${ height }px`,
|
|
140
|
+
} );
|
|
66
141
|
};
|
|
67
142
|
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
143
|
+
export const horizontalShifterMiddleware: {
|
|
144
|
+
name: string;
|
|
145
|
+
fn: ( state: MiddlewareState ) => MiddlewareReturn;
|
|
146
|
+
} = {
|
|
147
|
+
name: 'horizontalShifter',
|
|
148
|
+
fn( state ) {
|
|
149
|
+
const {
|
|
150
|
+
x: left,
|
|
151
|
+
y: top,
|
|
152
|
+
elements: { reference: anchor, floating },
|
|
153
|
+
} = state;
|
|
71
154
|
|
|
72
|
-
|
|
73
|
-
|
|
155
|
+
const newState: MiddlewareReturn = {
|
|
156
|
+
...state,
|
|
157
|
+
x: left,
|
|
158
|
+
y: top,
|
|
159
|
+
};
|
|
74
160
|
|
|
75
|
-
|
|
76
|
-
return null;
|
|
77
|
-
}
|
|
161
|
+
const isLeftOverflown = left < 0;
|
|
78
162
|
|
|
79
|
-
|
|
163
|
+
if ( isLeftOverflown ) {
|
|
164
|
+
newState.x = 0;
|
|
80
165
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
166
|
+
return newState;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const anchorRect = anchor.getBoundingClientRect();
|
|
170
|
+
const right = left + floating.offsetWidth;
|
|
171
|
+
const documentWidth = ( anchor as HTMLElement ).ownerDocument.body.offsetWidth;
|
|
172
|
+
const isRightOverflown = right > documentWidth && anchorRect.right < right;
|
|
173
|
+
|
|
174
|
+
if ( isRightOverflown ) {
|
|
175
|
+
const diff = right - documentWidth;
|
|
176
|
+
|
|
177
|
+
newState.x = left - diff;
|
|
178
|
+
|
|
179
|
+
return newState;
|
|
180
|
+
}
|
|
84
181
|
|
|
85
|
-
|
|
182
|
+
return newState;
|
|
183
|
+
},
|
|
86
184
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { htmlV3PropTypeUtil, stringPropTypeUtil } from '@elementor/editor-props';
|
|
2
2
|
|
|
3
3
|
import { type ModelExtensions } from './create-nested-templated-element-type';
|
|
4
4
|
import { registerModelExtensions } from './init-legacy-views';
|
|
@@ -23,7 +23,10 @@ const tabModelExtensions: ModelExtensions = {
|
|
|
23
23
|
...paragraphElement,
|
|
24
24
|
settings: {
|
|
25
25
|
...paragraphElement.settings,
|
|
26
|
-
paragraph:
|
|
26
|
+
paragraph: htmlV3PropTypeUtil.create( {
|
|
27
|
+
content: stringPropTypeUtil.create( `Tab ${ position }` ),
|
|
28
|
+
children: [],
|
|
29
|
+
} ),
|
|
27
30
|
},
|
|
28
31
|
};
|
|
29
32
|
|
|
@@ -26,3 +26,17 @@ export const inputSchema = {
|
|
|
26
26
|
)
|
|
27
27
|
.default( {} ),
|
|
28
28
|
};
|
|
29
|
+
|
|
30
|
+
export const outputSchema = {
|
|
31
|
+
errors: z.string().describe( 'Error message if the composition building failed' ).optional(),
|
|
32
|
+
xmlStructure: z
|
|
33
|
+
.string()
|
|
34
|
+
.describe(
|
|
35
|
+
'The built XML structure as a string. Must use this XML after completion of building the composition, it contains real IDs.'
|
|
36
|
+
)
|
|
37
|
+
.optional(),
|
|
38
|
+
llm_instructions: z
|
|
39
|
+
.string()
|
|
40
|
+
.describe( 'Instructions what to do next, Important to follow these instructions!' )
|
|
41
|
+
.optional(),
|
|
42
|
+
};
|
|
@@ -11,7 +11,7 @@ import { CompositionBuilder } from '../../../composition-builder/composition-bui
|
|
|
11
11
|
import { BEST_PRACTICES_URI, STYLE_SCHEMA_URI, WIDGET_SCHEMA_URI } from '../../resources/widgets-schema-resource';
|
|
12
12
|
import { doUpdateElementProperty } from '../../utils/do-update-element-property';
|
|
13
13
|
import { generatePrompt } from './prompt';
|
|
14
|
-
import { inputSchema as schema } from './schema';
|
|
14
|
+
import { inputSchema as schema, outputSchema } from './schema';
|
|
15
15
|
|
|
16
16
|
export const initBuildCompositionsTool = ( reg: MCPRegistryEntry ) => {
|
|
17
17
|
const { addTool } = reg;
|
|
@@ -27,7 +27,7 @@ export const initBuildCompositionsTool = ( reg: MCPRegistryEntry ) => {
|
|
|
27
27
|
{ description: 'Global Variables', uri: 'elementor://global-variables' },
|
|
28
28
|
{ description: 'Styles best practices', uri: BEST_PRACTICES_URI },
|
|
29
29
|
],
|
|
30
|
-
|
|
30
|
+
outputSchema,
|
|
31
31
|
modelPreferences: {
|
|
32
32
|
hints: [ { name: 'claude-sonnet-4-5' } ],
|
|
33
33
|
},
|
|
@@ -116,11 +116,12 @@ export const initBuildCompositionsTool = ( reg: MCPRegistryEntry ) => {
|
|
|
116
116
|
) }\n\n"Missing $$type" errors indicate that the configuration objects are invalid. Try again and apply **ALL** object entries with correct $$type.\nNow that you have these errors, fix them and try again. Errors regarding configuration objects, please check against the PropType schemas`;
|
|
117
117
|
throw new Error( errorText );
|
|
118
118
|
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
119
|
+
return {
|
|
120
|
+
xmlStructure: generatedXML,
|
|
121
|
+
errors: errors?.length
|
|
122
|
+
? errors.map( ( e ) => ( typeof e === 'string' ? e : e.message ) ).join( '\n\n' )
|
|
123
|
+
: undefined,
|
|
124
|
+
llm_instructions: `The composition was built successfully with element IDs embedded in the XML.
|
|
124
125
|
|
|
125
126
|
**CRITICAL NEXT STEPS** (Follow in order):
|
|
126
127
|
1. **Apply Global Classes**: Use "apply-global-class" tool to apply the global classes you created BEFORE building this composition
|
|
@@ -130,10 +131,8 @@ export const initBuildCompositionsTool = ( reg: MCPRegistryEntry ) => {
|
|
|
130
131
|
2. **Fine-tune if needed**: Use "configure-element" tool only for element-specific adjustments that don't warrant global classes
|
|
131
132
|
|
|
132
133
|
Remember: Global classes ensure design consistency and reusability. Don't skip applying them!
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
${ generatedXML }
|
|
136
|
-
`;
|
|
134
|
+
`,
|
|
135
|
+
};
|
|
137
136
|
},
|
|
138
137
|
} );
|
|
139
138
|
};
|