@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.
Files changed (59) hide show
  1. package/dist/index.d.mts +133 -10
  2. package/dist/index.d.ts +133 -10
  3. package/dist/index.js +1413 -212
  4. package/dist/index.mjs +1399 -180
  5. package/package.json +18 -14
  6. package/src/__tests__/settings-props-resolver.test.ts +0 -40
  7. package/src/__tests__/styles-prop-resolver.test.ts +13 -0
  8. package/src/components/__tests__/__snapshots__/style-renderer.test.tsx.snap +2 -6
  9. package/src/components/__tests__/elements-overlays.test.tsx +96 -12
  10. package/src/components/__tests__/inline-editor-overlay.test.tsx +245 -0
  11. package/src/components/__tests__/style-renderer.test.tsx +2 -2
  12. package/src/components/elements-overlays.tsx +33 -10
  13. package/src/components/inline-editor-overlay.tsx +79 -0
  14. package/src/components/interactions-renderer.tsx +33 -0
  15. package/src/components/{element-overlay.tsx → outline-overlay.tsx} +8 -7
  16. package/src/components/style-renderer.tsx +2 -4
  17. package/src/hooks/__tests__/use-has-overlapping.test.ts +187 -0
  18. package/src/hooks/use-floating-on-element.ts +11 -8
  19. package/src/hooks/use-has-overlapping.ts +21 -0
  20. package/src/hooks/use-interactions-items.ts +108 -0
  21. package/src/hooks/use-style-items.ts +34 -8
  22. package/src/index.ts +9 -0
  23. package/src/init-settings-transformers.ts +4 -0
  24. package/src/init.tsx +18 -0
  25. package/src/legacy/create-templated-element-type.ts +67 -42
  26. package/src/legacy/init-legacy-views.ts +27 -5
  27. package/src/legacy/types.ts +44 -4
  28. package/src/mcp/canvas-mcp.ts +17 -0
  29. package/src/mcp/mcp-description.ts +40 -0
  30. package/src/mcp/resources/widgets-schema-resource.ts +173 -0
  31. package/src/mcp/tools/build-composition/prompt.ts +128 -0
  32. package/src/mcp/tools/build-composition/schema.ts +31 -0
  33. package/src/mcp/tools/build-composition/tool.ts +163 -0
  34. package/src/mcp/tools/configure-element/prompt.ts +93 -0
  35. package/src/mcp/tools/configure-element/schema.ts +25 -0
  36. package/src/mcp/tools/configure-element/tool.ts +67 -0
  37. package/src/mcp/tools/get-element-config/tool.ts +69 -0
  38. package/src/mcp/utils/do-update-element-property.ts +129 -0
  39. package/src/mcp/utils/generate-available-tags.ts +23 -0
  40. package/src/renderers/__tests__/__snapshots__/create-styles-renderer.test.ts.snap +2 -0
  41. package/src/renderers/__tests__/create-styles-renderer.test.ts +25 -0
  42. package/src/renderers/create-props-resolver.ts +8 -1
  43. package/src/renderers/create-styles-renderer.ts +20 -9
  44. package/src/renderers/errors.ts +6 -0
  45. package/src/sync/drag-element-from-panel.ts +49 -0
  46. package/src/sync/types.ts +32 -1
  47. package/src/transformers/settings/__tests__/attributes-transformer.test.ts +15 -0
  48. package/src/transformers/settings/__tests__/classes-transformer.test.ts +83 -0
  49. package/src/transformers/settings/attributes-transformer.ts +1 -23
  50. package/src/transformers/settings/classes-transformer.ts +21 -21
  51. package/src/transformers/settings/date-time-transformer.ts +12 -0
  52. package/src/transformers/settings/query-transformer.ts +10 -0
  53. package/src/transformers/styles/__tests__/transform-origin-transformer.test.ts +24 -0
  54. package/src/transformers/styles/__tests__/transition-transformer.test.ts +52 -0
  55. package/src/transformers/styles/background-transformer.ts +3 -1
  56. package/src/transformers/styles/transform-origin-transformer.ts +12 -2
  57. package/src/transformers/styles/transition-transformer.ts +34 -4
  58. package/src/types/element-overlay.ts +18 -0
  59. 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 === '' ? '&nbsp;' : 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 function ElementOverlay( { element, isSelected, id }: Props ) {
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 data-e-style-id={ item.id } key={ `${ item.id }-${ item.breakpoint }` }>
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( ( { provider: providerA }, { provider: providerB } ) => providerA.priority - providerB.priority )
62
+ .sort( sortByProviderPriority )
62
63
  .flatMap( ( { items } ) => items )
63
- .sort( ( { breakpoint: breakpointA }, { breakpoint: breakpointB } ) => {
64
- return (
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
  }