@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 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
@@ -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
@@ -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,oBAAoB,IAAI,WAAW,CAclD;AAKD,wBAAgB,iBAAiB,IAAI,WAAW,CAU/C;AAGD,wBAAgB,cAAc,IAAI,WAAW,CAG5C;AAGD,wBAAgB,cAAc,IAAI,QAAQ,CAAC,WAAW,CAAC,GAAG,IAAI,CAE7D;AAKD,wBAAgB,YAAY,CAAC,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,GAAG,MAAM,IAAI,CAKvE;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;AAKD,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,QAAQ,CAAC,gBAAgB,CAAC,GAAG,MAAM,GAAG,IAAI,CAE5F"}
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
- // Returns the active backend; lazily creates a web default on first call.
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
- if (_backend === null)
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 the active menu backend; pass null to fall back to the web default.
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
- _backend = backend;
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
- function showWebContextMenu(items, x, y) {
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.1-next.975.01aea94",
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.3.1-next.975.01aea94",
42
- "@flighthq/types": "0.3.1-next.975.01aea94"
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('createWebMenuBackend', () => {
56
- it('reports no native application menu', () => {
57
- const backend = createWebMenuBackend();
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
- it('returns an unsubscribe function from subscribeSelect', () => {
62
- const backend = createWebMenuBackend();
63
- expect(typeof backend.subscribeSelect(() => {})).toBe('function');
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
- describe('createWebMenuBackend context-menu renderer', () => {
68
- afterEach(() => {
69
- document.body.replaceChildren();
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
- function getRootMenu(): HTMLUListElement {
74
- const menu = document.body.querySelector('ul');
75
- expect(menu).not.toBeNull();
76
- return menu as HTMLUListElement;
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
- function getOverlay(): HTMLDivElement {
80
- const overlay = document.body.querySelector('div');
81
- expect(overlay).not.toBeNull();
82
- return overlay as HTMLDivElement;
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
- function press(key: string): void {
86
- document.dispatchEvent(new KeyboardEvent('keydown', { key }));
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
- it('renders separators, checked kinds, accelerators, and disabled items', async () => {
90
- const promise = createWebMenuBackend().popupContextMenu(
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
- let settled = false;
110
- void promise.then(() => {
111
- settled = true;
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
- it('selects enabled items by click and dismisses on outside click', async () => {
121
- const backend = createWebMenuBackend();
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
- const dismissed = backend.popupContextMenu([{ id: 'paste', label: 'Paste' }], 0, 0);
128
- getOverlay().click();
129
- await expect(dismissed).resolves.toBeNull();
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('wraps keyboard focus and selects with Enter or Space', async () => {
133
- const items = [
134
- { id: 'first', label: 'First' },
135
- { id: 'disabled', label: 'Disabled', enabled: false },
136
- { id: 'last', label: 'Last' },
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
- const spaceSelection = createWebMenuBackend().popupContextMenu(items, 0, 0);
146
- press('ArrowDown');
147
- press('ArrowDown');
148
- press('ArrowDown');
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
- it('dismisses on Escape', async () => {
155
- const promise = createWebMenuBackend().popupContextMenu([{ id: 'copy', label: 'Copy' }], 0, 0);
156
- press('Escape');
157
- await expect(promise).resolves.toBeNull();
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
- it('opens a submenu on hover and resolves a child selection', async () => {
161
- const promise = createWebMenuBackend().popupContextMenu(
162
- [{ id: 'file', label: 'File', type: 'submenu', submenu: [{ id: 'open', label: 'Open' }] }],
163
- 0,
164
- 0,
165
- );
166
- const parent = getRootMenu().querySelector<HTMLElement>(':scope > li')!;
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('clamps a menu that overflows the viewport', async () => {
176
- vi.spyOn(HTMLElement.prototype, 'getBoundingClientRect').mockReturnValue(new DOMRect(1900, 1900, 100, 100));
177
- const promise = createWebMenuBackend().popupContextMenu([{ id: 'copy', label: 'Copy' }], 40, 30);
178
- expect(getRootMenu().style.left).toBe('0px');
179
- expect(getRootMenu().style.top).toBe('0px');
180
- getOverlay().click();
181
- await promise;
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
- describe('enableMenuSignals', () => {
186
- it('returns a stable instance across calls', () => {
187
- expect(enableMenuSignals()).toBe(enableMenuSignals());
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('getMenuBackend', () => {
192
- afterEach(() => setMenuBackend(null));
165
+ describe('observeMenuHostResult', () => {
166
+ afterEach(() => resetMenuBackendForTest());
193
167
 
194
- it('lazily returns a web default backend', () => {
195
- setMenuBackend(null);
196
- expect(getMenuBackend()).not.toBeNull();
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
- describe('getMenuSignals', () => {
201
- it('returns the active signal group once enabled', () => {
202
- const signals = enableMenuSignals();
203
- expect(getMenuSignals()).toBe(signals);
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(() => setMenuBackend(null));
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(() => setMenuBackend(null));
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(() => setMenuBackend(null));
236
+ afterEach(() => resetMenuBackendForTest());
247
237
 
248
- it('installs an explicit backend and reverts to the web default on null', () => {
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(() => setMenuBackend(null));
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
- setMenuBackend(null);
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();