@elementor/editor-panels 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/CHANGELOG.md +11 -0
- package/LICENSE +674 -0
- package/README.md +76 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.js +351 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +318 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +48 -0
- package/src/__tests__/api.test.tsx +145 -0
- package/src/__tests__/sync.test.tsx +129 -0
- package/src/api.ts +67 -0
- package/src/components/external/index.ts +4 -0
- package/src/components/external/panel-body.tsx +16 -0
- package/src/components/external/panel-header-title.tsx +21 -0
- package/src/components/external/panel-header.tsx +20 -0
- package/src/components/external/panel.tsx +24 -0
- package/src/components/internal/panels.tsx +18 -0
- package/src/components/internal/portal.tsx +18 -0
- package/src/hooks/use-open-panel-injection.ts +14 -0
- package/src/index.ts +6 -0
- package/src/init.ts +13 -0
- package/src/location.ts +6 -0
- package/src/store/index.ts +2 -0
- package/src/store/selectors.ts +6 -0
- package/src/store/slice.ts +22 -0
- package/src/sync.ts +141 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
// src/init.ts
|
|
2
|
+
import { injectIntoTop } from "@elementor/editor";
|
|
3
|
+
import { registerSlice } from "@elementor/store";
|
|
4
|
+
|
|
5
|
+
// src/components/internal/panels.tsx
|
|
6
|
+
import * as React2 from "react";
|
|
7
|
+
|
|
8
|
+
// src/hooks/use-open-panel-injection.ts
|
|
9
|
+
import { useSelector } from "@elementor/store";
|
|
10
|
+
|
|
11
|
+
// src/location.ts
|
|
12
|
+
import { createLocation } from "@elementor/locations";
|
|
13
|
+
var {
|
|
14
|
+
inject: injectIntoPanels,
|
|
15
|
+
useInjections: usePanelsInjections
|
|
16
|
+
} = createLocation();
|
|
17
|
+
|
|
18
|
+
// src/store/selectors.ts
|
|
19
|
+
var selectOpenId = (state) => state.panels.openId;
|
|
20
|
+
|
|
21
|
+
// src/store/slice.ts
|
|
22
|
+
import { createSlice } from "@elementor/store";
|
|
23
|
+
var initialState = {
|
|
24
|
+
openId: null
|
|
25
|
+
};
|
|
26
|
+
var slice_default = createSlice({
|
|
27
|
+
name: "panels",
|
|
28
|
+
initialState,
|
|
29
|
+
reducers: {
|
|
30
|
+
open(state, action) {
|
|
31
|
+
state.openId = action.payload;
|
|
32
|
+
},
|
|
33
|
+
close(state, action) {
|
|
34
|
+
if (!action.payload || state.openId === action.payload) {
|
|
35
|
+
state.openId = null;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// src/hooks/use-open-panel-injection.ts
|
|
42
|
+
import { useMemo } from "react";
|
|
43
|
+
function useOpenPanelInjection() {
|
|
44
|
+
const injections = usePanelsInjections();
|
|
45
|
+
const openId = useSelector(selectOpenId);
|
|
46
|
+
return useMemo(
|
|
47
|
+
() => injections.find((injection) => openId === injection.id),
|
|
48
|
+
[injections, openId]
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// src/components/internal/portal.tsx
|
|
53
|
+
import * as React from "react";
|
|
54
|
+
import { Portal as BasePortal } from "@elementor/ui";
|
|
55
|
+
import { useRef } from "react";
|
|
56
|
+
|
|
57
|
+
// src/sync.ts
|
|
58
|
+
import {
|
|
59
|
+
openRoute,
|
|
60
|
+
listenTo,
|
|
61
|
+
routeCloseEvent,
|
|
62
|
+
useRouteStatus,
|
|
63
|
+
routeOpenEvent,
|
|
64
|
+
windowEvent,
|
|
65
|
+
registerRoute,
|
|
66
|
+
isRouteActive
|
|
67
|
+
} from "@elementor/editor-v1-adapters";
|
|
68
|
+
import { dispatch, getState, subscribe as originalSubscribe } from "@elementor/store";
|
|
69
|
+
var V2_PANEL = "panel/v2";
|
|
70
|
+
function getPortalContainer() {
|
|
71
|
+
return document.querySelector("#elementor-panel-inner");
|
|
72
|
+
}
|
|
73
|
+
function useV1PanelStatus() {
|
|
74
|
+
return useRouteStatus(V2_PANEL, {
|
|
75
|
+
blockOnKitRoutes: true,
|
|
76
|
+
blockOnPreviewMode: true
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
function sync() {
|
|
80
|
+
listenTo(
|
|
81
|
+
windowEvent("elementor/panel/init"),
|
|
82
|
+
() => registerRoute(V2_PANEL)
|
|
83
|
+
);
|
|
84
|
+
listenTo(
|
|
85
|
+
routeOpenEvent(V2_PANEL),
|
|
86
|
+
() => {
|
|
87
|
+
getV1PanelElements().forEach((el) => {
|
|
88
|
+
el.setAttribute("hidden", "hidden");
|
|
89
|
+
el.setAttribute("aria-hidden", "true");
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
);
|
|
93
|
+
listenTo(
|
|
94
|
+
routeCloseEvent(V2_PANEL),
|
|
95
|
+
() => selectOpenId(getState()) && dispatch(slice_default.actions.close())
|
|
96
|
+
);
|
|
97
|
+
listenTo(
|
|
98
|
+
routeCloseEvent(V2_PANEL),
|
|
99
|
+
() => {
|
|
100
|
+
getV1PanelElements().forEach((el) => {
|
|
101
|
+
el.removeAttribute("hidden");
|
|
102
|
+
el.removeAttribute("aria-hidden");
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
);
|
|
106
|
+
listenTo(
|
|
107
|
+
windowEvent("elementor/panel/init"),
|
|
108
|
+
() => subscribe({
|
|
109
|
+
on: (state) => selectOpenId(state),
|
|
110
|
+
when: ({ prev, current }) => !!(!prev && current),
|
|
111
|
+
// is panel opened
|
|
112
|
+
callback: () => openRoute(V2_PANEL)
|
|
113
|
+
})
|
|
114
|
+
);
|
|
115
|
+
listenTo(
|
|
116
|
+
windowEvent("elementor/panel/init"),
|
|
117
|
+
() => subscribe({
|
|
118
|
+
on: (state) => selectOpenId(state),
|
|
119
|
+
when: ({ prev, current }) => !!(!current && prev),
|
|
120
|
+
// is panel closed
|
|
121
|
+
callback: () => isRouteActive(V2_PANEL) && openRoute(getDefaultRoute())
|
|
122
|
+
})
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
function getV1PanelElements() {
|
|
126
|
+
const v1ElementsSelector = [
|
|
127
|
+
"#elementor-panel-header-wrapper",
|
|
128
|
+
"#elementor-panel-content-wrapper",
|
|
129
|
+
"#elementor-panel-state-loading",
|
|
130
|
+
"#elementor-panel-footer"
|
|
131
|
+
].join(", ");
|
|
132
|
+
return document.querySelectorAll(v1ElementsSelector);
|
|
133
|
+
}
|
|
134
|
+
function getDefaultRoute() {
|
|
135
|
+
const defaultRoute = window?.elementor?.documents?.getCurrent?.()?.config?.panel?.default_route;
|
|
136
|
+
return defaultRoute || "panel/elements/categories";
|
|
137
|
+
}
|
|
138
|
+
function subscribe({
|
|
139
|
+
on,
|
|
140
|
+
when,
|
|
141
|
+
callback
|
|
142
|
+
}) {
|
|
143
|
+
let prev;
|
|
144
|
+
originalSubscribe(() => {
|
|
145
|
+
const current = on(getState());
|
|
146
|
+
if (when({ prev, current })) {
|
|
147
|
+
callback({ prev, current });
|
|
148
|
+
}
|
|
149
|
+
prev = current;
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// src/components/internal/portal.tsx
|
|
154
|
+
function Portal(props) {
|
|
155
|
+
const containerRef = useRef(getPortalContainer);
|
|
156
|
+
if (!containerRef.current) {
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
return /* @__PURE__ */ React.createElement(BasePortal, { container: containerRef.current, ...props });
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// src/components/internal/panels.tsx
|
|
163
|
+
function Panels() {
|
|
164
|
+
const openPanel = useOpenPanelInjection();
|
|
165
|
+
const Component = openPanel?.filler ?? null;
|
|
166
|
+
if (!Component) {
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
return /* @__PURE__ */ React2.createElement(Portal, null, /* @__PURE__ */ React2.createElement(Component, null));
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// src/init.ts
|
|
173
|
+
function init() {
|
|
174
|
+
sync();
|
|
175
|
+
registerSlice(slice_default);
|
|
176
|
+
injectIntoTop({ id: "panels", filler: Panels });
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// src/api.ts
|
|
180
|
+
import { useSelector as useSelector2, useDispatch } from "@elementor/store";
|
|
181
|
+
function createPanel({ id, component }) {
|
|
182
|
+
const usePanelStatus = createUseStatus(id);
|
|
183
|
+
const usePanelActions = createUseActions(id, usePanelStatus);
|
|
184
|
+
return {
|
|
185
|
+
panel: {
|
|
186
|
+
id,
|
|
187
|
+
component
|
|
188
|
+
},
|
|
189
|
+
usePanelStatus,
|
|
190
|
+
usePanelActions
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
function registerPanel({ id, component }) {
|
|
194
|
+
injectIntoPanels({
|
|
195
|
+
id,
|
|
196
|
+
filler: component
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
function createUseStatus(id) {
|
|
200
|
+
return () => {
|
|
201
|
+
const openPanelId = useSelector2(selectOpenId);
|
|
202
|
+
const v1PanelStatus = useV1PanelStatus();
|
|
203
|
+
return {
|
|
204
|
+
isOpen: openPanelId === id && v1PanelStatus.isActive,
|
|
205
|
+
isBlocked: v1PanelStatus.isBlocked
|
|
206
|
+
};
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
function createUseActions(id, useStatus) {
|
|
210
|
+
return () => {
|
|
211
|
+
const dispatch2 = useDispatch();
|
|
212
|
+
const { isBlocked } = useStatus();
|
|
213
|
+
return {
|
|
214
|
+
open: async () => {
|
|
215
|
+
if (isBlocked) {
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
dispatch2(slice_default.actions.open(id));
|
|
219
|
+
},
|
|
220
|
+
close: async () => {
|
|
221
|
+
if (isBlocked) {
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
dispatch2(slice_default.actions.close(id));
|
|
225
|
+
}
|
|
226
|
+
};
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// src/components/external/panel.tsx
|
|
231
|
+
import * as React3 from "react";
|
|
232
|
+
import { Drawer } from "@elementor/ui";
|
|
233
|
+
function Panel({ children, sx, ...props }) {
|
|
234
|
+
return /* @__PURE__ */ React3.createElement(
|
|
235
|
+
Drawer,
|
|
236
|
+
{
|
|
237
|
+
open: true,
|
|
238
|
+
variant: "persistent",
|
|
239
|
+
anchor: "left",
|
|
240
|
+
PaperProps: {
|
|
241
|
+
sx: {
|
|
242
|
+
position: "relative",
|
|
243
|
+
width: "100%",
|
|
244
|
+
bgcolor: "background.default",
|
|
245
|
+
border: "none"
|
|
246
|
+
}
|
|
247
|
+
},
|
|
248
|
+
sx: { height: "100%", ...sx },
|
|
249
|
+
...props
|
|
250
|
+
},
|
|
251
|
+
children
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// src/components/external/panel-header.tsx
|
|
256
|
+
import * as React4 from "react";
|
|
257
|
+
import { Box, Divider, styled } from "@elementor/ui";
|
|
258
|
+
var Header = styled(Box)(({ theme }) => ({
|
|
259
|
+
height: theme?.sizing?.["600"] || "48px",
|
|
260
|
+
display: "flex",
|
|
261
|
+
alignItems: "center",
|
|
262
|
+
justifyContent: "center"
|
|
263
|
+
}));
|
|
264
|
+
function PanelHeader({ children, ...props }) {
|
|
265
|
+
return /* @__PURE__ */ React4.createElement(React4.Fragment, null, /* @__PURE__ */ React4.createElement(Header, { component: "header", ...props }, children), /* @__PURE__ */ React4.createElement(Divider, null));
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// src/components/external/panel-header-title.tsx
|
|
269
|
+
import * as React5 from "react";
|
|
270
|
+
import { Typography, styled as styled2 } from "@elementor/ui";
|
|
271
|
+
var Title = styled2(Typography)(({ theme }) => ({
|
|
272
|
+
"&.MuiTypography-root": {
|
|
273
|
+
fontWeight: "bold",
|
|
274
|
+
fontSize: theme.typography.body1.fontSize
|
|
275
|
+
}
|
|
276
|
+
}));
|
|
277
|
+
function PanelHeaderTitle({ children, ...props }) {
|
|
278
|
+
return /* @__PURE__ */ React5.createElement(
|
|
279
|
+
Title,
|
|
280
|
+
{
|
|
281
|
+
component: "h2",
|
|
282
|
+
variant: "body1",
|
|
283
|
+
...props
|
|
284
|
+
},
|
|
285
|
+
children
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// src/components/external/panel-body.tsx
|
|
290
|
+
import * as React6 from "react";
|
|
291
|
+
import { Box as Box2 } from "@elementor/ui";
|
|
292
|
+
function PanelBody({ children, sx, ...props }) {
|
|
293
|
+
return /* @__PURE__ */ React6.createElement(
|
|
294
|
+
Box2,
|
|
295
|
+
{
|
|
296
|
+
component: "main",
|
|
297
|
+
sx: {
|
|
298
|
+
overflowY: "auto",
|
|
299
|
+
height: "100%",
|
|
300
|
+
...sx
|
|
301
|
+
},
|
|
302
|
+
...props
|
|
303
|
+
},
|
|
304
|
+
children
|
|
305
|
+
);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// src/index.ts
|
|
309
|
+
init();
|
|
310
|
+
export {
|
|
311
|
+
Panel,
|
|
312
|
+
PanelBody,
|
|
313
|
+
PanelHeader,
|
|
314
|
+
PanelHeaderTitle,
|
|
315
|
+
createPanel,
|
|
316
|
+
registerPanel
|
|
317
|
+
};
|
|
318
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/init.ts","../src/components/internal/panels.tsx","../src/hooks/use-open-panel-injection.ts","../src/location.ts","../src/store/selectors.ts","../src/store/slice.ts","../src/components/internal/portal.tsx","../src/sync.ts","../src/api.ts","../src/components/external/panel.tsx","../src/components/external/panel-header.tsx","../src/components/external/panel-header-title.tsx","../src/components/external/panel-body.tsx","../src/index.ts"],"sourcesContent":["import { injectIntoTop } from '@elementor/editor';\nimport { registerSlice } from '@elementor/store';\nimport Panels from './components/internal/panels';\nimport { sync } from './sync';\nimport { slice } from './store';\n\nexport default function init() {\n\tsync();\n\n\tregisterSlice( slice );\n\n\tinjectIntoTop( { id: 'panels', filler: Panels } );\n}\n","import * as React from 'react';\nimport useOpenPanelInjection from '../../hooks/use-open-panel-injection';\nimport Portal from './portal';\n\nexport default function Panels() {\n\tconst openPanel = useOpenPanelInjection();\n\tconst Component = openPanel?.filler ?? null;\n\n\tif ( ! Component ) {\n\t\treturn null;\n\t}\n\n\treturn (\n\t\t<Portal>\n\t\t\t<Component />\n\t\t</Portal>\n\t);\n}\n","import { useSelector } from '@elementor/store';\nimport { usePanelsInjections } from '../location';\nimport { selectOpenId } from '../store';\nimport { useMemo } from 'react';\n\nexport default function useOpenPanelInjection() {\n\tconst injections = usePanelsInjections();\n\tconst openId = useSelector( selectOpenId );\n\n\treturn useMemo(\n\t\t() => injections.find( ( injection ) => openId === injection.id ),\n\t\t[ injections, openId ]\n\t);\n}\n","import { createLocation } from '@elementor/locations';\n\nexport const {\n\tinject: injectIntoPanels,\n\tuseInjections: usePanelsInjections,\n} = createLocation();\n","import { SliceState } from '@elementor/store';\nimport slice from './slice';\n\ntype State = SliceState<typeof slice>;\n\nexport const selectOpenId = ( state: State ) => state.panels.openId;\n","import { createSlice, PayloadAction } from '@elementor/store';\n\nconst initialState: {\n\topenId: string | null;\n} = {\n\topenId: null,\n};\n\nexport default createSlice( {\n\tname: 'panels',\n\tinitialState,\n\treducers: {\n\t\topen( state, action: PayloadAction<string> ) {\n\t\t\tstate.openId = action.payload;\n\t\t},\n\t\tclose( state, action: PayloadAction<string | undefined> ) {\n\t\t\tif ( ! action.payload || state.openId === action.payload ) {\n\t\t\t\tstate.openId = null;\n\t\t\t}\n\t\t},\n\t},\n} );\n","import * as React from 'react';\nimport { Portal as BasePortal, PortalProps } from '@elementor/ui';\nimport { useRef } from 'react';\nimport { getPortalContainer } from '../../sync';\n\ntype Props = Omit<PortalProps, 'container'>;\n\nexport default function Portal( props: Props ) {\n\tconst containerRef = useRef( getPortalContainer );\n\n\tif ( ! containerRef.current ) {\n\t\treturn null;\n\t}\n\n\treturn (\n\t\t<BasePortal container={ containerRef.current } { ...props } />\n\t);\n}\n","import {\n\topenRoute,\n\tlistenTo,\n\trouteCloseEvent,\n\tuseRouteStatus,\n\trouteOpenEvent,\n\twindowEvent,\n\tregisterRoute,\n\tisRouteActive,\n} from '@elementor/editor-v1-adapters';\nimport { dispatch, getState, subscribe as originalSubscribe } from '@elementor/store';\nimport { selectOpenId, slice } from './store';\n\nconst V2_PANEL = 'panel/v2';\n\nexport function getPortalContainer() {\n\treturn document.querySelector( '#elementor-panel-inner' );\n}\n\nexport function useV1PanelStatus() {\n\t// For now supporting only panels that are not part of the kit and not in preview mode.\n\treturn useRouteStatus( V2_PANEL, {\n\t\tblockOnKitRoutes: true,\n\t\tblockOnPreviewMode: true,\n\t} );\n}\n\nexport function sync() {\n\t// Register the V2 panel route on panel init.\n\tlistenTo(\n\t\twindowEvent( 'elementor/panel/init' ),\n\t\t() => registerRoute( V2_PANEL )\n\t);\n\n\t// On empty route open, hide V1 panel elements.\n\tlistenTo(\n\t\trouteOpenEvent( V2_PANEL ),\n\t\t() => {\n\t\t\tgetV1PanelElements().forEach( ( el ) => {\n\t\t\t\tel.setAttribute( 'hidden', 'hidden' );\n\t\t\t\tel.setAttribute( 'aria-hidden', 'true' );\n\t\t\t} );\n\t\t},\n\t);\n\n\t// On empty route close, close the V2 panel.\n\tlistenTo(\n\t\trouteCloseEvent( V2_PANEL ),\n\t\t() => selectOpenId( getState() ) && dispatch( slice.actions.close() )\n\t);\n\n\t// On empty route close, show V1 panel elements.\n\tlistenTo(\n\t\trouteCloseEvent( V2_PANEL ),\n\t\t() => {\n\t\t\tgetV1PanelElements().forEach( ( el ) => {\n\t\t\t\tel.removeAttribute( 'hidden' );\n\t\t\t\tel.removeAttribute( 'aria-hidden' );\n\t\t\t} );\n\t\t},\n\t);\n\n\t// On V2 panel open, open the V2 panel route.\n\tlistenTo(\n\t\twindowEvent( 'elementor/panel/init' ),\n\t\t() => subscribe( {\n\t\t\ton: ( state ) => selectOpenId( state ),\n\t\t\twhen: ( { prev, current } ) => !! ( ! prev && current ), // is panel opened\n\t\t\tcallback: () => openRoute( V2_PANEL ),\n\t\t} )\n\t);\n\n\t// On V2 panel close, close the V2 panel route.\n\tlistenTo(\n\t\twindowEvent( 'elementor/panel/init' ),\n\t\t() => subscribe( {\n\t\t\ton: ( state ) => selectOpenId( state ),\n\t\t\twhen: ( { prev, current } ) => !! ( ! current && prev ), // is panel closed\n\t\t\tcallback: () => isRouteActive( V2_PANEL ) && openRoute( getDefaultRoute() ),\n\t\t} )\n\t);\n}\n\nfunction getV1PanelElements() {\n\tconst v1ElementsSelector = [\n\t\t'#elementor-panel-header-wrapper',\n\t\t'#elementor-panel-content-wrapper',\n\t\t'#elementor-panel-state-loading',\n\t\t'#elementor-panel-footer',\n\t].join( ', ' );\n\n\treturn document.querySelectorAll( v1ElementsSelector );\n}\n\nfunction getDefaultRoute() {\n\ttype ExtendedWindow = Window & {\n\t\telementor?: {\n\t\t\tdocuments?: {\n\t\t\t\tgetCurrent?: () => {\n\t\t\t\t\tconfig?: {\n\t\t\t\t\t\tpanel?: {\n\t\t\t\t\t\t\tdefault_route?: string,\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t}\n\t\t}\n\t}\n\n\tconst defaultRoute = ( window as unknown as ExtendedWindow )\n\t\t?.elementor\n\t\t?.documents\n\t\t?.getCurrent?.()\n\t\t?.config\n\t\t?.panel\n\t\t?.default_route;\n\n\treturn defaultRoute || 'panel/elements/categories';\n}\n\nfunction subscribe<TVal>( {\n\ton,\n\twhen,\n\tcallback,\n}: {\n\ton: ( state: ReturnType<typeof getState> ) => TVal,\n\twhen: ( { prev, current }: { prev: TVal, current: TVal } ) => boolean,\n\tcallback: ( { prev, current }: { prev: TVal, current: TVal } ) => void,\n} ) {\n\tlet prev: TVal;\n\n\toriginalSubscribe( () => {\n\t\tconst current = on( getState() );\n\n\t\tif ( when( { prev, current } ) ) {\n\t\t\tcallback( { prev, current } );\n\t\t}\n\n\t\tprev = current;\n\t} );\n}\n","import { ComponentType } from 'react';\nimport { injectIntoPanels } from './location';\nimport { selectOpenId, slice } from './store';\nimport { useSelector, useDispatch } from '@elementor/store';\nimport { useV1PanelStatus } from './sync';\n\nexport type PanelDeclaration = {\n\tid: string;\n\tcomponent: ComponentType;\n}\n\nexport function createPanel( { id, component }: PanelDeclaration ) {\n\tconst usePanelStatus = createUseStatus( id );\n\tconst usePanelActions = createUseActions( id, usePanelStatus );\n\n\treturn {\n\t\tpanel: {\n\t\t\tid,\n\t\t\tcomponent,\n\t\t},\n\t\tusePanelStatus,\n\t\tusePanelActions,\n\t};\n}\n\nexport function registerPanel( { id, component }: Pick<PanelDeclaration, 'id' | 'component'> ) {\n\tinjectIntoPanels( {\n\t\tid,\n\t\tfiller: component,\n\t} );\n}\n\nfunction createUseStatus( id: PanelDeclaration['id'] ) {\n\treturn () => {\n\t\tconst openPanelId = useSelector( selectOpenId );\n\t\tconst v1PanelStatus = useV1PanelStatus();\n\n\t\treturn {\n\t\t\tisOpen: openPanelId === id && v1PanelStatus.isActive,\n\t\t\tisBlocked: v1PanelStatus.isBlocked,\n\t\t};\n\t};\n}\n\nfunction createUseActions( id: PanelDeclaration['id'], useStatus: ReturnType<typeof createUseStatus> ) {\n\treturn () => {\n\t\tconst dispatch = useDispatch();\n\t\tconst { isBlocked } = useStatus();\n\n\t\treturn {\n\t\t\topen: async () => {\n\t\t\t\tif ( isBlocked ) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tdispatch( slice.actions.open( id ) );\n\t\t\t},\n\t\t\tclose: async () => {\n\t\t\t\tif ( isBlocked ) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tdispatch( slice.actions.close( id ) );\n\t\t\t},\n\t\t};\n\t};\n}\n","import * as React from 'react';\nimport { Drawer, DrawerProps } from '@elementor/ui';\n\nexport default function Panel( { children, sx, ...props }: DrawerProps ) {\n\treturn (\n\t\t<Drawer\n\t\t\topen={ true }\n\t\t\tvariant=\"persistent\"\n\t\t\tanchor=\"left\"\n\t\t\tPaperProps={ {\n\t\t\t\tsx: {\n\t\t\t\t\tposition: 'relative',\n\t\t\t\t\twidth: '100%',\n\t\t\t\t\tbgcolor: 'background.default',\n\t\t\t\t\tborder: 'none',\n\t\t\t\t},\n\t\t\t} }\n\t\t\tsx={ { height: '100%', ...sx } }\n\t\t\t{ ...props }\n\t\t>\n\t\t\t{ children }\n\t\t</Drawer>\n\t);\n}\n","import * as React from 'react';\nimport { Box, BoxProps, Divider, styled } from '@elementor/ui';\n\nconst Header = styled( Box )( ( { theme } ) => ( {\n\theight: theme?.sizing?.[ '600' ] || '48px',\n\tdisplay: 'flex',\n\talignItems: 'center',\n\tjustifyContent: 'center',\n} ) );\n\nexport default function PanelHeader( { children, ...props }: BoxProps ) {\n\treturn (\n\t\t<>\n\t\t\t<Header component=\"header\" { ...props }>\n\t\t\t\t{ children }\n\t\t\t</Header>\n\t\t\t<Divider />\n\t\t</>\n\t);\n}\n","import * as React from 'react';\nimport { Typography, TypographyProps, styled } from '@elementor/ui';\n\nconst Title = styled( Typography )( ( { theme } ) => ( {\n\t'&.MuiTypography-root': {\n\t\tfontWeight: 'bold',\n\t\tfontSize: theme.typography.body1.fontSize,\n\t},\n} ) );\n\nexport default function PanelHeaderTitle( { children, ...props }: TypographyProps ) {\n\treturn (\n\t\t<Title\n\t\t\tcomponent=\"h2\"\n\t\t\tvariant=\"body1\"\n\t\t\t{ ...props }\n\t\t>\n\t\t\t{ children }\n\t\t</Title>\n\t);\n}\n","import * as React from 'react';\nimport { Box, BoxProps } from '@elementor/ui';\n\nexport default function PanelBody( { children, sx, ...props }: BoxProps ) {\n\treturn (\n\t\t<Box\n\t\t\tcomponent=\"main\"\n\t\t\tsx={ {\n\t\t\t\toverflowY: 'auto',\n\t\t\t\theight: '100%',\n\t\t\t\t...sx,\n\t\t\t} }\n\t\t\t{ ...props }\n\t\t>{ children }</Box>\n\t);\n}\n","import init from './init';\n\nexport { createPanel, registerPanel } from './api';\nexport * from './components/external';\n\ninit();\n"],"mappings":"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","names":["React","useSelector","useSelector","dispatch","React","React","React","styled","React","Box"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@elementor/editor-panels",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": false,
|
|
5
|
+
"author": "Elementor Team",
|
|
6
|
+
"homepage": "https://elementor.com/",
|
|
7
|
+
"license": "GPL-3.0-or-later",
|
|
8
|
+
"main": "dist/index.js",
|
|
9
|
+
"module": "dist/index.mjs",
|
|
10
|
+
"types": "dist/index.d.ts",
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"import": "./dist/index.mjs",
|
|
14
|
+
"require": "./dist/index.js",
|
|
15
|
+
"types": "./dist/index.d.ts"
|
|
16
|
+
},
|
|
17
|
+
"./package.json": "./package.json"
|
|
18
|
+
},
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "https://github.com/elementor/elementor-packages.git",
|
|
22
|
+
"directory": "packages/core/editor-panels"
|
|
23
|
+
},
|
|
24
|
+
"bugs": {
|
|
25
|
+
"url": "https://github.com/elementor/elementor-packages/issues"
|
|
26
|
+
},
|
|
27
|
+
"publishConfig": {
|
|
28
|
+
"access": "public"
|
|
29
|
+
},
|
|
30
|
+
"scripts": {
|
|
31
|
+
"build": "tsup --config=../../tsup.build.ts",
|
|
32
|
+
"dev": "tsup --config=../../tsup.dev.ts"
|
|
33
|
+
},
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"@elementor/editor": "^0.6.1",
|
|
36
|
+
"@elementor/editor-v1-adapters": "^0.5.0",
|
|
37
|
+
"@elementor/locations": "^0.6.0",
|
|
38
|
+
"@elementor/store": "^0.6.0",
|
|
39
|
+
"@elementor/ui": "^1.4.50"
|
|
40
|
+
},
|
|
41
|
+
"peerDependencies": {
|
|
42
|
+
"react": "18.x"
|
|
43
|
+
},
|
|
44
|
+
"elementor": {
|
|
45
|
+
"type": "extension"
|
|
46
|
+
},
|
|
47
|
+
"gitHead": "70a2c6139730b7afa69d13eb65023b0e6612f23b"
|
|
48
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { createStore, registerSlice, StoreProvider } from '@elementor/store';
|
|
3
|
+
import { slice } from '../store';
|
|
4
|
+
import { createPanel, registerPanel } from '../api';
|
|
5
|
+
import { fireEvent, render, screen } from '@testing-library/react';
|
|
6
|
+
import Panels from '../components/internal/panels';
|
|
7
|
+
import { useRouteStatus } from '@elementor/editor-v1-adapters';
|
|
8
|
+
import { Panel, PanelBody, PanelHeader, PanelHeaderTitle } from '../components/external';
|
|
9
|
+
|
|
10
|
+
jest.mock( '@elementor/editor-v1-adapters', () => ( {
|
|
11
|
+
__esModule: true,
|
|
12
|
+
...jest.requireActual( '@elementor/editor-v1-adapters' ),
|
|
13
|
+
useRouteStatus: jest.fn( () => ( {
|
|
14
|
+
isActive: false,
|
|
15
|
+
isBlocked: false,
|
|
16
|
+
} ) ),
|
|
17
|
+
} ) );
|
|
18
|
+
|
|
19
|
+
describe( '@elementor/editor-panels api', () => {
|
|
20
|
+
const {
|
|
21
|
+
panel,
|
|
22
|
+
usePanelStatus,
|
|
23
|
+
usePanelActions,
|
|
24
|
+
} = createPanel( {
|
|
25
|
+
id: 'test-panel',
|
|
26
|
+
component: () => (
|
|
27
|
+
<Panel>
|
|
28
|
+
<PanelHeader>
|
|
29
|
+
<PanelHeaderTitle>Test Panel Header</PanelHeaderTitle>
|
|
30
|
+
</PanelHeader>
|
|
31
|
+
<PanelBody>
|
|
32
|
+
<p>Test Panel Body</p>
|
|
33
|
+
</PanelBody>
|
|
34
|
+
</Panel>
|
|
35
|
+
),
|
|
36
|
+
} );
|
|
37
|
+
|
|
38
|
+
const renderPanels = () => {
|
|
39
|
+
const Trigger = () => {
|
|
40
|
+
const { isOpen, isBlocked } = usePanelStatus();
|
|
41
|
+
const { open, close } = usePanelActions();
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<button onClick={ () => isOpen ? close() : open() } disabled={ isBlocked }>
|
|
45
|
+
{ isOpen ? 'Close Panel' : 'Open Panel' }
|
|
46
|
+
</button>
|
|
47
|
+
);
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
registerSlice( slice );
|
|
51
|
+
|
|
52
|
+
render(
|
|
53
|
+
<StoreProvider store={ createStore() }>
|
|
54
|
+
<Panels />
|
|
55
|
+
<Trigger />
|
|
56
|
+
</StoreProvider>
|
|
57
|
+
);
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
beforeEach( () => {
|
|
61
|
+
// Setup for the environment.
|
|
62
|
+
document.body.innerHTML = `
|
|
63
|
+
<div id="elementor-editor-wrapper">
|
|
64
|
+
<div id="elementor-panel-inner"></div>
|
|
65
|
+
</div>
|
|
66
|
+
`;
|
|
67
|
+
} );
|
|
68
|
+
|
|
69
|
+
afterEach( () => {
|
|
70
|
+
// Cleanup for the environment.
|
|
71
|
+
document.body.innerHTML = '';
|
|
72
|
+
} );
|
|
73
|
+
|
|
74
|
+
it( 'should open the panel when triggering `open` action', () => {
|
|
75
|
+
// Arrange.
|
|
76
|
+
registerPanel( panel );
|
|
77
|
+
|
|
78
|
+
// Act.
|
|
79
|
+
renderPanels();
|
|
80
|
+
|
|
81
|
+
// Assert.
|
|
82
|
+
expect( screen.queryByText( 'Test Panel Header' ) ).not.toBeInTheDocument();
|
|
83
|
+
expect( screen.queryByText( 'Test Panel Body' ) ).not.toBeInTheDocument();
|
|
84
|
+
|
|
85
|
+
// Act.
|
|
86
|
+
fireEvent.click( screen.getByText( 'Open Panel' ) );
|
|
87
|
+
|
|
88
|
+
// Assert.
|
|
89
|
+
expect( screen.queryByText( 'Test Panel Header' ) ).toBeInTheDocument();
|
|
90
|
+
expect( screen.queryByText( 'Test Panel Body' ) ).toBeInTheDocument();
|
|
91
|
+
} );
|
|
92
|
+
|
|
93
|
+
it( 'should close the panel when triggering `close` action', () => {
|
|
94
|
+
// Arrange.
|
|
95
|
+
registerPanel( panel );
|
|
96
|
+
|
|
97
|
+
jest.mocked( useRouteStatus ).mockReturnValue( {
|
|
98
|
+
isActive: true,
|
|
99
|
+
isBlocked: false,
|
|
100
|
+
} );
|
|
101
|
+
|
|
102
|
+
// Act.
|
|
103
|
+
renderPanels();
|
|
104
|
+
|
|
105
|
+
fireEvent.click( screen.getByText( 'Open Panel' ) );
|
|
106
|
+
|
|
107
|
+
// Assert.
|
|
108
|
+
expect( screen.queryByText( 'Test Panel Header' ) ).toBeInTheDocument();
|
|
109
|
+
expect( screen.queryByText( 'Test Panel Body' ) ).toBeInTheDocument();
|
|
110
|
+
|
|
111
|
+
// Act.
|
|
112
|
+
fireEvent.click( screen.getByText( 'Close Panel' ) );
|
|
113
|
+
|
|
114
|
+
// Assert.
|
|
115
|
+
expect( screen.queryByText( 'Test Panel Header' ) ).not.toBeInTheDocument();
|
|
116
|
+
expect( screen.queryByText( 'Test Panel Body' ) ).not.toBeInTheDocument();
|
|
117
|
+
} );
|
|
118
|
+
|
|
119
|
+
it( 'should not open the panel if the panel was not registered', () => {
|
|
120
|
+
// Act.
|
|
121
|
+
renderPanels();
|
|
122
|
+
|
|
123
|
+
fireEvent.click( screen.getByText( 'Open Panel' ) );
|
|
124
|
+
|
|
125
|
+
// Assert.
|
|
126
|
+
expect( screen.queryByText( 'Test Panel Header' ) ).not.toBeInTheDocument();
|
|
127
|
+
expect( screen.queryByText( 'Test Panel Body' ) ).not.toBeInTheDocument();
|
|
128
|
+
} );
|
|
129
|
+
|
|
130
|
+
it( 'should block open panel when isBlocked state equals `true`', () => {
|
|
131
|
+
// Arrange.
|
|
132
|
+
registerPanel( panel );
|
|
133
|
+
|
|
134
|
+
jest.mocked( useRouteStatus ).mockReturnValue( {
|
|
135
|
+
isActive: false,
|
|
136
|
+
isBlocked: true,
|
|
137
|
+
} );
|
|
138
|
+
|
|
139
|
+
// Act.
|
|
140
|
+
renderPanels();
|
|
141
|
+
|
|
142
|
+
// Assert.
|
|
143
|
+
expect( screen.getByText( 'Open Panel' ) ).toBeDisabled();
|
|
144
|
+
} );
|
|
145
|
+
} );
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { sync } from '../sync';
|
|
2
|
+
import { isRouteActive, openRoute, registerRoute } from '@elementor/editor-v1-adapters';
|
|
3
|
+
import { createStore, dispatch, getState, registerSlice } from '@elementor/store';
|
|
4
|
+
import { selectOpenId, slice } from '../store';
|
|
5
|
+
|
|
6
|
+
jest.mock( '@elementor/editor-v1-adapters', () => ( {
|
|
7
|
+
...jest.requireActual( '@elementor/editor-v1-adapters' ),
|
|
8
|
+
registerRoute: jest.fn(),
|
|
9
|
+
openRoute: jest.fn(),
|
|
10
|
+
isRouteActive: jest.fn(),
|
|
11
|
+
} ) );
|
|
12
|
+
|
|
13
|
+
describe( '@elementor/editor-panels sync', () => {
|
|
14
|
+
const v1PanelElementsIds = [
|
|
15
|
+
'elementor-panel-header-wrapper',
|
|
16
|
+
'elementor-panel-content-wrapper',
|
|
17
|
+
'elementor-panel-state-loading',
|
|
18
|
+
'elementor-panel-footer',
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
beforeEach( () => {
|
|
22
|
+
sync();
|
|
23
|
+
registerSlice( slice );
|
|
24
|
+
createStore();
|
|
25
|
+
} );
|
|
26
|
+
|
|
27
|
+
afterEach( () => {
|
|
28
|
+
document.body.innerHTML = '';
|
|
29
|
+
} );
|
|
30
|
+
|
|
31
|
+
it( 'should register the empty route on init', () => {
|
|
32
|
+
// Arrange & Act.
|
|
33
|
+
window.dispatchEvent( new CustomEvent( 'elementor/panel/init' ) );
|
|
34
|
+
|
|
35
|
+
// Assert.
|
|
36
|
+
expect( registerRoute ).toHaveBeenCalledTimes( 1 );
|
|
37
|
+
expect( registerRoute ).toHaveBeenCalledWith( 'panel/v2' );
|
|
38
|
+
} );
|
|
39
|
+
|
|
40
|
+
it( 'should open v1 route `panels/empty` when triggering `open` action', () => {
|
|
41
|
+
// Arrange.
|
|
42
|
+
window.dispatchEvent( new CustomEvent( 'elementor/panel/init' ) );
|
|
43
|
+
|
|
44
|
+
// Act.
|
|
45
|
+
dispatch( slice.actions.open( 'test' ) );
|
|
46
|
+
|
|
47
|
+
// Arrange.
|
|
48
|
+
expect( openRoute ).toHaveBeenCalledTimes( 1 );
|
|
49
|
+
expect( openRoute ).toHaveBeenCalledWith( 'panel/v2' );
|
|
50
|
+
} );
|
|
51
|
+
|
|
52
|
+
it( 'should close v1 route `panel/v2` when triggering `close` action and set default v1 route', () => {
|
|
53
|
+
// Arrange.
|
|
54
|
+
window.dispatchEvent( new CustomEvent( 'elementor/panel/init' ) );
|
|
55
|
+
|
|
56
|
+
jest.mocked( isRouteActive ).mockImplementation( ( route ) => route === 'panel/v2' );
|
|
57
|
+
|
|
58
|
+
dispatch( slice.actions.open( 'not-relevant-test' ) );
|
|
59
|
+
dispatch( slice.actions.open( 'not-relevant-test-2' ) );
|
|
60
|
+
dispatch( slice.actions.open( 'test' ) );
|
|
61
|
+
|
|
62
|
+
// Act.
|
|
63
|
+
dispatch( slice.actions.close( 'test' ) );
|
|
64
|
+
|
|
65
|
+
// Arrange.
|
|
66
|
+
expect( openRoute ).toHaveBeenCalledTimes( 2 );
|
|
67
|
+
expect( openRoute ).toHaveBeenNthCalledWith( 1, 'panel/v2' );
|
|
68
|
+
expect( openRoute ).toHaveBeenNthCalledWith( 2, 'panel/elements/categories' );
|
|
69
|
+
} );
|
|
70
|
+
|
|
71
|
+
it( 'should not navigate to v1 default route if the route is not `panel/v2`', () => {
|
|
72
|
+
// Arrange.
|
|
73
|
+
window.dispatchEvent( new CustomEvent( 'elementor/panel/init' ) );
|
|
74
|
+
|
|
75
|
+
jest.mocked( isRouteActive ).mockImplementation( () => false );
|
|
76
|
+
|
|
77
|
+
dispatch( slice.actions.open( 'test' ) );
|
|
78
|
+
|
|
79
|
+
// Act.
|
|
80
|
+
dispatch( slice.actions.close( 'test' ) );
|
|
81
|
+
|
|
82
|
+
// Arrange.
|
|
83
|
+
expect( openRoute ).toHaveBeenCalledTimes( 1 );
|
|
84
|
+
expect( openRoute ).toHaveBeenCalledWith( 'panel/v2' );
|
|
85
|
+
} );
|
|
86
|
+
|
|
87
|
+
it( 'should close the panel when navigating to another v1 route', () => {
|
|
88
|
+
// Arrange.
|
|
89
|
+
dispatch( slice.actions.open( 'test' ) );
|
|
90
|
+
|
|
91
|
+
// Act.
|
|
92
|
+
window.dispatchEvent( new CustomEvent( 'elementor/routes/close', {
|
|
93
|
+
detail: { route: 'panel/v2' },
|
|
94
|
+
} ) );
|
|
95
|
+
|
|
96
|
+
// Act.
|
|
97
|
+
expect( selectOpenId( getState() ) ).toBe( null );
|
|
98
|
+
} );
|
|
99
|
+
|
|
100
|
+
it( 'should hide old panel elements when navigating to empty route', () => {
|
|
101
|
+
// Arrange.
|
|
102
|
+
document.body.innerHTML = v1PanelElementsIds.map( ( id ) => `<div id="${ id }"></div>` ).join( '' );
|
|
103
|
+
|
|
104
|
+
// Act.
|
|
105
|
+
window.dispatchEvent( new CustomEvent( 'elementor/routes/open', {
|
|
106
|
+
detail: { route: 'panel/v2' },
|
|
107
|
+
} ) );
|
|
108
|
+
|
|
109
|
+
// Assert.
|
|
110
|
+
v1PanelElementsIds.forEach( ( id ) => {
|
|
111
|
+
expect( document.getElementById( id ) ).not.toBeVisible();
|
|
112
|
+
} );
|
|
113
|
+
} );
|
|
114
|
+
|
|
115
|
+
it( 'should show old panel elements when navigating out of the empty route', () => {
|
|
116
|
+
// Arrange.
|
|
117
|
+
document.body.innerHTML = v1PanelElementsIds.map( ( id ) => `<div id="${ id }" aria-hidden="true" hidden="hidden"></div>` ).join( '' );
|
|
118
|
+
|
|
119
|
+
// Act.
|
|
120
|
+
window.dispatchEvent( new CustomEvent( 'elementor/routes/close', {
|
|
121
|
+
detail: { route: 'panel/v2' },
|
|
122
|
+
} ) );
|
|
123
|
+
|
|
124
|
+
// Assert.
|
|
125
|
+
v1PanelElementsIds.forEach( ( id ) => {
|
|
126
|
+
expect( document.getElementById( id ) ).toBeVisible();
|
|
127
|
+
} );
|
|
128
|
+
} );
|
|
129
|
+
} );
|