plusui-native-core 0.1.4 → 0.1.5
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/Core/CMakeLists.txt +190 -7
- package/Core/Features/App/app.cpp +129 -0
- package/Core/Features/App/app.ts +126 -0
- package/Core/Features/Browser/browser.cpp +181 -0
- package/Core/Features/Browser/browser.ts +182 -0
- package/Core/Features/Clipboard/clipboard.cpp +234 -0
- package/Core/Features/Clipboard/clipboard.ts +113 -0
- package/Core/Features/Display/display.cpp +209 -0
- package/Core/Features/Display/display.ts +104 -0
- package/Core/Features/Event/Events.ts +166 -0
- package/Core/Features/Event/events.cpp +200 -0
- package/Core/Features/Keyboard/keyboard.cpp +186 -0
- package/Core/Features/Keyboard/keyboard.ts +175 -0
- package/Core/Features/Menu/context-menu.css +293 -0
- package/Core/Features/Menu/menu.cpp +481 -0
- package/Core/Features/Menu/menu.ts +439 -0
- package/Core/Features/Tray/tray.cpp +310 -0
- package/Core/Features/Tray/tray.ts +68 -0
- package/Core/Features/WebGPU/webgpu.cpp +937 -0
- package/Core/Features/WebGPU/webgpu.ts +1013 -0
- package/Core/Features/WebView/webview.cpp +1052 -0
- package/Core/Features/WebView/webview.ts +510 -0
- package/Core/Features/Window/window.cpp +664 -0
- package/Core/Features/Window/window.ts +142 -0
- package/Core/Features/WindowManager/window_manager.cpp +341 -0
- package/Core/include/plusui/app.hpp +73 -0
- package/Core/include/plusui/browser.hpp +66 -0
- package/Core/include/plusui/clipboard.hpp +41 -0
- package/Core/include/plusui/events.hpp +58 -0
- package/Core/include/{keyboard.hpp → plusui/keyboard.hpp} +21 -44
- package/Core/include/plusui/menu.hpp +153 -0
- package/Core/include/plusui/tray.hpp +93 -0
- package/Core/include/plusui/webgpu.hpp +434 -0
- package/Core/include/plusui/webview.hpp +142 -0
- package/Core/include/plusui/window.hpp +111 -0
- package/Core/include/plusui/window_manager.hpp +57 -0
- package/Core/vendor/WebView2EnvironmentOptions.h +406 -0
- package/Core/vendor/stb_image.h +7988 -0
- package/Core/vendor/webview.h +618 -510
- package/Core/vendor/webview2.h +52079 -0
- package/README.md +19 -0
- package/package.json +12 -15
- package/Core/include/app.hpp +0 -121
- package/Core/include/menu.hpp +0 -79
- package/Core/include/tray.hpp +0 -81
- package/Core/include/window.hpp +0 -106
- package/Core/src/app.cpp +0 -311
- package/Core/src/display.cpp +0 -424
- package/Core/src/tray.cpp +0 -275
- package/Core/src/window.cpp +0 -528
- package/dist/index.d.ts +0 -205
- package/dist/index.js +0 -198
- package/src/index.ts +0 -574
- /package/Core/include/{display.hpp → plusui/display.hpp} +0 -0
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PlusUI Menu API
|
|
3
|
+
*
|
|
4
|
+
* Cross-platform native menu system including:
|
|
5
|
+
* - Application menu bar (File, Edit, View, etc.)
|
|
6
|
+
* - Context menus (right-click)
|
|
7
|
+
* - Popup menus
|
|
8
|
+
* - System tray menus
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
// ============================================================================
|
|
12
|
+
// Types
|
|
13
|
+
// ============================================================================
|
|
14
|
+
|
|
15
|
+
export type MenuItemType = 'normal' | 'separator' | 'submenu' | 'checkbox' | 'radio';
|
|
16
|
+
|
|
17
|
+
export interface MenuItem {
|
|
18
|
+
/** Unique identifier for this menu item */
|
|
19
|
+
id: string;
|
|
20
|
+
/** Display text (use & before a letter for keyboard shortcut, e.g., "&File") */
|
|
21
|
+
label: string;
|
|
22
|
+
/** Keyboard shortcut (e.g., "Ctrl+S", "Cmd+Shift+N") */
|
|
23
|
+
accelerator?: string;
|
|
24
|
+
/** Icon path or data URL */
|
|
25
|
+
icon?: string;
|
|
26
|
+
/** Type of menu item */
|
|
27
|
+
type?: MenuItemType;
|
|
28
|
+
/** Whether the item is clickable */
|
|
29
|
+
enabled?: boolean;
|
|
30
|
+
/** For checkbox/radio types, whether it's checked */
|
|
31
|
+
checked?: boolean;
|
|
32
|
+
/** Nested menu items for submenus */
|
|
33
|
+
submenu?: MenuItem[];
|
|
34
|
+
/** Click handler (frontend only, not sent to backend) */
|
|
35
|
+
click?: (menuItem: MenuItem) => void;
|
|
36
|
+
/** Custom data attached to this item */
|
|
37
|
+
data?: Record<string, unknown>;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface MenuBarData {
|
|
41
|
+
items: MenuItem[];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface ContextMenuOptions {
|
|
45
|
+
/** Position to show the menu (defaults to cursor position) */
|
|
46
|
+
x?: number;
|
|
47
|
+
y?: number;
|
|
48
|
+
/** Only show for elements matching this selector */
|
|
49
|
+
selector?: string;
|
|
50
|
+
/** Context info passed to click handlers */
|
|
51
|
+
context?: Record<string, unknown>;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface ContextInfo {
|
|
55
|
+
x: number;
|
|
56
|
+
y: number;
|
|
57
|
+
clientX: number;
|
|
58
|
+
clientY: number;
|
|
59
|
+
selector: string;
|
|
60
|
+
tagName: string;
|
|
61
|
+
isEditable: boolean;
|
|
62
|
+
hasSelection: boolean;
|
|
63
|
+
selectedText?: string;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// ============================================================================
|
|
67
|
+
// Menu API Class
|
|
68
|
+
// ============================================================================
|
|
69
|
+
|
|
70
|
+
export class MenuAPI {
|
|
71
|
+
private clickHandlers: Map<string, (item: MenuItem) => void> = new Map();
|
|
72
|
+
private contextMenuItems: MenuItem[] = [];
|
|
73
|
+
private contextMenuEnabled = false;
|
|
74
|
+
private selectorMenus: Map<string, MenuItem[]> = new Map();
|
|
75
|
+
private unsubscribeClick?: () => void;
|
|
76
|
+
|
|
77
|
+
constructor(
|
|
78
|
+
private invokeFn: (name: string, args?: unknown[]) => Promise<unknown>,
|
|
79
|
+
private eventFn: (event: string, callback: (...args: unknown[]) => void) => () => void
|
|
80
|
+
) {
|
|
81
|
+
// Listen for menu item clicks from backend
|
|
82
|
+
this.unsubscribeClick = this.eventFn('menu:itemClick', (...args: unknown[]) => {
|
|
83
|
+
const id = args[0] as string;
|
|
84
|
+
this.handleItemClick(id);
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// ========================================================================
|
|
89
|
+
// Popup Menus
|
|
90
|
+
// ========================================================================
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Create a new popup menu and return its ID
|
|
94
|
+
*/
|
|
95
|
+
async create(items: MenuItem[]): Promise<string> {
|
|
96
|
+
this.registerClickHandlers(items);
|
|
97
|
+
const cleanItems = this.stripFunctions(items);
|
|
98
|
+
return await this.invokeFn('menu.create', [cleanItems]) as string;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Show a popup menu at specified coordinates
|
|
103
|
+
*/
|
|
104
|
+
async popup(menuId: string, x?: number, y?: number): Promise<void> {
|
|
105
|
+
await this.invokeFn('menu.popup', [menuId, x ?? 0, y ?? 0]);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Show a popup menu at the current cursor position
|
|
110
|
+
*/
|
|
111
|
+
async popupAtCursor(menuId: string): Promise<void> {
|
|
112
|
+
await this.invokeFn('menu.popupAtCursor', [menuId]);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Close an open popup menu
|
|
117
|
+
*/
|
|
118
|
+
async close(menuId: string): Promise<void> {
|
|
119
|
+
await this.invokeFn('menu.close', [menuId]);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Destroy a menu and free its resources
|
|
124
|
+
*/
|
|
125
|
+
async destroy(menuId: string): Promise<void> {
|
|
126
|
+
await this.invokeFn('menu.destroy', [menuId]);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// ========================================================================
|
|
130
|
+
// Application Menu Bar
|
|
131
|
+
// ========================================================================
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Set the application menu bar (Window menu on Windows/Linux, global on macOS)
|
|
135
|
+
*/
|
|
136
|
+
async setApplicationMenu(items: MenuItem[]): Promise<void> {
|
|
137
|
+
this.registerClickHandlers(items);
|
|
138
|
+
const cleanItems = this.stripFunctions(items);
|
|
139
|
+
await this.invokeFn('menu.setApplicationMenu', [cleanItems]);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Get the current application menu structure
|
|
144
|
+
*/
|
|
145
|
+
async getApplicationMenu(): Promise<MenuItem[]> {
|
|
146
|
+
return await this.invokeFn('menu.getApplicationMenu', []) as MenuItem[];
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Append an item to the application menu bar
|
|
151
|
+
*/
|
|
152
|
+
async appendToMenuBar(item: MenuItem): Promise<void> {
|
|
153
|
+
this.registerClickHandlers([item]);
|
|
154
|
+
const cleanItem = this.stripFunctions([item])[0];
|
|
155
|
+
await this.invokeFn('menu.appendToMenuBar', [cleanItem]);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// ========================================================================
|
|
159
|
+
// Context Menus (Right-Click)
|
|
160
|
+
// ========================================================================
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Enable custom context menu, replacing the default browser/webview menu
|
|
164
|
+
*/
|
|
165
|
+
enableContextMenu(items: MenuItem[]): void {
|
|
166
|
+
this.contextMenuItems = items;
|
|
167
|
+
this.contextMenuEnabled = true;
|
|
168
|
+
this.registerClickHandlers(items);
|
|
169
|
+
this.setupContextMenuHandler();
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Disable custom context menu, restoring default behavior
|
|
174
|
+
*/
|
|
175
|
+
disableContextMenu(): void {
|
|
176
|
+
this.contextMenuEnabled = false;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Set context menu items for a specific CSS selector
|
|
181
|
+
* Allows different menus for different elements
|
|
182
|
+
*/
|
|
183
|
+
setContextMenuForSelector(selector: string, items: MenuItem[]): void {
|
|
184
|
+
this.selectorMenus.set(selector, items);
|
|
185
|
+
this.registerClickHandlers(items);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Show context menu programmatically
|
|
190
|
+
*/
|
|
191
|
+
async showContextMenu(items: MenuItem[], options: ContextMenuOptions = {}): Promise<void> {
|
|
192
|
+
const menuId = await this.create(items);
|
|
193
|
+
await this.popup(menuId, options.x, options.y);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// ========================================================================
|
|
197
|
+
// Event Handling
|
|
198
|
+
// ========================================================================
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Listen for any menu item click
|
|
202
|
+
*/
|
|
203
|
+
onMenuItemClick(callback: (id: string, item?: MenuItem) => void): () => void {
|
|
204
|
+
return this.eventFn('menu:itemClick', (...args: unknown[]) => {
|
|
205
|
+
const id = args[0] as string;
|
|
206
|
+
callback(id);
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Listen for context menu open
|
|
212
|
+
*/
|
|
213
|
+
onContextMenuOpen(callback: (info: ContextInfo) => void): () => void {
|
|
214
|
+
return this.eventFn('menu:contextOpen', callback as (...args: unknown[]) => void);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// ========================================================================
|
|
218
|
+
// Predefined Menu Templates
|
|
219
|
+
// ========================================================================
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Create a standard edit menu (Cut, Copy, Paste, etc.)
|
|
223
|
+
*/
|
|
224
|
+
static createEditMenu(handlers?: Partial<{
|
|
225
|
+
undo: () => void;
|
|
226
|
+
redo: () => void;
|
|
227
|
+
cut: () => void;
|
|
228
|
+
copy: () => void;
|
|
229
|
+
paste: () => void;
|
|
230
|
+
selectAll: () => void;
|
|
231
|
+
}>): MenuItem {
|
|
232
|
+
return {
|
|
233
|
+
id: 'edit',
|
|
234
|
+
label: '&Edit',
|
|
235
|
+
submenu: [
|
|
236
|
+
{ id: 'undo', label: 'Undo', accelerator: 'Ctrl+Z', click: handlers?.undo },
|
|
237
|
+
{ id: 'redo', label: 'Redo', accelerator: 'Ctrl+Y', click: handlers?.redo },
|
|
238
|
+
{ id: 'sep1', label: '', type: 'separator' },
|
|
239
|
+
{ id: 'cut', label: 'Cut', accelerator: 'Ctrl+X', click: handlers?.cut },
|
|
240
|
+
{ id: 'copy', label: 'Copy', accelerator: 'Ctrl+C', click: handlers?.copy },
|
|
241
|
+
{ id: 'paste', label: 'Paste', accelerator: 'Ctrl+V', click: handlers?.paste },
|
|
242
|
+
{ id: 'sep2', label: '', type: 'separator' },
|
|
243
|
+
{ id: 'selectAll', label: 'Select All', accelerator: 'Ctrl+A', click: handlers?.selectAll },
|
|
244
|
+
]
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Create a standard file menu
|
|
250
|
+
*/
|
|
251
|
+
static createFileMenu(handlers?: Partial<{
|
|
252
|
+
new: () => void;
|
|
253
|
+
open: () => void;
|
|
254
|
+
save: () => void;
|
|
255
|
+
saveAs: () => void;
|
|
256
|
+
exit: () => void;
|
|
257
|
+
}>): MenuItem {
|
|
258
|
+
return {
|
|
259
|
+
id: 'file',
|
|
260
|
+
label: '&File',
|
|
261
|
+
submenu: [
|
|
262
|
+
{ id: 'new', label: 'New', accelerator: 'Ctrl+N', click: handlers?.new },
|
|
263
|
+
{ id: 'open', label: 'Open...', accelerator: 'Ctrl+O', click: handlers?.open },
|
|
264
|
+
{ id: 'sep1', label: '', type: 'separator' },
|
|
265
|
+
{ id: 'save', label: 'Save', accelerator: 'Ctrl+S', click: handlers?.save },
|
|
266
|
+
{ id: 'saveAs', label: 'Save As...', accelerator: 'Ctrl+Shift+S', click: handlers?.saveAs },
|
|
267
|
+
{ id: 'sep2', label: '', type: 'separator' },
|
|
268
|
+
{ id: 'exit', label: 'Exit', accelerator: 'Alt+F4', click: handlers?.exit },
|
|
269
|
+
]
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Create a standard view menu
|
|
275
|
+
*/
|
|
276
|
+
static createViewMenu(handlers?: Partial<{
|
|
277
|
+
zoomIn: () => void;
|
|
278
|
+
zoomOut: () => void;
|
|
279
|
+
resetZoom: () => void;
|
|
280
|
+
fullscreen: () => void;
|
|
281
|
+
devtools: () => void;
|
|
282
|
+
}>): MenuItem {
|
|
283
|
+
return {
|
|
284
|
+
id: 'view',
|
|
285
|
+
label: '&View',
|
|
286
|
+
submenu: [
|
|
287
|
+
{ id: 'zoomIn', label: 'Zoom In', accelerator: 'Ctrl++', click: handlers?.zoomIn },
|
|
288
|
+
{ id: 'zoomOut', label: 'Zoom Out', accelerator: 'Ctrl+-', click: handlers?.zoomOut },
|
|
289
|
+
{ id: 'resetZoom', label: 'Reset Zoom', accelerator: 'Ctrl+0', click: handlers?.resetZoom },
|
|
290
|
+
{ id: 'sep1', label: '', type: 'separator' },
|
|
291
|
+
{ id: 'fullscreen', label: 'Toggle Fullscreen', accelerator: 'F11', click: handlers?.fullscreen },
|
|
292
|
+
{ id: 'sep2', label: '', type: 'separator' },
|
|
293
|
+
{ id: 'devtools', label: 'Developer Tools', accelerator: 'F12', click: handlers?.devtools },
|
|
294
|
+
]
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Create a default context menu for text areas
|
|
300
|
+
*/
|
|
301
|
+
static createTextContextMenu(): MenuItem[] {
|
|
302
|
+
return [
|
|
303
|
+
{ id: 'cut', label: 'Cut', accelerator: 'Ctrl+X' },
|
|
304
|
+
{ id: 'copy', label: 'Copy', accelerator: 'Ctrl+C' },
|
|
305
|
+
{ id: 'paste', label: 'Paste', accelerator: 'Ctrl+V' },
|
|
306
|
+
{ id: 'sep1', label: '', type: 'separator' },
|
|
307
|
+
{ id: 'selectAll', label: 'Select All', accelerator: 'Ctrl+A' },
|
|
308
|
+
];
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Create a default context menu for images
|
|
313
|
+
*/
|
|
314
|
+
static createImageContextMenu(): MenuItem[] {
|
|
315
|
+
return [
|
|
316
|
+
{ id: 'copyImage', label: 'Copy Image' },
|
|
317
|
+
{ id: 'saveImage', label: 'Save Image As...' },
|
|
318
|
+
{ id: 'sep1', label: '', type: 'separator' },
|
|
319
|
+
{ id: 'openInNewTab', label: 'Open Image in New Tab' },
|
|
320
|
+
];
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Create a default context menu for links
|
|
325
|
+
*/
|
|
326
|
+
static createLinkContextMenu(): MenuItem[] {
|
|
327
|
+
return [
|
|
328
|
+
{ id: 'openLink', label: 'Open Link' },
|
|
329
|
+
{ id: 'openInNewTab', label: 'Open in New Tab' },
|
|
330
|
+
{ id: 'sep1', label: '', type: 'separator' },
|
|
331
|
+
{ id: 'copyLink', label: 'Copy Link Address' },
|
|
332
|
+
];
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// ========================================================================
|
|
336
|
+
// Private Helpers
|
|
337
|
+
// ========================================================================
|
|
338
|
+
|
|
339
|
+
private contextMenuHandlerSetup = false;
|
|
340
|
+
|
|
341
|
+
private setupContextMenuHandler(): void {
|
|
342
|
+
if (this.contextMenuHandlerSetup) return;
|
|
343
|
+
this.contextMenuHandlerSetup = true;
|
|
344
|
+
|
|
345
|
+
document.addEventListener('contextmenu', async (e) => {
|
|
346
|
+
if (!this.contextMenuEnabled) return;
|
|
347
|
+
|
|
348
|
+
e.preventDefault();
|
|
349
|
+
|
|
350
|
+
const target = e.target as HTMLElement;
|
|
351
|
+
const info: ContextInfo = {
|
|
352
|
+
x: e.screenX,
|
|
353
|
+
y: e.screenY,
|
|
354
|
+
clientX: e.clientX,
|
|
355
|
+
clientY: e.clientY,
|
|
356
|
+
selector: this.getElementSelector(target),
|
|
357
|
+
tagName: target.tagName,
|
|
358
|
+
isEditable: target.isContentEditable ||
|
|
359
|
+
target.tagName === 'INPUT' ||
|
|
360
|
+
target.tagName === 'TEXTAREA',
|
|
361
|
+
hasSelection: (window.getSelection()?.toString().length ?? 0) > 0,
|
|
362
|
+
selectedText: window.getSelection()?.toString()
|
|
363
|
+
};
|
|
364
|
+
|
|
365
|
+
// Find appropriate menu for this element
|
|
366
|
+
let items = this.contextMenuItems;
|
|
367
|
+
|
|
368
|
+
for (const [selector, menuItems] of this.selectorMenus) {
|
|
369
|
+
if (target.matches(selector)) {
|
|
370
|
+
items = menuItems;
|
|
371
|
+
break;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// Show the menu
|
|
376
|
+
await this.showContextMenu(items, { x: info.x, y: info.y, context: info as unknown as Record<string, unknown> });
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
private getElementSelector(el: HTMLElement): string {
|
|
381
|
+
let selector = el.tagName.toLowerCase();
|
|
382
|
+
if (el.id) selector += '#' + el.id;
|
|
383
|
+
if (el.className && typeof el.className === 'string') {
|
|
384
|
+
selector += '.' + el.className.split(' ').filter(Boolean).join('.');
|
|
385
|
+
}
|
|
386
|
+
return selector;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
private registerClickHandlers(items: MenuItem[]): void {
|
|
390
|
+
for (const item of items) {
|
|
391
|
+
if (item.click) {
|
|
392
|
+
this.clickHandlers.set(item.id, item.click);
|
|
393
|
+
}
|
|
394
|
+
if (item.submenu) {
|
|
395
|
+
this.registerClickHandlers(item.submenu);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
private handleItemClick(id: string): void {
|
|
401
|
+
const handler = this.clickHandlers.get(id);
|
|
402
|
+
if (handler) {
|
|
403
|
+
handler({ id, label: '' });
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
private stripFunctions(items: MenuItem[]): unknown[] {
|
|
408
|
+
return items.map(item => {
|
|
409
|
+
const { click, submenu, ...rest } = item;
|
|
410
|
+
const cleanItem: Record<string, unknown> = { ...rest };
|
|
411
|
+
if (submenu) {
|
|
412
|
+
cleanItem.submenu = this.stripFunctions(submenu);
|
|
413
|
+
}
|
|
414
|
+
return cleanItem;
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* Cleanup resources
|
|
420
|
+
*/
|
|
421
|
+
dispose(): void {
|
|
422
|
+
this.unsubscribeClick?.();
|
|
423
|
+
this.clickHandlers.clear();
|
|
424
|
+
this.selectorMenus.clear();
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// ============================================================================
|
|
429
|
+
// Factory Function
|
|
430
|
+
// ============================================================================
|
|
431
|
+
|
|
432
|
+
export function createMenuAPI(
|
|
433
|
+
invokeFn: (name: string, args?: unknown[]) => Promise<unknown>,
|
|
434
|
+
eventFn: (event: string, callback: (...args: unknown[]) => void) => () => void
|
|
435
|
+
): MenuAPI {
|
|
436
|
+
return new MenuAPI(invokeFn, eventFn);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
export default MenuAPI;
|