@elementor/editor-canvas 3.35.0-359 → 3.35.0-360

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@elementor/editor-canvas",
3
3
  "description": "Elementor Editor Canvas",
4
- "version": "3.35.0-359",
4
+ "version": "3.35.0-360",
5
5
  "private": false,
6
6
  "author": "Elementor Team",
7
7
  "homepage": "https://elementor.com/",
@@ -37,24 +37,24 @@
37
37
  "react-dom": "^18.3.1"
38
38
  },
39
39
  "dependencies": {
40
- "@elementor/editor": "3.35.0-359",
41
- "@elementor/editor-controls": "3.35.0-359",
42
- "@elementor/editor-documents": "3.35.0-359",
43
- "@elementor/editor-elements": "3.35.0-359",
44
- "@elementor/editor-interactions": "3.35.0-359",
45
- "@elementor/editor-mcp": "3.35.0-359",
46
- "@elementor/editor-notifications": "3.35.0-359",
47
- "@elementor/editor-props": "3.35.0-359",
48
- "@elementor/editor-responsive": "3.35.0-359",
49
- "@elementor/editor-styles": "3.35.0-359",
50
- "@elementor/editor-styles-repository": "3.35.0-359",
51
- "@elementor/editor-ui": "3.35.0-359",
52
- "@elementor/editor-v1-adapters": "3.35.0-359",
53
- "@elementor/schema": "3.35.0-359",
54
- "@elementor/twing": "3.35.0-359",
40
+ "@elementor/editor": "3.35.0-360",
41
+ "@elementor/editor-controls": "3.35.0-360",
42
+ "@elementor/editor-documents": "3.35.0-360",
43
+ "@elementor/editor-elements": "3.35.0-360",
44
+ "@elementor/editor-interactions": "3.35.0-360",
45
+ "@elementor/editor-mcp": "3.35.0-360",
46
+ "@elementor/editor-notifications": "3.35.0-360",
47
+ "@elementor/editor-props": "3.35.0-360",
48
+ "@elementor/editor-responsive": "3.35.0-360",
49
+ "@elementor/editor-styles": "3.35.0-360",
50
+ "@elementor/editor-styles-repository": "3.35.0-360",
51
+ "@elementor/editor-ui": "3.35.0-360",
52
+ "@elementor/editor-v1-adapters": "3.35.0-360",
53
+ "@elementor/schema": "3.35.0-360",
54
+ "@elementor/twing": "3.35.0-360",
55
55
  "@elementor/ui": "1.36.17",
56
- "@elementor/utils": "3.35.0-359",
57
- "@elementor/wp-media": "3.35.0-359",
56
+ "@elementor/utils": "3.35.0-360",
57
+ "@elementor/wp-media": "3.35.0-360",
58
58
  "@floating-ui/react": "^0.27.5",
59
59
  "@wordpress/i18n": "^5.13.0"
60
60
  },
@@ -8,7 +8,6 @@ import {
8
8
  } from '@elementor/editor-v1-adapters';
9
9
  import { screen, waitFor } from '@testing-library/react';
10
10
 
11
- import { hasInlineEditableProperty } from '../../utils/inline-editing-utils';
12
11
  import { ElementsOverlays } from '../elements-overlays';
13
12
  import { CANVAS_WRAPPER_ID } from '../outline-overlay';
14
13
 
@@ -19,7 +18,6 @@ jest.mock( '@elementor/editor-v1-adapters', () => ( {
19
18
  __privateUseIsRouteActive: jest.fn(),
20
19
  isExperimentActive: jest.fn(),
21
20
  } ) );
22
- jest.mock( '../../utils/inline-editing-utils' );
23
21
 
24
22
  describe( '<ElementsOverlays />', () => {
25
23
  beforeEach( () => {
@@ -30,7 +28,6 @@ describe( '<ElementsOverlays />', () => {
30
28
 
31
29
  jest.mocked( useEditMode ).mockReturnValue( 'edit' );
32
30
  jest.mocked( useIsRouteActive ).mockReturnValue( false );
33
- jest.mocked( hasInlineEditableProperty ).mockReturnValue( false );
34
31
  jest.mocked( isExperimentActive ).mockReturnValue( true );
35
32
 
36
33
  jest.mocked( getElements ).mockReturnValue( [
package/src/init.tsx CHANGED
@@ -9,6 +9,7 @@ import { StyleRenderer } from './components/style-renderer';
9
9
  import { initSettingsTransformers } from './init-settings-transformers';
10
10
  import { initStyleTransformers } from './init-style-transformers';
11
11
  import { initLegacyViews } from './legacy/init-legacy-views';
12
+ import { initViewReplacements } from './legacy/replacements/manager';
12
13
  import { initCanvasMcp } from './mcp/canvas-mcp';
13
14
  import { mcpDescription } from './mcp/mcp-description';
14
15
  import { initLinkInLinkPrevention } from './prevent-link-in-link-commands';
@@ -20,6 +21,8 @@ export function init() {
20
21
 
21
22
  initLinkInLinkPrevention();
22
23
 
24
+ initViewReplacements();
25
+
23
26
  initLegacyViews();
24
27
 
25
28
  initSettingsTransformers();
@@ -86,11 +86,11 @@ export function createTemplatedElementView( {
86
86
  this.#abortController = new AbortController();
87
87
 
88
88
  const process = signalizedProcess( this.#abortController.signal )
89
- .then( () => this.#beforeRender() )
89
+ .then( () => this._beforeRender() )
90
90
  .then( () => this._renderTemplate() )
91
91
  .then( () => {
92
92
  this._renderChildren();
93
- this.#afterRender();
93
+ this._afterRender();
94
94
  } );
95
95
 
96
96
  return process.execute();
@@ -136,7 +136,7 @@ export function createTemplatedElementView( {
136
136
  return settings;
137
137
  }
138
138
 
139
- #beforeRender() {
139
+ _beforeRender() {
140
140
  this._ensureViewIsIntact();
141
141
 
142
142
  this._isRendering = true;
@@ -146,7 +146,7 @@ export function createTemplatedElementView( {
146
146
  this.triggerMethod( 'before:render', this );
147
147
  }
148
148
 
149
- #afterRender() {
149
+ _afterRender() {
150
150
  this._isRendering = false;
151
151
  this.isRendered = true;
152
152
 
@@ -2,14 +2,9 @@ import { getWidgetsCache } from '@elementor/editor-elements';
2
2
  import { __privateListenTo, v1ReadyEvent } from '@elementor/editor-v1-adapters';
3
3
 
4
4
  import { createDomRenderer } from '../renderers/create-dom-renderer';
5
- import { shouldRenderInlineEditingView } from '../utils/inline-editing-utils';
6
5
  import { createElementType } from './create-element-type';
7
- import { createInlineEditingElementType } from './create-inline-editing-element-type';
8
- import {
9
- canBeTemplated,
10
- createTemplatedElementType,
11
- type CreateTemplatedElementTypeOptions,
12
- } from './create-templated-element-type';
6
+ import { canBeTemplated, type CreateTemplatedElementTypeOptions } from './create-templated-element-type';
7
+ import { createTemplatedElementTypeWithReplacements } from './replacements/manager';
13
8
  import type { ElementType, LegacyWindow } from './types';
14
9
 
15
10
  type ElementLegacyType = {
@@ -41,9 +36,7 @@ export function initLegacyViews() {
41
36
  if ( !! elementsLegacyTypes[ type ] && canBeTemplated( element ) ) {
42
37
  ElementType = elementsLegacyTypes[ type ]( { type, renderer, element } );
43
38
  } else if ( canBeTemplated( element ) ) {
44
- ElementType = shouldRenderInlineEditingView( type )
45
- ? createInlineEditingElementType( { type, renderer, element } )
46
- : createTemplatedElementType( { type, renderer, element } );
39
+ ElementType = createTemplatedElementTypeWithReplacements( { type, renderer, element } );
47
40
  } else {
48
41
  ElementType = createElementType( type );
49
42
  }
@@ -0,0 +1,35 @@
1
+ import { type ReplacementSettings } from '../types';
2
+
3
+ export default class ReplacementBase {
4
+ protected getSetting: ReplacementSettings[ 'getSetting' ];
5
+ protected setSetting: ReplacementSettings[ 'setSetting' ];
6
+ protected element: ReplacementSettings[ 'element' ];
7
+ protected type: ReplacementSettings[ 'type' ];
8
+ protected id: ReplacementSettings[ 'id' ];
9
+ protected refreshView: ReplacementSettings[ 'refreshView' ];
10
+
11
+ constructor( settings: ReplacementSettings ) {
12
+ this.getSetting = settings.getSetting;
13
+ this.setSetting = settings.setSetting;
14
+ this.element = settings.element;
15
+ this.type = settings.type;
16
+ this.id = settings.id;
17
+ this.refreshView = settings.refreshView;
18
+ }
19
+
20
+ static getTypes(): string[] | null {
21
+ return null;
22
+ }
23
+
24
+ render(): void {}
25
+
26
+ onDestroy(): void {}
27
+
28
+ _beforeRender(): void {}
29
+
30
+ _afterRender(): void {}
31
+
32
+ shouldRenderReplacement(): boolean {
33
+ return true;
34
+ }
35
+ }
@@ -0,0 +1,161 @@
1
+ import * as React from 'react';
2
+ import { createRoot, type Root } from 'react-dom/client';
3
+ import { InlineEditor } from '@elementor/editor-controls';
4
+ import { getElementType } from '@elementor/editor-elements';
5
+ import {
6
+ htmlPropTypeUtil,
7
+ stringPropTypeUtil,
8
+ type StringPropValue,
9
+ type TransformablePropValue,
10
+ } from '@elementor/editor-props';
11
+ import { isExperimentActive } from '@elementor/editor-v1-adapters';
12
+ import { ThemeProvider } from '@elementor/ui';
13
+
14
+ import ReplacementBase from '../base';
15
+ import { getInitialPopoverPosition, INLINE_EDITING_PROPERTY_PER_TYPE } from './inline-editing-utils';
16
+
17
+ const EXPERIMENT_KEY = 'v4-inline-text-editing';
18
+
19
+ export default class InlineEditingReplacement extends ReplacementBase {
20
+ private inlineEditorRoot: Root | null = null;
21
+ private handlerAttached = false;
22
+
23
+ getReplacementKey() {
24
+ return 'inline-editing';
25
+ }
26
+
27
+ static getTypes() {
28
+ return Object.keys( INLINE_EDITING_PROPERTY_PER_TYPE );
29
+ }
30
+
31
+ isEditingModeActive() {
32
+ return !! this.inlineEditorRoot;
33
+ }
34
+
35
+ shouldRenderReplacement() {
36
+ return isExperimentActive( EXPERIMENT_KEY ) && this.isEditingModeActive() && ! this.isValueDynamic();
37
+ }
38
+
39
+ handleRenderInlineEditor = ( event: Event ) => {
40
+ event.stopPropagation();
41
+
42
+ if ( ! this.isValueDynamic() ) {
43
+ this.renderInlineEditor();
44
+ }
45
+ };
46
+
47
+ handleUnmountInlineEditor = ( event: Event ) => {
48
+ event.stopPropagation();
49
+ this.unmountInlineEditor();
50
+ };
51
+
52
+ onDestroy() {
53
+ this.resetInlineEditorRoot();
54
+ }
55
+
56
+ _beforeRender(): void {
57
+ this.resetInlineEditorRoot();
58
+ }
59
+
60
+ _afterRender() {
61
+ if ( ! this.isValueDynamic() && ! this.handlerAttached ) {
62
+ this.element.addEventListener( 'dblclick', this.handleRenderInlineEditor );
63
+ this.handlerAttached = true;
64
+ }
65
+ }
66
+
67
+ resetInlineEditorRoot() {
68
+ this.element.removeEventListener( 'dblclick', this.handleRenderInlineEditor );
69
+ this.handlerAttached = false;
70
+ this.inlineEditorRoot?.unmount?.();
71
+ this.inlineEditorRoot = null;
72
+ }
73
+
74
+ unmountInlineEditor() {
75
+ this.resetInlineEditorRoot();
76
+ this.refreshView();
77
+ }
78
+
79
+ isValueDynamic() {
80
+ const settingKey = this.getInlineEditablePropertyName();
81
+ const propValue = this.getSetting( settingKey ) as TransformablePropValue< string >;
82
+
83
+ return propValue?.$$type === 'dynamic';
84
+ }
85
+
86
+ getInlineEditablePropertyName(): string {
87
+ return INLINE_EDITING_PROPERTY_PER_TYPE[ this.type ] ?? '';
88
+ }
89
+
90
+ getHtmlPropType() {
91
+ const propSchema = getElementType( this.type )?.propsSchema;
92
+ const propertyName = this.getInlineEditablePropertyName();
93
+
94
+ return propSchema?.[ propertyName ] ?? null;
95
+ }
96
+
97
+ getContentValue() {
98
+ const prop = this.getHtmlPropType();
99
+ const defaultValue = ( prop?.default as StringPropValue | null )?.value ?? '';
100
+ const settingKey = this.getInlineEditablePropertyName();
101
+
102
+ return (
103
+ htmlPropTypeUtil.extract( this.getSetting( settingKey ) ?? null ) ??
104
+ htmlPropTypeUtil.extract( prop?.default ?? null ) ??
105
+ defaultValue ??
106
+ ''
107
+ );
108
+ }
109
+
110
+ setContentValue( value: string | null ) {
111
+ const settingKey = this.getInlineEditablePropertyName();
112
+ const valueToSave = value ? htmlPropTypeUtil.create( value ) : null;
113
+
114
+ this.setSetting( settingKey, valueToSave );
115
+ }
116
+
117
+ getExpectedTag() {
118
+ const propsSchema = getElementType( this.type )?.propsSchema;
119
+
120
+ if ( ! propsSchema?.tag ) {
121
+ return null;
122
+ }
123
+
124
+ const tagSettingKey = 'tag';
125
+
126
+ return (
127
+ stringPropTypeUtil.extract( this.getSetting( tagSettingKey ) ?? null ) ??
128
+ stringPropTypeUtil.extract( propsSchema.tag.default ?? null ) ??
129
+ null
130
+ );
131
+ }
132
+
133
+ renderInlineEditor() {
134
+ const propValue = this.getContentValue();
135
+ const classes = ( this.element.children?.[ 0 ]?.classList.toString() ?? '' ) + ' strip-styles';
136
+ const expectedTag = this.getExpectedTag();
137
+
138
+ this.element.innerHTML = '';
139
+
140
+ if ( this.inlineEditorRoot ) {
141
+ this.resetInlineEditorRoot();
142
+ }
143
+
144
+ this.inlineEditorRoot = createRoot( this.element );
145
+
146
+ this.inlineEditorRoot.render(
147
+ <ThemeProvider>
148
+ <InlineEditor
149
+ attributes={ { class: classes } }
150
+ value={ propValue }
151
+ setValue={ this.setContentValue.bind( this ) }
152
+ onBlur={ this.handleUnmountInlineEditor.bind( this ) }
153
+ autofocus
154
+ showToolbar
155
+ getInitialPopoverPosition={ getInitialPopoverPosition }
156
+ expectedTag={ expectedTag }
157
+ />
158
+ </ThemeProvider>
159
+ );
160
+ }
161
+ }
@@ -0,0 +1,31 @@
1
+ import { type V1Element } from '@elementor/editor-elements';
2
+
3
+ import { type LegacyWindow } from '../../types';
4
+
5
+ export const INLINE_EDITING_PROPERTY_PER_TYPE: Record< string, string > = {
6
+ 'e-heading': 'title',
7
+ 'e-paragraph': 'paragraph',
8
+ };
9
+
10
+ export const legacyWindow = window as unknown as LegacyWindow;
11
+
12
+ export const getWidgetType = ( container: V1Element | null ) => {
13
+ return container?.model?.get( 'widgetType' ) ?? container?.model?.get( 'elType' ) ?? null;
14
+ };
15
+
16
+ export const getInitialPopoverPosition = () => {
17
+ const positionFallback = { left: 0, top: 0 };
18
+
19
+ const iFrameElement = legacyWindow?.elementor?.$preview?.get( 0 );
20
+ const iFramePosition = iFrameElement?.getBoundingClientRect() ?? positionFallback;
21
+
22
+ const previewElement = legacyWindow?.elementor?.$previewWrapper?.get( 0 );
23
+ const previewPosition = previewElement
24
+ ? { left: previewElement.scrollLeft, top: previewElement.scrollTop }
25
+ : positionFallback;
26
+
27
+ return {
28
+ left: iFramePosition.left + previewPosition.left,
29
+ top: iFramePosition.top + previewPosition.top,
30
+ };
31
+ };
@@ -0,0 +1,119 @@
1
+ import type { CreateTemplatedElementTypeOptions } from '../create-templated-element-type';
2
+ import { createTemplatedElementView } from '../create-templated-element-type';
3
+ import type { ElementType, ElementView, LegacyWindow, ReplacementSettings } from '../types';
4
+ import type ReplacementBase from './base';
5
+ import InlineEditingReplacement from './inline-editing/inline-editing-elements';
6
+
7
+ type ReplacementConstructor = new ( settings: ReplacementSettings ) => ReplacementBase;
8
+
9
+ const replacements = new Map< string, ReplacementConstructor >();
10
+
11
+ export const initViewReplacements = () => {
12
+ registerReplacement( InlineEditingReplacement );
13
+ };
14
+
15
+ export const registerReplacement = ( replacement: typeof ReplacementBase ) => {
16
+ const types = replacement.getTypes();
17
+
18
+ if ( ! types ) {
19
+ return;
20
+ }
21
+
22
+ types.forEach( ( type ) => {
23
+ replacements.set( type, replacement );
24
+ } );
25
+ };
26
+
27
+ export const getReplacement = ( type: string ) => {
28
+ return replacements.get( type ) ?? null;
29
+ };
30
+
31
+ export const createViewWithReplacements = ( options: CreateTemplatedElementTypeOptions ): typeof ElementView => {
32
+ const TemplatedView = createTemplatedElementView( options );
33
+
34
+ return class extends TemplatedView {
35
+ #replacement: ReplacementBase | null = null;
36
+ #config: ReplacementSettings;
37
+
38
+ constructor( ...args: unknown[] ) {
39
+ super( ...args );
40
+ const settings = this.model.get( 'settings' );
41
+
42
+ this.#config = {
43
+ getSetting: settings.get.bind( settings ),
44
+ setSetting: settings.set.bind( settings ),
45
+ element: this.el,
46
+ type: this?.model?.get( 'widgetType' ) ?? this.container?.model?.get( 'elType' ) ?? null,
47
+ id: this?.model?.get( 'id' ) ?? null,
48
+ refreshView: this.render.bind( this ),
49
+ };
50
+ }
51
+
52
+ refreshView() {
53
+ this.render();
54
+ }
55
+
56
+ render() {
57
+ const config = this.#config;
58
+ const widgetType = config.type;
59
+ const ReplacementClass = widgetType ? getReplacement( widgetType ) : null;
60
+
61
+ if ( ReplacementClass && ! this.#replacement ) {
62
+ this.#replacement = new ReplacementClass( config );
63
+ }
64
+
65
+ if ( ! this.#replacement?.shouldRenderReplacement() ) {
66
+ return TemplatedView.prototype.render.apply( this );
67
+ }
68
+
69
+ this.#replacement.render();
70
+ }
71
+
72
+ onDestroy() {
73
+ if ( this.#replacement ) {
74
+ this.#replacement.onDestroy();
75
+ this.#replacement = null;
76
+ }
77
+
78
+ TemplatedView.prototype.onDestroy.apply( this );
79
+ }
80
+
81
+ _afterRender() {
82
+ if ( this.#replacement ) {
83
+ this.#replacement._afterRender();
84
+ }
85
+
86
+ TemplatedView.prototype._afterRender.apply( this );
87
+ }
88
+
89
+ _beforeRender(): void {
90
+ if ( this.#replacement ) {
91
+ this.#replacement._beforeRender();
92
+ }
93
+
94
+ TemplatedView.prototype._beforeRender.apply( this );
95
+ }
96
+ };
97
+ };
98
+
99
+ export const createTemplatedElementTypeWithReplacements = ( {
100
+ type,
101
+ renderer,
102
+ element,
103
+ }: CreateTemplatedElementTypeOptions ): typeof ElementType => {
104
+ const legacyWindow = window as unknown as LegacyWindow;
105
+
106
+ return class extends legacyWindow.elementor.modules.elements.types.Widget {
107
+ getType() {
108
+ return type;
109
+ }
110
+
111
+ getView() {
112
+ return createViewWithReplacements( {
113
+ type,
114
+ renderer,
115
+ element,
116
+ } );
117
+ }
118
+ };
119
+ };
@@ -1,5 +1,5 @@
1
1
  import { type V1Element } from '@elementor/editor-elements';
2
- import { type Props } from '@elementor/editor-props';
2
+ import { type Props, type PropValue } from '@elementor/editor-props';
3
3
 
4
4
  export type LegacyWindow = Window & {
5
5
  elementor: {
@@ -80,6 +80,10 @@ export declare class ElementView {
80
80
 
81
81
  _renderChildren(): void;
82
82
 
83
+ _beforeRender(): void;
84
+
85
+ _afterRender(): void;
86
+
83
87
  attachBuffer( collectionView: this, buffer: DocumentFragment ): void;
84
88
 
85
89
  triggerMethod( method: string, ...args: unknown[] ): void;
@@ -140,3 +144,12 @@ type ContextMenuGroup = {
140
144
  name: string;
141
145
  actions: unknown[];
142
146
  };
147
+
148
+ export type ReplacementSettings = {
149
+ getSetting: ( key: string ) => unknown;
150
+ setSetting: ( key: string, value: PropValue ) => void;
151
+ type: string;
152
+ id: string;
153
+ element: HTMLElement;
154
+ refreshView: () => void;
155
+ };