@elementor/editor-canvas 0.14.0 → 0.15.0
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/.turbo/turbo-build.log +8 -8
- package/CHANGELOG.md +16 -0
- package/dist/index.js +146 -3
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +146 -3
- package/dist/index.mjs.map +1 -1
- package/package.json +5 -5
- package/src/components/__tests__/elements-overlays.test.tsx +4 -4
- package/src/legacy/__tests__/signalized-process.test.ts +80 -0
- package/src/legacy/create-element-type.ts +2 -2
- package/src/legacy/create-templated-element-type.ts +131 -0
- package/src/legacy/init-legacy-views.ts +7 -1
- package/src/legacy/signalized-process.ts +35 -0
- package/src/legacy/types.ts +27 -3
- package/src/renderers/create-dom-renderer.ts +1 -1
- package/src/renderers/create-props-resolver.ts +3 -1
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { signalizedProcess } from '../signalized-process';
|
|
2
|
+
|
|
3
|
+
describe( 'signalizedProcess', () => {
|
|
4
|
+
it( 'should run the steps in order', async () => {
|
|
5
|
+
// Arrange.
|
|
6
|
+
const abortController = new AbortController();
|
|
7
|
+
|
|
8
|
+
let value = '';
|
|
9
|
+
|
|
10
|
+
const process = signalizedProcess( abortController.signal )
|
|
11
|
+
.then( () => {
|
|
12
|
+
value += 'a';
|
|
13
|
+
|
|
14
|
+
return Promise.resolve( 'b' );
|
|
15
|
+
} )
|
|
16
|
+
.then( ( v ) => {
|
|
17
|
+
value += v;
|
|
18
|
+
|
|
19
|
+
return Promise.resolve( 'c' );
|
|
20
|
+
} )
|
|
21
|
+
.then( ( v ) => {
|
|
22
|
+
value += v;
|
|
23
|
+
} );
|
|
24
|
+
|
|
25
|
+
// Act.
|
|
26
|
+
await process.execute();
|
|
27
|
+
|
|
28
|
+
// Assert.
|
|
29
|
+
expect( value ).toBe( 'abc' );
|
|
30
|
+
} );
|
|
31
|
+
|
|
32
|
+
it( 'should not execute anything when the signal is aborted before the process has started', async () => {
|
|
33
|
+
// Arrange.
|
|
34
|
+
const abortController = new AbortController();
|
|
35
|
+
|
|
36
|
+
let value = 'initial';
|
|
37
|
+
|
|
38
|
+
const process = signalizedProcess( abortController.signal ).then( () => {
|
|
39
|
+
value = 'updated';
|
|
40
|
+
} );
|
|
41
|
+
|
|
42
|
+
// Act.
|
|
43
|
+
abortController.abort();
|
|
44
|
+
|
|
45
|
+
await process.execute();
|
|
46
|
+
|
|
47
|
+
// Assert.
|
|
48
|
+
expect( value ).toBe( 'initial' );
|
|
49
|
+
} );
|
|
50
|
+
|
|
51
|
+
it( 'should abort all queued steps when the signal is aborted', async () => {
|
|
52
|
+
// Arrange.
|
|
53
|
+
const abortController = new AbortController();
|
|
54
|
+
|
|
55
|
+
let value = '';
|
|
56
|
+
|
|
57
|
+
const process = signalizedProcess( abortController.signal )
|
|
58
|
+
.then( () => {
|
|
59
|
+
value += 'a';
|
|
60
|
+
|
|
61
|
+
return Promise.resolve( 'b' );
|
|
62
|
+
} )
|
|
63
|
+
.then( ( v ) => {
|
|
64
|
+
value += v;
|
|
65
|
+
|
|
66
|
+
abortController.abort();
|
|
67
|
+
|
|
68
|
+
return Promise.resolve( 'c' );
|
|
69
|
+
} )
|
|
70
|
+
.then( ( v ) => {
|
|
71
|
+
value += v;
|
|
72
|
+
} );
|
|
73
|
+
|
|
74
|
+
// Act.
|
|
75
|
+
await process.execute();
|
|
76
|
+
|
|
77
|
+
// Assert.
|
|
78
|
+
expect( value ).toBe( 'ab' );
|
|
79
|
+
} );
|
|
80
|
+
} );
|
|
@@ -13,12 +13,12 @@ export function createElementType( type: string ): typeof ElementType {
|
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
getView() {
|
|
16
|
-
return
|
|
16
|
+
return createElementViewClassDeclaration();
|
|
17
17
|
}
|
|
18
18
|
};
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
function
|
|
21
|
+
export function createElementViewClassDeclaration(): typeof ElementView {
|
|
22
22
|
const legacyWindow = window as unknown as LegacyWindow;
|
|
23
23
|
|
|
24
24
|
return class extends legacyWindow.elementor.modules.elements.views.Widget {
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import type { V1ElementConfig } from '@elementor/editor-elements';
|
|
2
|
+
|
|
3
|
+
import { type DomRenderer } from '../renderers/create-dom-renderer';
|
|
4
|
+
import { createPropsResolver, type PropsResolver } from '../renderers/create-props-resolver';
|
|
5
|
+
import { settingsTransformersRegistry } from '../settings-transformers-registry';
|
|
6
|
+
import { createElementViewClassDeclaration } from './create-element-type';
|
|
7
|
+
import { signalizedProcess } from './signalized-process';
|
|
8
|
+
import { type ElementType, type ElementView, type LegacyWindow } from './types';
|
|
9
|
+
|
|
10
|
+
type CreateTypeOptions = {
|
|
11
|
+
type: string;
|
|
12
|
+
renderer: DomRenderer;
|
|
13
|
+
element: TemplatedElementConfig;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
type TemplatedElementConfig = Required<
|
|
17
|
+
Pick< V1ElementConfig, 'twig_templates' | 'twig_main_template' | 'atomic_props_schema' | 'base_styles_dictionary' >
|
|
18
|
+
>;
|
|
19
|
+
|
|
20
|
+
export function createTemplatedElementType( { type, renderer, element }: CreateTypeOptions ): typeof ElementType {
|
|
21
|
+
const legacyWindow = window as unknown as LegacyWindow;
|
|
22
|
+
|
|
23
|
+
Object.entries( element.twig_templates ).forEach( ( [ key, template ] ) => {
|
|
24
|
+
renderer.register( key, template );
|
|
25
|
+
} );
|
|
26
|
+
|
|
27
|
+
const propsResolver = createPropsResolver( {
|
|
28
|
+
transformers: settingsTransformersRegistry,
|
|
29
|
+
schema: element.atomic_props_schema,
|
|
30
|
+
} );
|
|
31
|
+
|
|
32
|
+
return class extends legacyWindow.elementor.modules.elements.types.Widget {
|
|
33
|
+
getType() {
|
|
34
|
+
return type;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
getView() {
|
|
38
|
+
return createTemplatedElementViewClassDeclaration( {
|
|
39
|
+
type,
|
|
40
|
+
renderer,
|
|
41
|
+
propsResolver,
|
|
42
|
+
baseStylesDictionary: element.base_styles_dictionary,
|
|
43
|
+
templateKey: element.twig_main_template,
|
|
44
|
+
} );
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function canBeTemplated( element: Partial< TemplatedElementConfig > ): element is TemplatedElementConfig {
|
|
50
|
+
return !! (
|
|
51
|
+
element.atomic_props_schema &&
|
|
52
|
+
element.twig_templates &&
|
|
53
|
+
element.twig_main_template &&
|
|
54
|
+
element.base_styles_dictionary
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
type CreateViewOptions = {
|
|
59
|
+
type: string;
|
|
60
|
+
renderer: DomRenderer;
|
|
61
|
+
propsResolver: PropsResolver;
|
|
62
|
+
templateKey: string;
|
|
63
|
+
baseStylesDictionary: Record< string, string >;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
function createTemplatedElementViewClassDeclaration( {
|
|
67
|
+
type,
|
|
68
|
+
renderer,
|
|
69
|
+
propsResolver: resolveProps,
|
|
70
|
+
templateKey,
|
|
71
|
+
baseStylesDictionary,
|
|
72
|
+
}: CreateViewOptions ): typeof ElementView {
|
|
73
|
+
const BaseView = createElementViewClassDeclaration();
|
|
74
|
+
|
|
75
|
+
return class extends BaseView {
|
|
76
|
+
#abortController: AbortController | null = null;
|
|
77
|
+
|
|
78
|
+
getTemplateType() {
|
|
79
|
+
return 'twig';
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
renderOnChange() {
|
|
83
|
+
this.render();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Overriding Marionette original render method to inject our renderer.
|
|
87
|
+
async _renderTemplate() {
|
|
88
|
+
this.#beforeRenderTemplate();
|
|
89
|
+
|
|
90
|
+
this.#abortController?.abort();
|
|
91
|
+
this.#abortController = new AbortController();
|
|
92
|
+
|
|
93
|
+
const process = signalizedProcess( this.#abortController.signal )
|
|
94
|
+
.then( ( _, signal ) => {
|
|
95
|
+
const settings = this.model.get( 'settings' ).toJSON();
|
|
96
|
+
|
|
97
|
+
return resolveProps( {
|
|
98
|
+
props: settings,
|
|
99
|
+
signal,
|
|
100
|
+
} );
|
|
101
|
+
} )
|
|
102
|
+
.then( ( resolvedSettings ) => {
|
|
103
|
+
// Same as the Backend.
|
|
104
|
+
const context = {
|
|
105
|
+
id: this.model.get( 'id' ),
|
|
106
|
+
type,
|
|
107
|
+
settings: resolvedSettings,
|
|
108
|
+
base_styles: baseStylesDictionary,
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
return renderer.render( templateKey, context );
|
|
112
|
+
} )
|
|
113
|
+
.then( ( html ) => this.$el.html( html ) );
|
|
114
|
+
|
|
115
|
+
await process.execute();
|
|
116
|
+
|
|
117
|
+
this.#afterRenderTemplate();
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Emulating the original Marionette behavior.
|
|
121
|
+
#beforeRenderTemplate() {
|
|
122
|
+
this.triggerMethod( 'before:render:template' );
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
#afterRenderTemplate() {
|
|
126
|
+
this.bindUIElements();
|
|
127
|
+
|
|
128
|
+
this.triggerMethod( 'render:template' );
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { getWidgetsCache } from '@elementor/editor-elements';
|
|
2
2
|
import { __privateListenTo, v1ReadyEvent } from '@elementor/editor-v1-adapters';
|
|
3
3
|
|
|
4
|
+
import { createDomRenderer } from '../renderers/create-dom-renderer';
|
|
4
5
|
import { createElementType } from './create-element-type';
|
|
6
|
+
import { canBeTemplated, createTemplatedElementType } from './create-templated-element-type';
|
|
5
7
|
import type { LegacyWindow } from './types';
|
|
6
8
|
|
|
7
9
|
export function initLegacyViews() {
|
|
@@ -9,12 +11,16 @@ export function initLegacyViews() {
|
|
|
9
11
|
const config = getWidgetsCache() ?? {};
|
|
10
12
|
const legacyWindow = window as unknown as LegacyWindow;
|
|
11
13
|
|
|
14
|
+
const renderer = createDomRenderer();
|
|
15
|
+
|
|
12
16
|
Object.entries( config ).forEach( ( [ type, element ] ) => {
|
|
13
17
|
if ( ! element.atomic ) {
|
|
14
18
|
return;
|
|
15
19
|
}
|
|
16
20
|
|
|
17
|
-
const ElementType =
|
|
21
|
+
const ElementType = canBeTemplated( element )
|
|
22
|
+
? createTemplatedElementType( { type, renderer, element } )
|
|
23
|
+
: createElementType( type );
|
|
18
24
|
|
|
19
25
|
legacyWindow.elementor.elementsManager.registerElementType( new ElementType() );
|
|
20
26
|
} );
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2
|
+
type AnyFn = ( ...args: any[] ) => any;
|
|
3
|
+
|
|
4
|
+
type SignalizedProcess< TNextArg = never > = {
|
|
5
|
+
then: < TReturn >(
|
|
6
|
+
cb: ( arg: TNextArg, signal: AbortSignal ) => TReturn
|
|
7
|
+
) => SignalizedProcess< Awaited< TReturn > >;
|
|
8
|
+
|
|
9
|
+
execute: () => Promise< void >;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export function signalizedProcess< TNextArg = never >(
|
|
13
|
+
signal: AbortSignal,
|
|
14
|
+
steps: AnyFn[] = []
|
|
15
|
+
): SignalizedProcess< TNextArg > {
|
|
16
|
+
return {
|
|
17
|
+
then: ( cb ) => {
|
|
18
|
+
steps.push( cb );
|
|
19
|
+
|
|
20
|
+
return signalizedProcess( signal, steps );
|
|
21
|
+
},
|
|
22
|
+
|
|
23
|
+
execute: async () => {
|
|
24
|
+
let lastResult: TNextArg | undefined;
|
|
25
|
+
|
|
26
|
+
for ( const step of steps ) {
|
|
27
|
+
if ( signal.aborted ) {
|
|
28
|
+
break;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
lastResult = await step( lastResult, signal );
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
}
|
package/src/legacy/types.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { type Props } from '@elementor/editor-props';
|
|
2
|
+
|
|
1
3
|
export type LegacyWindow = Window & {
|
|
2
4
|
elementor: {
|
|
3
5
|
modules: {
|
|
@@ -25,7 +27,7 @@ export declare class ElementType {
|
|
|
25
27
|
export declare class ElementView {
|
|
26
28
|
$el: JQueryElement;
|
|
27
29
|
|
|
28
|
-
model: BackboneModel
|
|
30
|
+
model: BackboneModel< ElementModel >;
|
|
29
31
|
|
|
30
32
|
onRender( ...args: unknown[] ): void;
|
|
31
33
|
|
|
@@ -40,18 +42,40 @@ export declare class ElementView {
|
|
|
40
42
|
getHandlesOverlay(): JQueryElement | null;
|
|
41
43
|
|
|
42
44
|
getContextMenuGroups(): ContextMenuGroup[];
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Templated view methods:
|
|
48
|
+
*/
|
|
49
|
+
getTemplateType(): string;
|
|
50
|
+
|
|
51
|
+
renderOnChange(): void;
|
|
52
|
+
|
|
53
|
+
render(): void;
|
|
54
|
+
|
|
55
|
+
_renderTemplate(): void;
|
|
56
|
+
|
|
57
|
+
triggerMethod( method: string ): void;
|
|
58
|
+
|
|
59
|
+
bindUIElements(): void;
|
|
43
60
|
}
|
|
44
61
|
|
|
45
62
|
type JQueryElement = {
|
|
46
63
|
find: ( selector: string ) => JQueryElement;
|
|
64
|
+
html: ( html: string ) => void;
|
|
47
65
|
};
|
|
48
66
|
|
|
49
|
-
type BackboneModel = {
|
|
67
|
+
type BackboneModel< Model extends object > = {
|
|
50
68
|
get: < T extends keyof Model >( key: T ) => Model[ T ];
|
|
69
|
+
toJSON: () => ToJSON< Model >;
|
|
51
70
|
};
|
|
52
71
|
|
|
53
|
-
type
|
|
72
|
+
type ElementModel = {
|
|
54
73
|
id: string;
|
|
74
|
+
settings: BackboneModel< Props >;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
type ToJSON< T > = {
|
|
78
|
+
[ K in keyof T ]: T[ K ] extends BackboneModel< infer M > ? ToJSON< M > : T[ K ];
|
|
55
79
|
};
|
|
56
80
|
|
|
57
81
|
type ContextMenuGroup = {
|
|
@@ -30,12 +30,14 @@ type TransformArgs = {
|
|
|
30
30
|
depth?: number;
|
|
31
31
|
};
|
|
32
32
|
|
|
33
|
+
type ResolvedProps = Record< string, unknown >;
|
|
34
|
+
|
|
33
35
|
export type PropsResolver = ReturnType< typeof createPropsResolver >;
|
|
34
36
|
|
|
35
37
|
const TRANSFORM_DEPTH_LIMIT = 3;
|
|
36
38
|
|
|
37
39
|
export function createPropsResolver( { transformers, schema: initialSchema, onPropResolve }: CreatePropResolverArgs ) {
|
|
38
|
-
async function resolve( { props, schema, signal }: ResolveArgs ) {
|
|
40
|
+
async function resolve( { props, schema, signal }: ResolveArgs ): Promise< ResolvedProps > {
|
|
39
41
|
schema = schema ?? initialSchema;
|
|
40
42
|
|
|
41
43
|
const promises = Promise.all(
|