@flighthq/menu 0.3.1-next.975.01aea94 → 0.4.0-edge.1908.751021c
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/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/menu.d.ts +6 -1
- package/dist/menu.d.ts.map +1 -1
- package/dist/menu.js +122 -94
- package/dist/menu.js.map +1 -1
- package/package.json +3 -3
- package/src/menu.test.ts +234 -126
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { cloneMenuTemplate, createDefaultAppMenuTemplate, createDefaultEditMenuTemplate, createDefaultFileMenuTemplate, createDefaultHelpMenuTemplate, createDefaultViewMenuTemplate, createDefaultWindowMenuTemplate, createMenuItemTemplate, enableMenuSignals, getMenuSignals, onMenuSelect, setApplicationMenu, showContextMenu, validateMenuItemTemplate, } from './contract';
|
|
1
|
+
export { cloneMenuTemplate, createDefaultAppMenuTemplate, createDefaultEditMenuTemplate, createDefaultFileMenuTemplate, createDefaultHelpMenuTemplate, createDefaultViewMenuTemplate, createDefaultWindowMenuTemplate, createMenuItemTemplate, enableMenuSignals, explainMenuBackend, getMenuSignals, onMenuSelect, setApplicationMenu, showContextMenu, validateMenuItemTemplate, } from './contract';
|
|
2
2
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,iBAAiB,EACjB,4BAA4B,EAC5B,6BAA6B,EAC7B,6BAA6B,EAC7B,6BAA6B,EAC7B,6BAA6B,EAC7B,+BAA+B,EAC/B,sBAAsB,EACtB,iBAAiB,EACjB,cAAc,EACd,YAAY,EACZ,kBAAkB,EAClB,eAAe,EACf,wBAAwB,GACzB,MAAM,YAAY,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,iBAAiB,EACjB,4BAA4B,EAC5B,6BAA6B,EAC7B,6BAA6B,EAC7B,6BAA6B,EAC7B,6BAA6B,EAC7B,+BAA+B,EAC/B,sBAAsB,EACtB,iBAAiB,EACjB,kBAAkB,EAClB,cAAc,EACd,YAAY,EACZ,kBAAkB,EAClB,eAAe,EACf,wBAAwB,GACzB,MAAM,YAAY,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { cloneMenuTemplate, createDefaultAppMenuTemplate, createDefaultEditMenuTemplate, createDefaultFileMenuTemplate, createDefaultHelpMenuTemplate, createDefaultViewMenuTemplate, createDefaultWindowMenuTemplate, createMenuItemTemplate, enableMenuSignals, getMenuSignals, onMenuSelect, setApplicationMenu, showContextMenu, validateMenuItemTemplate, } from './contract';
|
|
1
|
+
export { cloneMenuTemplate, createDefaultAppMenuTemplate, createDefaultEditMenuTemplate, createDefaultFileMenuTemplate, createDefaultHelpMenuTemplate, createDefaultViewMenuTemplate, createDefaultWindowMenuTemplate, createMenuItemTemplate, enableMenuSignals, explainMenuBackend, getMenuSignals, onMenuSelect, setApplicationMenu, showContextMenu, validateMenuItemTemplate, } from './contract';
|
|
2
2
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,iBAAiB,EACjB,4BAA4B,EAC5B,6BAA6B,EAC7B,6BAA6B,EAC7B,6BAA6B,EAC7B,6BAA6B,EAC7B,+BAA+B,EAC/B,sBAAsB,EACtB,iBAAiB,EACjB,cAAc,EACd,YAAY,EACZ,kBAAkB,EAClB,eAAe,EACf,wBAAwB,GACzB,MAAM,YAAY,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,iBAAiB,EACjB,4BAA4B,EAC5B,6BAA6B,EAC7B,6BAA6B,EAC7B,6BAA6B,EAC7B,6BAA6B,EAC7B,+BAA+B,EAC/B,sBAAsB,EACtB,iBAAiB,EACjB,kBAAkB,EAClB,cAAc,EACd,YAAY,EACZ,kBAAkB,EAClB,eAAe,EACf,wBAAwB,GACzB,MAAM,YAAY,CAAC"}
|
package/dist/menu.d.ts
CHANGED
|
@@ -1,13 +1,18 @@
|
|
|
1
|
+
import type { BackendExplanation } from '@flighthq/types/contract';
|
|
1
2
|
import type { MenuBackend, MenuItemTemplate, MenuSignals } from '@flighthq/types/contract';
|
|
2
3
|
export declare function cloneMenuTemplate(template: Readonly<MenuItemTemplate>): MenuItemTemplate;
|
|
3
4
|
export declare function createMenuItemTemplate(template?: Readonly<Partial<MenuItemTemplate>>): MenuItemTemplate;
|
|
4
|
-
export declare function createWebMenuBackend(): MenuBackend;
|
|
5
5
|
export declare function enableMenuSignals(): MenuSignals;
|
|
6
|
+
export declare function explainMenuBackend(): BackendExplanation;
|
|
6
7
|
export declare function getMenuBackend(): MenuBackend;
|
|
7
8
|
export declare function getMenuSignals(): Readonly<MenuSignals> | null;
|
|
9
|
+
export declare function installMenuHostBackend(backend: MenuBackend): void;
|
|
10
|
+
export declare function observeMenuHostResult(operation: string, succeeded: boolean): void;
|
|
8
11
|
export declare function onMenuSelect(listener: (id: string) => void): () => void;
|
|
12
|
+
export declare function resetMenuBackendForTest(): void;
|
|
9
13
|
export declare function setApplicationMenu(items: readonly MenuItemTemplate[]): boolean;
|
|
10
14
|
export declare function setMenuBackend(backend: MenuBackend | null): void;
|
|
11
15
|
export declare function showContextMenu(items: readonly MenuItemTemplate[], x: number, y: number): Promise<string | null>;
|
|
16
|
+
export declare function showWebContextMenu(items: readonly MenuItemTemplate[], x: number, y: number): Promise<string | null>;
|
|
12
17
|
export declare function validateMenuItemTemplate(template: Readonly<MenuItemTemplate>): string | null;
|
|
13
18
|
//# sourceMappingURL=menu.d.ts.map
|
package/dist/menu.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"menu.d.ts","sourceRoot":"","sources":["../src/menu.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAI3F,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,QAAQ,CAAC,gBAAgB,CAAC,GAAG,gBAAgB,CAMxF;AAKD,wBAAgB,sBAAsB,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC,GAAG,gBAAgB,CAUvG;AAKD,wBAAgB,
|
|
1
|
+
{"version":3,"file":"menu.d.ts","sourceRoot":"","sources":["../src/menu.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AACnE,OAAO,KAAK,EAAE,WAAW,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAI3F,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,QAAQ,CAAC,gBAAgB,CAAC,GAAG,gBAAgB,CAMxF;AAKD,wBAAgB,sBAAsB,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC,GAAG,gBAAgB,CAUvG;AAKD,wBAAgB,iBAAiB,IAAI,WAAW,CAU/C;AAED,wBAAgB,kBAAkB,IAAI,kBAAkB,CAavD;AAGD,wBAAgB,cAAc,IAAI,WAAW,CAE5C;AAGD,wBAAgB,cAAc,IAAI,QAAQ,CAAC,WAAW,CAAC,GAAG,IAAI,CAE7D;AAED,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI,CAMjE;AAED,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,GAAG,IAAI,CAKjF;AAKD,wBAAgB,YAAY,CAAC,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,GAAG,MAAM,IAAI,CAKvE;AAED,wBAAgB,uBAAuB,IAAI,IAAI,CAK9C;AAID,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,SAAS,gBAAgB,EAAE,GAAG,OAAO,CAE9E;AAGD,wBAAgB,cAAc,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI,GAAG,IAAI,CAEhE;AAKD,wBAAgB,eAAe,CAAC,KAAK,EAAE,SAAS,gBAAgB,EAAE,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAQhH;AASD,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,SAAS,gBAAgB,EAAE,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAiFnH;AAkFD,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,QAAQ,CAAC,gBAAgB,CAAC,GAAG,MAAM,GAAG,IAAI,CAE5F"}
|
package/dist/menu.js
CHANGED
|
@@ -22,24 +22,6 @@ export function createMenuItemTemplate(template) {
|
|
|
22
22
|
}
|
|
23
23
|
return item;
|
|
24
24
|
}
|
|
25
|
-
// Builds the default web backend. The app-menu bar returns false (native host required for a true menu
|
|
26
|
-
// bar). popupContextMenu delegates to the DOM-rendered context-menu popup. subscribeSelect never fires
|
|
27
|
-
// on web (no native app menu).
|
|
28
|
-
export function createWebMenuBackend() {
|
|
29
|
-
return {
|
|
30
|
-
setApplicationMenu() {
|
|
31
|
-
// Web has no native menu bar; return false (not an error — caller should handle gracefully).
|
|
32
|
-
return false;
|
|
33
|
-
},
|
|
34
|
-
popupContextMenu(items, x, y) {
|
|
35
|
-
return showWebContextMenu(items, x, y);
|
|
36
|
-
},
|
|
37
|
-
subscribeSelect() {
|
|
38
|
-
// Web app-menu has no select source — native host required.
|
|
39
|
-
return () => { };
|
|
40
|
-
},
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
25
|
// Activates the optional MenuSignals group and returns it. Calling this is when the cost is assumed.
|
|
44
26
|
// The returned object is shared for the lifetime of the package; calling enableMenuSignals multiple
|
|
45
27
|
// times returns the same instance. Connect slots via connectSignal from @flighthq/signals.
|
|
@@ -54,16 +36,42 @@ export function enableMenuSignals() {
|
|
|
54
36
|
}
|
|
55
37
|
return _menuSignals;
|
|
56
38
|
}
|
|
57
|
-
|
|
39
|
+
export function explainMenuBackend() {
|
|
40
|
+
if (_custom !== null) {
|
|
41
|
+
return { conflict: _hostConflict, layer: 'custom', operation: null, viability: 'unobserved' };
|
|
42
|
+
}
|
|
43
|
+
if (_host !== null) {
|
|
44
|
+
return {
|
|
45
|
+
conflict: _hostConflict,
|
|
46
|
+
layer: 'host',
|
|
47
|
+
operation: _hostObservation !== null ? _hostObservation.operation : null,
|
|
48
|
+
viability: _hostObservation !== null ? _hostObservation.viability : 'unobserved',
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
return { conflict: false, layer: 'host-not-enabled', operation: null, viability: 'unobserved' };
|
|
52
|
+
}
|
|
53
|
+
// Returns the active backend following the precedence chain: custom > host > sentinel.
|
|
58
54
|
export function getMenuBackend() {
|
|
59
|
-
|
|
60
|
-
_backend = createWebMenuBackend();
|
|
61
|
-
return _backend;
|
|
55
|
+
return _custom ?? _host ?? _sentinel;
|
|
62
56
|
}
|
|
63
57
|
// Returns the active MenuSignals group, or null if enableMenuSignals has not been called.
|
|
64
58
|
export function getMenuSignals() {
|
|
65
59
|
return _menuSignals;
|
|
66
60
|
}
|
|
61
|
+
export function installMenuHostBackend(backend) {
|
|
62
|
+
if (_host !== null) {
|
|
63
|
+
if (_host !== backend)
|
|
64
|
+
_hostConflict = true;
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
_host = backend;
|
|
68
|
+
}
|
|
69
|
+
export function observeMenuHostResult(operation, succeeded) {
|
|
70
|
+
_hostObservation = {
|
|
71
|
+
operation,
|
|
72
|
+
viability: succeeded ? 'available' : 'runtime-api-unavailable',
|
|
73
|
+
};
|
|
74
|
+
}
|
|
67
75
|
// Subscribes to application menu item selections by item id. Returns an unsubscribe function. On web
|
|
68
76
|
// this never fires (no native app menu bar). Selections are also fanned out to onMenuItemSelect when
|
|
69
77
|
// the MenuSignals group is enabled.
|
|
@@ -74,14 +82,20 @@ export function onMenuSelect(listener) {
|
|
|
74
82
|
emitSignal(_menuSignals.onMenuItemSelect, id);
|
|
75
83
|
});
|
|
76
84
|
}
|
|
85
|
+
export function resetMenuBackendForTest() {
|
|
86
|
+
_custom = null;
|
|
87
|
+
_host = null;
|
|
88
|
+
_hostConflict = false;
|
|
89
|
+
_hostObservation = null;
|
|
90
|
+
}
|
|
77
91
|
// Installs the application menu bar. Returns true on success, or false when the host lacks a native
|
|
78
92
|
// menu bar (e.g. web).
|
|
79
93
|
export function setApplicationMenu(items) {
|
|
80
94
|
return getMenuBackend().setApplicationMenu(items);
|
|
81
95
|
}
|
|
82
|
-
// Sets
|
|
96
|
+
// Sets a custom menu backend; pass null to clear and fall back to the host or sentinel.
|
|
83
97
|
export function setMenuBackend(backend) {
|
|
84
|
-
|
|
98
|
+
_custom = backend;
|
|
85
99
|
}
|
|
86
100
|
// Pops up a context menu at (x, y) and resolves the clicked item id, or null when dismissed. On web,
|
|
87
101
|
// renders a positioned DOM popup (separators, enabled/checked rendering, dismiss on outside-click /
|
|
@@ -96,82 +110,14 @@ export function showContextMenu(items, x, y) {
|
|
|
96
110
|
}
|
|
97
111
|
return promise;
|
|
98
112
|
}
|
|
99
|
-
// Validates a MenuItemTemplate tree for consistency. Returns null on success, or a string describing
|
|
100
|
-
// the first violation found. Does not throw — returns a sentinel for expected failures. Throws only
|
|
101
|
-
// for cyclic submenu references (programmer error).
|
|
102
|
-
export function validateMenuItemTemplate(template) {
|
|
103
|
-
return _validateItem(template, new Set());
|
|
104
|
-
}
|
|
105
|
-
let _backend = null;
|
|
106
|
-
let _menuSignals = null;
|
|
107
|
-
function _validateItem(item, seen) {
|
|
108
|
-
if (seen.has(item)) {
|
|
109
|
-
throw new Error('validateMenuItemTemplate: cyclic submenu reference detected');
|
|
110
|
-
}
|
|
111
|
-
if (item.type === 'separator') {
|
|
112
|
-
if (item.label !== undefined && item.label !== '') {
|
|
113
|
-
return `separator item has a label: "${item.label}" (separators should not have labels)`;
|
|
114
|
-
}
|
|
115
|
-
if (item.accelerator !== undefined) {
|
|
116
|
-
return `separator item has an accelerator: "${item.accelerator}"`;
|
|
117
|
-
}
|
|
118
|
-
if (item.submenu !== undefined) {
|
|
119
|
-
return 'separator item has a submenu';
|
|
120
|
-
}
|
|
121
|
-
return null;
|
|
122
|
-
}
|
|
123
|
-
if (item.type !== 'submenu' && item.submenu !== undefined && item.submenu.length > 0) {
|
|
124
|
-
return `item type "${item.type ?? 'normal'}" has a submenu (only type "submenu" should carry children)`;
|
|
125
|
-
}
|
|
126
|
-
// `checked` only means something on the two toggle types. Setting it elsewhere is the mistake that
|
|
127
|
-
// renders silently: the web backend draws the checkmark from `checked` alone, so a normal item with
|
|
128
|
-
// checked: true grows a tick it can never clear, and native backends typically drop it instead —
|
|
129
|
-
// same descriptor, two different wrong results, which is exactly what validation is for.
|
|
130
|
-
if (item.checked !== undefined && item.type !== 'checkbox' && item.type !== 'radio') {
|
|
131
|
-
return `item type "${item.type ?? 'normal'}" has "checked" (only "checkbox" and "radio" items are checkable)`;
|
|
132
|
-
}
|
|
133
|
-
if (item.submenu !== undefined) {
|
|
134
|
-
const groupError = _validateRadioGroups(item.submenu);
|
|
135
|
-
if (groupError !== null)
|
|
136
|
-
return groupError;
|
|
137
|
-
seen.add(item);
|
|
138
|
-
for (const child of item.submenu) {
|
|
139
|
-
const err = _validateItem(child, seen);
|
|
140
|
-
if (err !== null)
|
|
141
|
-
return err;
|
|
142
|
-
}
|
|
143
|
-
seen.delete(item);
|
|
144
|
-
}
|
|
145
|
-
return null;
|
|
146
|
-
}
|
|
147
|
-
// A radio group is a run of adjacent radio items — any other item type, including a separator, starts
|
|
148
|
-
// a new one. Exactly one member of a run may be checked; two checked members describe a state the
|
|
149
|
-
// widget cannot represent, and each backend picks a different winner. Checked on a non-radio item is
|
|
150
|
-
// caught per-item above; this is the rule that only exists across siblings, which is why it runs where
|
|
151
|
-
// the child list is known rather than inside the per-item walk.
|
|
152
|
-
function _validateRadioGroups(items) {
|
|
153
|
-
let checkedInRun = 0;
|
|
154
|
-
for (const item of items) {
|
|
155
|
-
if (item.type !== 'radio') {
|
|
156
|
-
checkedInRun = 0;
|
|
157
|
-
continue;
|
|
158
|
-
}
|
|
159
|
-
if (item.checked === true)
|
|
160
|
-
checkedInRun++;
|
|
161
|
-
if (checkedInRun > 1) {
|
|
162
|
-
return `radio group has ${checkedInRun} checked items (a radio group may have at most one)`;
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
return null;
|
|
166
|
-
}
|
|
167
|
-
// --- Web context-menu renderer ---
|
|
168
113
|
// Renders a minimal DOM popup for showContextMenu in a browser environment. Returns the clicked item
|
|
169
114
|
// id or null when the menu is dismissed without a selection. Keyboard support is ArrowUp/ArrowDown
|
|
170
115
|
// within one menu level, plus Enter/Space to select and Escape to dismiss. Submenus open on hover
|
|
171
116
|
// only: there is no ArrowRight/ArrowLeft traversal, so a submenu's rows are unreachable by keyboard,
|
|
172
117
|
// and Enter on a submenu parent currently resolves the popup with that parent's id rather than
|
|
173
118
|
// opening it. Both are recorded as open work in the package assessment rather than fixed here.
|
|
174
|
-
|
|
119
|
+
// Exported so the host-web enabler can delegate to this DOM rendering implementation.
|
|
120
|
+
export function showWebContextMenu(items, x, y) {
|
|
175
121
|
return new Promise((resolve) => {
|
|
176
122
|
if (typeof document === 'undefined') {
|
|
177
123
|
resolve(null);
|
|
@@ -262,6 +208,88 @@ function showWebContextMenu(items, x, y) {
|
|
|
262
208
|
clampMenu(menu, x, y);
|
|
263
209
|
});
|
|
264
210
|
}
|
|
211
|
+
let _custom = null;
|
|
212
|
+
let _host = null;
|
|
213
|
+
let _hostConflict = false;
|
|
214
|
+
let _hostObservation = null;
|
|
215
|
+
let _menuSignals = null;
|
|
216
|
+
const _sentinel = {
|
|
217
|
+
async popupContextMenu() {
|
|
218
|
+
return null;
|
|
219
|
+
},
|
|
220
|
+
setApplicationMenu() {
|
|
221
|
+
return false;
|
|
222
|
+
},
|
|
223
|
+
subscribeSelect() {
|
|
224
|
+
return () => { };
|
|
225
|
+
},
|
|
226
|
+
};
|
|
227
|
+
function _validateItem(item, seen) {
|
|
228
|
+
if (seen.has(item)) {
|
|
229
|
+
throw new Error('validateMenuItemTemplate: cyclic submenu reference detected');
|
|
230
|
+
}
|
|
231
|
+
if (item.type === 'separator') {
|
|
232
|
+
if (item.label !== undefined && item.label !== '') {
|
|
233
|
+
return `separator item has a label: "${item.label}" (separators should not have labels)`;
|
|
234
|
+
}
|
|
235
|
+
if (item.accelerator !== undefined) {
|
|
236
|
+
return `separator item has an accelerator: "${item.accelerator}"`;
|
|
237
|
+
}
|
|
238
|
+
if (item.submenu !== undefined) {
|
|
239
|
+
return 'separator item has a submenu';
|
|
240
|
+
}
|
|
241
|
+
return null;
|
|
242
|
+
}
|
|
243
|
+
if (item.type !== 'submenu' && item.submenu !== undefined && item.submenu.length > 0) {
|
|
244
|
+
return `item type "${item.type ?? 'normal'}" has a submenu (only type "submenu" should carry children)`;
|
|
245
|
+
}
|
|
246
|
+
// `checked` only means something on the two toggle types. Setting it elsewhere is the mistake that
|
|
247
|
+
// renders silently: the web backend draws the checkmark from `checked` alone, so a normal item with
|
|
248
|
+
// checked: true grows a tick it can never clear, and native backends typically drop it instead —
|
|
249
|
+
// same descriptor, two different wrong results, which is exactly what validation is for.
|
|
250
|
+
if (item.checked !== undefined && item.type !== 'checkbox' && item.type !== 'radio') {
|
|
251
|
+
return `item type "${item.type ?? 'normal'}" has "checked" (only "checkbox" and "radio" items are checkable)`;
|
|
252
|
+
}
|
|
253
|
+
if (item.submenu !== undefined) {
|
|
254
|
+
const groupError = _validateRadioGroups(item.submenu);
|
|
255
|
+
if (groupError !== null)
|
|
256
|
+
return groupError;
|
|
257
|
+
seen.add(item);
|
|
258
|
+
for (const child of item.submenu) {
|
|
259
|
+
const err = _validateItem(child, seen);
|
|
260
|
+
if (err !== null)
|
|
261
|
+
return err;
|
|
262
|
+
}
|
|
263
|
+
seen.delete(item);
|
|
264
|
+
}
|
|
265
|
+
return null;
|
|
266
|
+
}
|
|
267
|
+
// A radio group is a run of adjacent radio items — any other item type, including a separator, starts
|
|
268
|
+
// a new one. Exactly one member of a run may be checked; two checked members describe a state the
|
|
269
|
+
// widget cannot represent, and each backend picks a different winner. Checked on a non-radio item is
|
|
270
|
+
// caught per-item above; this is the rule that only exists across siblings, which is why it runs where
|
|
271
|
+
// the child list is known rather than inside the per-item walk.
|
|
272
|
+
function _validateRadioGroups(items) {
|
|
273
|
+
let checkedInRun = 0;
|
|
274
|
+
for (const item of items) {
|
|
275
|
+
if (item.type !== 'radio') {
|
|
276
|
+
checkedInRun = 0;
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
if (item.checked === true)
|
|
280
|
+
checkedInRun++;
|
|
281
|
+
if (checkedInRun > 1) {
|
|
282
|
+
return `radio group has ${checkedInRun} checked items (a radio group may have at most one)`;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
return null;
|
|
286
|
+
}
|
|
287
|
+
// Validates a MenuItemTemplate tree for consistency. Returns null on success, or a string describing
|
|
288
|
+
// the first violation found. Does not throw — returns a sentinel for expected failures. Throws only
|
|
289
|
+
// for cyclic submenu references (programmer error).
|
|
290
|
+
export function validateMenuItemTemplate(template) {
|
|
291
|
+
return _validateItem(template, new Set());
|
|
292
|
+
}
|
|
265
293
|
function buildWebMenuElement(items, onSelect) {
|
|
266
294
|
const menu = document.createElement('ul');
|
|
267
295
|
menu.style.cssText = [
|
package/dist/menu.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"menu.js","sourceRoot":"","sources":["../src/menu.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"menu.js","sourceRoot":"","sources":["../src/menu.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flighthq/menu",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0-edge.1908.751021c",
|
|
4
4
|
"author": "Joshua Granick and other contributors",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -38,8 +38,8 @@
|
|
|
38
38
|
"clean:dist": "tsx ../../scripts/clean-package-dist.ts"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@flighthq/signals": "0.
|
|
42
|
-
"@flighthq/types": "0.
|
|
41
|
+
"@flighthq/signals": "0.4.0-edge.1908.751021c",
|
|
42
|
+
"@flighthq/types": "0.4.0-edge.1908.751021c"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
45
|
"typescript": "^5.3.0"
|
package/src/menu.test.ts
CHANGED
|
@@ -3,14 +3,18 @@ import type { MenuBackend, MenuItemTemplate } from '@flighthq/types/contract';
|
|
|
3
3
|
import {
|
|
4
4
|
cloneMenuTemplate,
|
|
5
5
|
createMenuItemTemplate,
|
|
6
|
-
createWebMenuBackend,
|
|
7
6
|
enableMenuSignals,
|
|
7
|
+
explainMenuBackend,
|
|
8
8
|
getMenuBackend,
|
|
9
9
|
getMenuSignals,
|
|
10
|
+
installMenuHostBackend,
|
|
11
|
+
observeMenuHostResult,
|
|
10
12
|
onMenuSelect,
|
|
13
|
+
resetMenuBackendForTest,
|
|
11
14
|
setApplicationMenu,
|
|
12
15
|
setMenuBackend,
|
|
13
16
|
showContextMenu,
|
|
17
|
+
showWebContextMenu,
|
|
14
18
|
validateMenuItemTemplate,
|
|
15
19
|
} from './menu';
|
|
16
20
|
|
|
@@ -52,160 +56,132 @@ describe('createMenuItemTemplate', () => {
|
|
|
52
56
|
});
|
|
53
57
|
});
|
|
54
58
|
|
|
55
|
-
describe('
|
|
56
|
-
it('
|
|
57
|
-
|
|
58
|
-
expect(backend.setApplicationMenu([])).toBe(false);
|
|
59
|
+
describe('enableMenuSignals', () => {
|
|
60
|
+
it('returns a stable instance across calls', () => {
|
|
61
|
+
expect(enableMenuSignals()).toBe(enableMenuSignals());
|
|
59
62
|
});
|
|
63
|
+
});
|
|
60
64
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
65
|
+
describe('explainMenuBackend', () => {
|
|
66
|
+
afterEach(() => resetMenuBackendForTest());
|
|
67
|
+
|
|
68
|
+
it('reports host-not-enabled when no backend is installed', () => {
|
|
69
|
+
resetMenuBackendForTest();
|
|
70
|
+
const explanation = explainMenuBackend();
|
|
71
|
+
expect(explanation.layer).toBe('host-not-enabled');
|
|
72
|
+
expect(explanation.conflict).toBe(false);
|
|
73
|
+
expect(explanation.viability).toBe('unobserved');
|
|
64
74
|
});
|
|
65
|
-
});
|
|
66
75
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
vi.restoreAllMocks();
|
|
76
|
+
it('reports custom layer when a custom backend is set', () => {
|
|
77
|
+
setMenuBackend(fakeBackend());
|
|
78
|
+
expect(explainMenuBackend().layer).toBe('custom');
|
|
71
79
|
});
|
|
72
80
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
expect(
|
|
76
|
-
|
|
77
|
-
}
|
|
81
|
+
it('reports host layer when a host backend is installed', () => {
|
|
82
|
+
installMenuHostBackend(fakeBackend());
|
|
83
|
+
expect(explainMenuBackend().layer).toBe('host');
|
|
84
|
+
});
|
|
78
85
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
}
|
|
86
|
+
it('reports conflict when two different host backends are installed', () => {
|
|
87
|
+
installMenuHostBackend(fakeBackend());
|
|
88
|
+
installMenuHostBackend(fakeBackend());
|
|
89
|
+
expect(explainMenuBackend().conflict).toBe(true);
|
|
90
|
+
});
|
|
84
91
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
92
|
+
it('reports observed viability after observeMenuHostResult', () => {
|
|
93
|
+
installMenuHostBackend(fakeBackend());
|
|
94
|
+
observeMenuHostResult('popupContextMenu', true);
|
|
95
|
+
const explanation = explainMenuBackend();
|
|
96
|
+
expect(explanation.operation).toBe('popupContextMenu');
|
|
97
|
+
expect(explanation.viability).toBe('available');
|
|
98
|
+
});
|
|
99
|
+
});
|
|
88
100
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
[
|
|
92
|
-
{ id: 'checked', label: 'Checked', type: 'checkbox', checked: true },
|
|
93
|
-
{ id: 'chosen', label: 'Chosen', type: 'radio', checked: true },
|
|
94
|
-
{ type: 'separator' },
|
|
95
|
-
{ id: 'save', label: 'Save', accelerator: 'Ctrl+S' },
|
|
96
|
-
{ id: 'disabled', label: 'Disabled', enabled: false },
|
|
97
|
-
],
|
|
98
|
-
10,
|
|
99
|
-
20,
|
|
100
|
-
);
|
|
101
|
-
const items = getRootMenu().querySelectorAll(':scope > li');
|
|
102
|
-
expect(items).toHaveLength(5);
|
|
103
|
-
expect(items[0].textContent).toBe('✓Checked');
|
|
104
|
-
expect(items[1].textContent).toBe('●Chosen');
|
|
105
|
-
expect(items[2].getAttribute('data-enabled')).toBeNull();
|
|
106
|
-
expect(items[3].textContent).toBe('SaveCtrl+S');
|
|
107
|
-
expect(items[4].getAttribute('data-enabled')).toBe('false');
|
|
101
|
+
describe('getMenuBackend', () => {
|
|
102
|
+
afterEach(() => resetMenuBackendForTest());
|
|
108
103
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
});
|
|
113
|
-
items[4].dispatchEvent(new MouseEvent('click', { bubbles: true }));
|
|
114
|
-
await Promise.resolve();
|
|
115
|
-
expect(settled).toBe(false);
|
|
116
|
-
getOverlay().click();
|
|
117
|
-
await expect(promise).resolves.toBeNull();
|
|
104
|
+
it('returns the sentinel when no backend is installed', () => {
|
|
105
|
+
resetMenuBackendForTest();
|
|
106
|
+
expect(getMenuBackend()).not.toBeNull();
|
|
118
107
|
});
|
|
108
|
+
});
|
|
119
109
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
const selected = backend.popupContextMenu([{ id: 'copy', label: 'Copy' }], 0, 0);
|
|
123
|
-
getRootMenu().querySelector<HTMLElement>('li')!.click();
|
|
124
|
-
await expect(selected).resolves.toBe('copy');
|
|
125
|
-
expect(document.body.querySelector('ul')).toBeNull();
|
|
110
|
+
describe('getMenuBackend sentinel', () => {
|
|
111
|
+
afterEach(() => resetMenuBackendForTest());
|
|
126
112
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
113
|
+
it('reports no native application menu', () => {
|
|
114
|
+
resetMenuBackendForTest();
|
|
115
|
+
const backend = getMenuBackend();
|
|
116
|
+
expect(backend.setApplicationMenu([])).toBe(false);
|
|
130
117
|
});
|
|
131
118
|
|
|
132
|
-
it('
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
];
|
|
138
|
-
|
|
139
|
-
const enterSelection = createWebMenuBackend().popupContextMenu(items, 0, 0);
|
|
140
|
-
press('ArrowUp');
|
|
141
|
-
expect(getRootMenu().querySelector('[data-focused="true"]')?.getAttribute('data-item-id')).toBe('last');
|
|
142
|
-
press('Enter');
|
|
143
|
-
await expect(enterSelection).resolves.toBe('last');
|
|
119
|
+
it('returns an unsubscribe function from subscribeSelect', () => {
|
|
120
|
+
resetMenuBackendForTest();
|
|
121
|
+
const backend = getMenuBackend();
|
|
122
|
+
expect(typeof backend.subscribeSelect(() => {})).toBe('function');
|
|
123
|
+
});
|
|
144
124
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
expect(getRootMenu().querySelector('[data-focused="true"]')?.getAttribute('data-item-id')).toBe('first');
|
|
150
|
-
press(' ');
|
|
151
|
-
await expect(spaceSelection).resolves.toBe('first');
|
|
125
|
+
it('returns null from popupContextMenu', async () => {
|
|
126
|
+
resetMenuBackendForTest();
|
|
127
|
+
const backend = getMenuBackend();
|
|
128
|
+
await expect(backend.popupContextMenu([], 0, 0)).resolves.toBeNull();
|
|
152
129
|
});
|
|
130
|
+
});
|
|
153
131
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
132
|
+
describe('getMenuSignals', () => {
|
|
133
|
+
it('returns the active signal group once enabled', () => {
|
|
134
|
+
const signals = enableMenuSignals();
|
|
135
|
+
expect(getMenuSignals()).toBe(signals);
|
|
158
136
|
});
|
|
137
|
+
});
|
|
159
138
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
);
|
|
166
|
-
|
|
167
|
-
const submenu = parent.querySelector<HTMLElement>('ul')!;
|
|
168
|
-
expect(submenu.style.display).toBe('none');
|
|
169
|
-
parent.dispatchEvent(new MouseEvent('mouseenter'));
|
|
170
|
-
expect(submenu.style.display).toBe('block');
|
|
171
|
-
submenu.querySelector<HTMLElement>('li')!.click();
|
|
172
|
-
await expect(promise).resolves.toBe('open');
|
|
139
|
+
describe('installMenuHostBackend', () => {
|
|
140
|
+
afterEach(() => resetMenuBackendForTest());
|
|
141
|
+
|
|
142
|
+
it('installs a host backend that getMenuBackend returns', () => {
|
|
143
|
+
const backend = fakeBackend();
|
|
144
|
+
installMenuHostBackend(backend);
|
|
145
|
+
expect(getMenuBackend()).toBe(backend);
|
|
173
146
|
});
|
|
174
147
|
|
|
175
|
-
it('
|
|
176
|
-
|
|
177
|
-
const
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
148
|
+
it('is first-host-wins: a second different backend sets conflict', () => {
|
|
149
|
+
const first = fakeBackend();
|
|
150
|
+
const second = fakeBackend();
|
|
151
|
+
installMenuHostBackend(first);
|
|
152
|
+
installMenuHostBackend(second);
|
|
153
|
+
expect(getMenuBackend()).toBe(first);
|
|
154
|
+
expect(explainMenuBackend().conflict).toBe(true);
|
|
182
155
|
});
|
|
183
|
-
});
|
|
184
156
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
157
|
+
it('does not set conflict when the same backend is installed twice', () => {
|
|
158
|
+
const backend = fakeBackend();
|
|
159
|
+
installMenuHostBackend(backend);
|
|
160
|
+
installMenuHostBackend(backend);
|
|
161
|
+
expect(explainMenuBackend().conflict).toBe(false);
|
|
188
162
|
});
|
|
189
163
|
});
|
|
190
164
|
|
|
191
|
-
describe('
|
|
192
|
-
afterEach(() =>
|
|
165
|
+
describe('observeMenuHostResult', () => {
|
|
166
|
+
afterEach(() => resetMenuBackendForTest());
|
|
193
167
|
|
|
194
|
-
it('
|
|
195
|
-
|
|
196
|
-
|
|
168
|
+
it('records a successful observation', () => {
|
|
169
|
+
installMenuHostBackend(fakeBackend());
|
|
170
|
+
observeMenuHostResult('popupContextMenu', true);
|
|
171
|
+
const explanation = explainMenuBackend();
|
|
172
|
+
expect(explanation.operation).toBe('popupContextMenu');
|
|
173
|
+
expect(explanation.viability).toBe('available');
|
|
197
174
|
});
|
|
198
|
-
});
|
|
199
175
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
expect(
|
|
176
|
+
it('records a failed observation', () => {
|
|
177
|
+
installMenuHostBackend(fakeBackend());
|
|
178
|
+
observeMenuHostResult('popupContextMenu', false);
|
|
179
|
+
expect(explainMenuBackend().viability).toBe('runtime-api-unavailable');
|
|
204
180
|
});
|
|
205
181
|
});
|
|
206
182
|
|
|
207
183
|
describe('onMenuSelect', () => {
|
|
208
|
-
afterEach(() =>
|
|
184
|
+
afterEach(() => resetMenuBackendForTest());
|
|
209
185
|
|
|
210
186
|
it('delivers the selected id from the backend', () => {
|
|
211
187
|
let captured: ((id: string) => void) | null = null;
|
|
@@ -224,8 +200,22 @@ describe('onMenuSelect', () => {
|
|
|
224
200
|
});
|
|
225
201
|
});
|
|
226
202
|
|
|
203
|
+
describe('resetMenuBackendForTest', () => {
|
|
204
|
+
it('clears all backend slots', () => {
|
|
205
|
+
const backend = fakeBackend();
|
|
206
|
+
setMenuBackend(backend);
|
|
207
|
+
installMenuHostBackend(fakeBackend());
|
|
208
|
+
observeMenuHostResult('popupContextMenu', true);
|
|
209
|
+
resetMenuBackendForTest();
|
|
210
|
+
expect(getMenuBackend()).not.toBe(backend);
|
|
211
|
+
expect(explainMenuBackend().layer).toBe('host-not-enabled');
|
|
212
|
+
expect(explainMenuBackend().conflict).toBe(false);
|
|
213
|
+
expect(explainMenuBackend().viability).toBe('unobserved');
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
|
|
227
217
|
describe('setApplicationMenu', () => {
|
|
228
|
-
afterEach(() =>
|
|
218
|
+
afterEach(() => resetMenuBackendForTest());
|
|
229
219
|
|
|
230
220
|
it('delegates to the active backend', () => {
|
|
231
221
|
let installed = 0;
|
|
@@ -243,9 +233,9 @@ describe('setApplicationMenu', () => {
|
|
|
243
233
|
});
|
|
244
234
|
|
|
245
235
|
describe('setMenuBackend', () => {
|
|
246
|
-
afterEach(() =>
|
|
236
|
+
afterEach(() => resetMenuBackendForTest());
|
|
247
237
|
|
|
248
|
-
it('installs
|
|
238
|
+
it('installs a custom backend and reverts to the sentinel on null', () => {
|
|
249
239
|
const backend = fakeBackend();
|
|
250
240
|
setMenuBackend(backend);
|
|
251
241
|
expect(getMenuBackend()).toBe(backend);
|
|
@@ -255,7 +245,7 @@ describe('setMenuBackend', () => {
|
|
|
255
245
|
});
|
|
256
246
|
|
|
257
247
|
describe('showContextMenu', () => {
|
|
258
|
-
afterEach(() =>
|
|
248
|
+
afterEach(() => resetMenuBackendForTest());
|
|
259
249
|
|
|
260
250
|
it('resolves the id returned by the backend popup', async () => {
|
|
261
251
|
setMenuBackend(fakeBackend({ popupContextMenu: () => Promise.resolve('copy') }));
|
|
@@ -273,7 +263,8 @@ describe('showContextMenu', () => {
|
|
|
273
263
|
];
|
|
274
264
|
|
|
275
265
|
beforeEach(() => {
|
|
276
|
-
|
|
266
|
+
resetMenuBackendForTest();
|
|
267
|
+
setMenuBackend(fakeBackend({ popupContextMenu: showWebContextMenu }));
|
|
277
268
|
document.body.innerHTML = '';
|
|
278
269
|
});
|
|
279
270
|
|
|
@@ -414,6 +405,123 @@ describe('showContextMenu', () => {
|
|
|
414
405
|
});
|
|
415
406
|
});
|
|
416
407
|
|
|
408
|
+
describe('showWebContextMenu', () => {
|
|
409
|
+
afterEach(() => {
|
|
410
|
+
document.body.replaceChildren();
|
|
411
|
+
vi.restoreAllMocks();
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
function getRootMenu(): HTMLUListElement {
|
|
415
|
+
const menu = document.body.querySelector('ul');
|
|
416
|
+
expect(menu).not.toBeNull();
|
|
417
|
+
return menu as HTMLUListElement;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
function getOverlay(): HTMLDivElement {
|
|
421
|
+
const overlay = document.body.querySelector('div');
|
|
422
|
+
expect(overlay).not.toBeNull();
|
|
423
|
+
return overlay as HTMLDivElement;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
function press(key: string): void {
|
|
427
|
+
document.dispatchEvent(new KeyboardEvent('keydown', { key }));
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
it('renders separators, checked kinds, accelerators, and disabled items', async () => {
|
|
431
|
+
const promise = showWebContextMenu(
|
|
432
|
+
[
|
|
433
|
+
{ id: 'checked', label: 'Checked', type: 'checkbox', checked: true },
|
|
434
|
+
{ id: 'chosen', label: 'Chosen', type: 'radio', checked: true },
|
|
435
|
+
{ type: 'separator' },
|
|
436
|
+
{ id: 'save', label: 'Save', accelerator: 'Ctrl+S' },
|
|
437
|
+
{ id: 'disabled', label: 'Disabled', enabled: false },
|
|
438
|
+
],
|
|
439
|
+
10,
|
|
440
|
+
20,
|
|
441
|
+
);
|
|
442
|
+
const items = getRootMenu().querySelectorAll(':scope > li');
|
|
443
|
+
expect(items).toHaveLength(5);
|
|
444
|
+
expect(items[0].textContent).toBe('✓Checked');
|
|
445
|
+
expect(items[1].textContent).toBe('●Chosen');
|
|
446
|
+
expect(items[2].getAttribute('data-enabled')).toBeNull();
|
|
447
|
+
expect(items[3].textContent).toBe('SaveCtrl+S');
|
|
448
|
+
expect(items[4].getAttribute('data-enabled')).toBe('false');
|
|
449
|
+
|
|
450
|
+
let settled = false;
|
|
451
|
+
void promise.then(() => {
|
|
452
|
+
settled = true;
|
|
453
|
+
});
|
|
454
|
+
items[4].dispatchEvent(new MouseEvent('click', { bubbles: true }));
|
|
455
|
+
await Promise.resolve();
|
|
456
|
+
expect(settled).toBe(false);
|
|
457
|
+
getOverlay().click();
|
|
458
|
+
await expect(promise).resolves.toBeNull();
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
it('selects enabled items by click and dismisses on outside click', async () => {
|
|
462
|
+
const selected = showWebContextMenu([{ id: 'copy', label: 'Copy' }], 0, 0);
|
|
463
|
+
getRootMenu().querySelector<HTMLElement>('li')!.click();
|
|
464
|
+
await expect(selected).resolves.toBe('copy');
|
|
465
|
+
expect(document.body.querySelector('ul')).toBeNull();
|
|
466
|
+
|
|
467
|
+
const dismissed = showWebContextMenu([{ id: 'paste', label: 'Paste' }], 0, 0);
|
|
468
|
+
getOverlay().click();
|
|
469
|
+
await expect(dismissed).resolves.toBeNull();
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
it('wraps keyboard focus and selects with Enter or Space', async () => {
|
|
473
|
+
const items = [
|
|
474
|
+
{ id: 'first', label: 'First' },
|
|
475
|
+
{ id: 'disabled', label: 'Disabled', enabled: false },
|
|
476
|
+
{ id: 'last', label: 'Last' },
|
|
477
|
+
];
|
|
478
|
+
|
|
479
|
+
const enterSelection = showWebContextMenu(items, 0, 0);
|
|
480
|
+
press('ArrowUp');
|
|
481
|
+
expect(getRootMenu().querySelector('[data-focused="true"]')?.getAttribute('data-item-id')).toBe('last');
|
|
482
|
+
press('Enter');
|
|
483
|
+
await expect(enterSelection).resolves.toBe('last');
|
|
484
|
+
|
|
485
|
+
const spaceSelection = showWebContextMenu(items, 0, 0);
|
|
486
|
+
press('ArrowDown');
|
|
487
|
+
press('ArrowDown');
|
|
488
|
+
press('ArrowDown');
|
|
489
|
+
expect(getRootMenu().querySelector('[data-focused="true"]')?.getAttribute('data-item-id')).toBe('first');
|
|
490
|
+
press(' ');
|
|
491
|
+
await expect(spaceSelection).resolves.toBe('first');
|
|
492
|
+
});
|
|
493
|
+
|
|
494
|
+
it('dismisses on Escape', async () => {
|
|
495
|
+
const promise = showWebContextMenu([{ id: 'copy', label: 'Copy' }], 0, 0);
|
|
496
|
+
press('Escape');
|
|
497
|
+
await expect(promise).resolves.toBeNull();
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
it('opens a submenu on hover and resolves a child selection', async () => {
|
|
501
|
+
const promise = showWebContextMenu(
|
|
502
|
+
[{ id: 'file', label: 'File', type: 'submenu', submenu: [{ id: 'open', label: 'Open' }] }],
|
|
503
|
+
0,
|
|
504
|
+
0,
|
|
505
|
+
);
|
|
506
|
+
const parent = getRootMenu().querySelector<HTMLElement>(':scope > li')!;
|
|
507
|
+
const submenu = parent.querySelector<HTMLElement>('ul')!;
|
|
508
|
+
expect(submenu.style.display).toBe('none');
|
|
509
|
+
parent.dispatchEvent(new MouseEvent('mouseenter'));
|
|
510
|
+
expect(submenu.style.display).toBe('block');
|
|
511
|
+
submenu.querySelector<HTMLElement>('li')!.click();
|
|
512
|
+
await expect(promise).resolves.toBe('open');
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
it('clamps a menu that overflows the viewport', async () => {
|
|
516
|
+
vi.spyOn(HTMLElement.prototype, 'getBoundingClientRect').mockReturnValue(new DOMRect(1900, 1900, 100, 100));
|
|
517
|
+
const promise = showWebContextMenu([{ id: 'copy', label: 'Copy' }], 40, 30);
|
|
518
|
+
expect(getRootMenu().style.left).toBe('0px');
|
|
519
|
+
expect(getRootMenu().style.top).toBe('0px');
|
|
520
|
+
getOverlay().click();
|
|
521
|
+
await promise;
|
|
522
|
+
});
|
|
523
|
+
});
|
|
524
|
+
|
|
417
525
|
describe('validateMenuItemTemplate', () => {
|
|
418
526
|
it('returns null for a well-formed item', () => {
|
|
419
527
|
expect(validateMenuItemTemplate(createMenuItemTemplate({ id: 'ok', label: 'Ok' }))).toBeNull();
|