@elementor/editor-v1-adapters 0.1.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/LICENSE +674 -0
- package/README.md +5 -0
- package/dist/index.d.ts +81 -0
- package/dist/index.js +334 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +288 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +38 -0
- package/src/__tests__/utils.ts +39 -0
- package/src/dispatchers/__tests__/index.test.ts +126 -0
- package/src/dispatchers/dispatchers.ts +38 -0
- package/src/dispatchers/index.ts +1 -0
- package/src/dispatchers/types.ts +10 -0
- package/src/dispatchers/utils.ts +18 -0
- package/src/hooks/__tests__/test-utils.ts +21 -0
- package/src/hooks/__tests__/use-is-preview-mode.test.ts +47 -0
- package/src/hooks/__tests__/use-is-route-active.test.ts +93 -0
- package/src/hooks/__tests__/use-route-status.test.ts +124 -0
- package/src/hooks/index.ts +4 -0
- package/src/hooks/use-is-preview-mode.ts +10 -0
- package/src/hooks/use-is-route-active.ts +14 -0
- package/src/hooks/use-listen-to.ts +21 -0
- package/src/hooks/use-route-status.ts +32 -0
- package/src/index.ts +4 -0
- package/src/listeners/__tests__/index.test.ts +343 -0
- package/src/listeners/event-creators.ts +48 -0
- package/src/listeners/index.ts +5 -0
- package/src/listeners/is-ready.ts +14 -0
- package/src/listeners/listeners.ts +124 -0
- package/src/listeners/types.ts +45 -0
- package/src/listeners/utils.ts +44 -0
- package/src/readers/__tests__/index.test.ts +71 -0
- package/src/readers/index.ts +13 -0
- package/src/readers/types.ts +16 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is used to store the state of the isReady variable, which is used to determine
|
|
3
|
+
* if the adapter is ready to receive events (editor v1 and v2 are loaded).
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
let ready = false;
|
|
7
|
+
|
|
8
|
+
export function isReady() {
|
|
9
|
+
return ready;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function setReady( value: boolean ) {
|
|
13
|
+
ready = value;
|
|
14
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { normalizeEvent } from './utils';
|
|
2
|
+
import {
|
|
3
|
+
CommandEventDescriptor,
|
|
4
|
+
EventDescriptor,
|
|
5
|
+
ListenerCallback,
|
|
6
|
+
RouteEventDescriptor,
|
|
7
|
+
WindowEventDescriptor,
|
|
8
|
+
} from './types';
|
|
9
|
+
import { isReady, setReady } from './is-ready';
|
|
10
|
+
|
|
11
|
+
const callbacksByEvent = new Map<EventDescriptor['name'], ListenerCallback[]>();
|
|
12
|
+
let abortController = new AbortController();
|
|
13
|
+
|
|
14
|
+
export function listenTo(
|
|
15
|
+
eventDescriptors: EventDescriptor | EventDescriptor[],
|
|
16
|
+
callback: ListenerCallback
|
|
17
|
+
) {
|
|
18
|
+
if ( ! Array.isArray( eventDescriptors ) ) {
|
|
19
|
+
eventDescriptors = [ eventDescriptors ];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// @see https://github.com/typescript-eslint/typescript-eslint/issues/2841
|
|
23
|
+
// eslint-disable-next-line array-callback-return -- Clashes with typescript.
|
|
24
|
+
const cleanups = eventDescriptors.map( ( event ) => {
|
|
25
|
+
const { type, name } = event;
|
|
26
|
+
|
|
27
|
+
switch ( type ) {
|
|
28
|
+
case 'command':
|
|
29
|
+
return registerCommandListener( name, event.state, callback );
|
|
30
|
+
|
|
31
|
+
case 'route':
|
|
32
|
+
return registerRouteListener( name, event.state, callback );
|
|
33
|
+
|
|
34
|
+
case 'window-event':
|
|
35
|
+
return registerWindowEventListener( name, callback );
|
|
36
|
+
}
|
|
37
|
+
} );
|
|
38
|
+
|
|
39
|
+
return () => {
|
|
40
|
+
cleanups.forEach( ( cleanup ) => cleanup() );
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function flushListeners() {
|
|
45
|
+
abortController.abort();
|
|
46
|
+
callbacksByEvent.clear();
|
|
47
|
+
setReady( false );
|
|
48
|
+
|
|
49
|
+
abortController = new AbortController();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function registerCommandListener(
|
|
53
|
+
command: CommandEventDescriptor['name'],
|
|
54
|
+
state: CommandEventDescriptor['state'],
|
|
55
|
+
callback: ListenerCallback
|
|
56
|
+
) {
|
|
57
|
+
return registerWindowEventListener( `elementor/commands/run/${ state }`, ( e ) => {
|
|
58
|
+
const shouldRunCallback = e.type === 'command' && e.command === command;
|
|
59
|
+
|
|
60
|
+
if ( shouldRunCallback ) {
|
|
61
|
+
callback( e );
|
|
62
|
+
}
|
|
63
|
+
} );
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function registerRouteListener(
|
|
67
|
+
route: RouteEventDescriptor['name'],
|
|
68
|
+
state: RouteEventDescriptor['state'],
|
|
69
|
+
callback: ListenerCallback
|
|
70
|
+
) {
|
|
71
|
+
return registerWindowEventListener( `elementor/routes/${ state }`, ( e ) => {
|
|
72
|
+
const shouldRunCallback = e.type === 'route' && e.route.startsWith( route );
|
|
73
|
+
|
|
74
|
+
if ( shouldRunCallback ) {
|
|
75
|
+
callback( e );
|
|
76
|
+
}
|
|
77
|
+
} );
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function registerWindowEventListener( event: WindowEventDescriptor['name'], callback: ListenerCallback ) {
|
|
81
|
+
const isFirstListener = ! callbacksByEvent.has( event );
|
|
82
|
+
|
|
83
|
+
if ( isFirstListener ) {
|
|
84
|
+
callbacksByEvent.set( event, [] );
|
|
85
|
+
|
|
86
|
+
addListener( event );
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
callbacksByEvent.get( event )?.push( callback );
|
|
90
|
+
|
|
91
|
+
return () => {
|
|
92
|
+
const callbacks = callbacksByEvent.get( event );
|
|
93
|
+
|
|
94
|
+
if ( ! callbacks?.length ) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const filtered = callbacks.filter( ( cb ) => cb !== callback );
|
|
99
|
+
|
|
100
|
+
callbacksByEvent.set( event, filtered );
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function addListener( event: EventDescriptor['name'] ) {
|
|
105
|
+
window.addEventListener(
|
|
106
|
+
event,
|
|
107
|
+
makeEventHandler( event ),
|
|
108
|
+
{ signal: abortController.signal }
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function makeEventHandler( event: EventDescriptor['name'] ): EventListener {
|
|
113
|
+
return ( e ) => {
|
|
114
|
+
if ( ! isReady() ) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const normalizedEvent = normalizeEvent( e );
|
|
119
|
+
|
|
120
|
+
callbacksByEvent.get( event )?.forEach( ( callback ) => {
|
|
121
|
+
callback( normalizedEvent );
|
|
122
|
+
} );
|
|
123
|
+
};
|
|
124
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export type ExtendedWindow = Window & {
|
|
2
|
+
__elementorEditorV1LoadingPromise?: Promise<void>;
|
|
3
|
+
};
|
|
4
|
+
|
|
5
|
+
export type CommandEventDescriptor = {
|
|
6
|
+
type: 'command',
|
|
7
|
+
name: string,
|
|
8
|
+
state: 'before' | 'after',
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export type RouteEventDescriptor = {
|
|
12
|
+
type: 'route',
|
|
13
|
+
name: string,
|
|
14
|
+
state: 'open' | 'close',
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export type WindowEventDescriptor = {
|
|
18
|
+
type: 'window-event',
|
|
19
|
+
name: string,
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export type CommandEvent<TArgs extends object = object> = {
|
|
23
|
+
type: CommandEventDescriptor['type'],
|
|
24
|
+
command: string,
|
|
25
|
+
args: TArgs,
|
|
26
|
+
originalEvent: CustomEvent,
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export type RouteEvent = {
|
|
30
|
+
type: RouteEventDescriptor['type'],
|
|
31
|
+
route: string,
|
|
32
|
+
originalEvent: CustomEvent,
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export type WindowEvent = {
|
|
36
|
+
type: WindowEventDescriptor['type'],
|
|
37
|
+
event: string,
|
|
38
|
+
originalEvent: Event,
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export type EventDescriptor = CommandEventDescriptor | WindowEventDescriptor | RouteEventDescriptor;
|
|
42
|
+
|
|
43
|
+
export type ListenerEvent = WindowEvent | CommandEvent | RouteEvent;
|
|
44
|
+
|
|
45
|
+
export type ListenerCallback = ( e: ListenerEvent ) => void;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { ExtendedWindow, ListenerEvent } from './types';
|
|
2
|
+
import { setReady } from './is-ready';
|
|
3
|
+
|
|
4
|
+
export function dispatchReadyEvent() {
|
|
5
|
+
return getV1LoadingPromise().then( () => {
|
|
6
|
+
setReady( true );
|
|
7
|
+
window.dispatchEvent( new CustomEvent( 'elementor/initialized' ) );
|
|
8
|
+
} );
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function getV1LoadingPromise() {
|
|
12
|
+
const v1LoadingPromise = ( window as unknown as ExtendedWindow ).__elementorEditorV1LoadingPromise;
|
|
13
|
+
|
|
14
|
+
if ( ! v1LoadingPromise ) {
|
|
15
|
+
return Promise.reject( 'Elementor Editor V1 is not loaded' );
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return v1LoadingPromise;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function normalizeEvent( e: ListenerEvent['originalEvent'] ): ListenerEvent {
|
|
22
|
+
if ( e instanceof CustomEvent && e.detail?.command ) {
|
|
23
|
+
return {
|
|
24
|
+
type: 'command',
|
|
25
|
+
command: e.detail.command,
|
|
26
|
+
args: e.detail.args,
|
|
27
|
+
originalEvent: e,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if ( e instanceof CustomEvent && e.detail?.route ) {
|
|
32
|
+
return {
|
|
33
|
+
type: 'route',
|
|
34
|
+
route: e.detail.route,
|
|
35
|
+
originalEvent: e,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
type: 'window-event',
|
|
41
|
+
event: e.type,
|
|
42
|
+
originalEvent: e,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { isRouteActive, getCurrentEditMode } from '../';
|
|
2
|
+
|
|
3
|
+
type ExtendedWindow = Window & {
|
|
4
|
+
$e: {
|
|
5
|
+
routes: {
|
|
6
|
+
isPartOf: jest.Mock;
|
|
7
|
+
}
|
|
8
|
+
},
|
|
9
|
+
elementor: {
|
|
10
|
+
channels: {
|
|
11
|
+
dataEditMode: {
|
|
12
|
+
request: jest.Mock;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
describe( '@elementor/editor-v1-adapters - Readers', () => {
|
|
19
|
+
let eIsPartOf: jest.Mock,
|
|
20
|
+
eGetEditMode: jest.Mock;
|
|
21
|
+
|
|
22
|
+
beforeEach( () => {
|
|
23
|
+
const extendedWindow = window as unknown as ExtendedWindow;
|
|
24
|
+
|
|
25
|
+
extendedWindow.$e = {
|
|
26
|
+
routes: {
|
|
27
|
+
isPartOf: jest.fn(),
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
extendedWindow.elementor = {
|
|
32
|
+
channels: {
|
|
33
|
+
dataEditMode: {
|
|
34
|
+
request: jest.fn(),
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
eIsPartOf = extendedWindow.$e.routes.isPartOf;
|
|
40
|
+
eGetEditMode = extendedWindow.elementor.channels.dataEditMode.request;
|
|
41
|
+
} );
|
|
42
|
+
|
|
43
|
+
it( 'should determine if a route is active', () => {
|
|
44
|
+
// Arrange.
|
|
45
|
+
const route = 'test/route';
|
|
46
|
+
|
|
47
|
+
eIsPartOf.mockReturnValue( true );
|
|
48
|
+
|
|
49
|
+
// Act.
|
|
50
|
+
const result = isRouteActive( route );
|
|
51
|
+
|
|
52
|
+
// Assert.
|
|
53
|
+
expect( result ).toEqual( true );
|
|
54
|
+
expect( eIsPartOf ).toHaveBeenCalledTimes( 1 );
|
|
55
|
+
expect( eIsPartOf ).toHaveBeenCalledWith( route );
|
|
56
|
+
} );
|
|
57
|
+
|
|
58
|
+
it( 'should return the current edit mode', () => {
|
|
59
|
+
// Arrange.
|
|
60
|
+
const editMode = 'edit';
|
|
61
|
+
|
|
62
|
+
eGetEditMode.mockReturnValue( editMode );
|
|
63
|
+
|
|
64
|
+
// Act.
|
|
65
|
+
const result = getCurrentEditMode();
|
|
66
|
+
|
|
67
|
+
// Assert.
|
|
68
|
+
expect( result ).toEqual( editMode );
|
|
69
|
+
expect( eGetEditMode ).toHaveBeenCalledTimes( 1 );
|
|
70
|
+
} );
|
|
71
|
+
} );
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { ExtendedWindow } from './types';
|
|
2
|
+
|
|
3
|
+
export function isRouteActive( route: string ) {
|
|
4
|
+
const extendedWindow = window as unknown as ExtendedWindow;
|
|
5
|
+
|
|
6
|
+
return !! extendedWindow.$e?.routes?.isPartOf( route );
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function getCurrentEditMode() {
|
|
10
|
+
const extendedWindow = window as unknown as ExtendedWindow;
|
|
11
|
+
|
|
12
|
+
return extendedWindow.elementor?.channels?.dataEditMode?.request?.( 'activeMode' );
|
|
13
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export type EditMode = 'edit' | 'preview' | 'picker';
|
|
2
|
+
|
|
3
|
+
export type ExtendedWindow = Window & {
|
|
4
|
+
$e: {
|
|
5
|
+
routes: {
|
|
6
|
+
isPartOf: ( route: string ) => boolean;
|
|
7
|
+
}
|
|
8
|
+
},
|
|
9
|
+
elementor: {
|
|
10
|
+
channels: {
|
|
11
|
+
dataEditMode: {
|
|
12
|
+
request: ( key: 'activeMode' ) => EditMode;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|