waybetter 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (190) hide show
  1. package/README.md +1 -2
  2. package/bin/waybetter.mjs +2 -5
  3. package/cli/buildenv.mjs +1 -1
  4. package/cli/release.mjs +11 -18
  5. package/cli/releasecheck.mjs +15 -0
  6. package/cli/remote.mjs +4 -4
  7. package/cli/store.mjs +80 -28
  8. package/cli/strings.mjs +2 -0
  9. package/dist/runtime/stringPackHook.js +2 -2
  10. package/dist/runtime/stringPackHook.js.map +1 -1
  11. package/dist/runtime/stringPackTranslate.d.ts +3 -1
  12. package/dist/runtime/stringPackTranslate.js +6 -3
  13. package/dist/runtime/stringPackTranslate.js.map +1 -1
  14. package/dist/runtime/stringPacks.d.ts +53 -5
  15. package/dist/runtime/stringPacks.js +161 -25
  16. package/dist/runtime/stringPacks.js.map +1 -1
  17. package/dist/runtime/themeBar.d.ts +28 -0
  18. package/dist/runtime/themeBar.js +82 -0
  19. package/dist/runtime/themeBar.js.map +1 -0
  20. package/dist/runtime/themeComponents.js +16 -3
  21. package/dist/runtime/themeComponents.js.map +1 -1
  22. package/dist/runtime/themeDecor.js +3 -1
  23. package/dist/runtime/themeDecor.js.map +1 -1
  24. package/dist/runtime/themeDerive.d.ts +69 -0
  25. package/dist/runtime/themeDerive.js +117 -0
  26. package/dist/runtime/themeDerive.js.map +1 -0
  27. package/dist/runtime/themeDoctor.d.ts +44 -3
  28. package/dist/runtime/themeDoctor.js +86 -25
  29. package/dist/runtime/themeDoctor.js.map +1 -1
  30. package/dist/runtime/themeGuards.d.ts +60 -0
  31. package/dist/runtime/themeGuards.js +235 -0
  32. package/dist/runtime/themeGuards.js.map +1 -0
  33. package/dist/runtime/themeIcons.d.ts +1 -1
  34. package/dist/runtime/themeIcons.js +13 -5
  35. package/dist/runtime/themeIcons.js.map +1 -1
  36. package/dist/runtime/themeInk.d.ts +37 -0
  37. package/dist/runtime/themeInk.js +111 -0
  38. package/dist/runtime/themeInk.js.map +1 -0
  39. package/dist/runtime/themeInvariants.d.ts +39 -0
  40. package/dist/runtime/themeInvariants.js +273 -0
  41. package/dist/runtime/themeInvariants.js.map +1 -0
  42. package/dist/runtime/themeRuntime.d.ts +15 -1
  43. package/dist/runtime/themeRuntime.js +78 -5
  44. package/dist/runtime/themeRuntime.js.map +1 -1
  45. package/dist/runtime/themeSpec.d.ts +5 -1
  46. package/dist/runtime/themeSpec.js +9 -13
  47. package/dist/runtime/themeSpec.js.map +1 -1
  48. package/dist/shell/InstallDialog.js +14 -8
  49. package/dist/shell/InstallDialog.js.map +1 -1
  50. package/dist/shell/StoreView.d.ts +12 -0
  51. package/dist/shell/StoreView.js +9 -4
  52. package/dist/shell/StoreView.js.map +1 -1
  53. package/dist/shell/frame/AppMenu.d.ts +26 -3
  54. package/dist/shell/frame/AppMenu.js +106 -27
  55. package/dist/shell/frame/AppMenu.js.map +1 -1
  56. package/dist/shell/frame/FindBar.js +1 -1
  57. package/dist/shell/frame/FindBar.js.map +1 -1
  58. package/dist/shell/frame/Shell.d.ts +1 -0
  59. package/dist/shell/frame/Shell.js +10 -2
  60. package/dist/shell/frame/Shell.js.map +1 -1
  61. package/dist/shell/frame/Titlebar.d.ts +2 -1
  62. package/dist/shell/frame/Titlebar.js +5 -3
  63. package/dist/shell/frame/Titlebar.js.map +1 -1
  64. package/dist/shell/frame/WebPanel.js +4 -1
  65. package/dist/shell/frame/WebPanel.js.map +1 -1
  66. package/dist/shell/frame/accountSwitcher.css +10 -10
  67. package/dist/shell/frame/appMenu.css +19 -13
  68. package/dist/shell/frame/boot.js +2 -0
  69. package/dist/shell/frame/boot.js.map +1 -1
  70. package/dist/shell/frame/chromeTheme.d.ts +19 -1
  71. package/dist/shell/frame/chromeTheme.js +51 -14
  72. package/dist/shell/frame/chromeTheme.js.map +1 -1
  73. package/dist/shell/frame/deck.css +30 -30
  74. package/dist/shell/frame/edges.css +159 -0
  75. package/dist/shell/frame/index.d.ts +2 -2
  76. package/dist/shell/frame/index.js +1 -1
  77. package/dist/shell/frame/index.js.map +1 -1
  78. package/dist/shell/frame/mediaModel.d.ts +13 -0
  79. package/dist/shell/frame/mediaModel.js +14 -0
  80. package/dist/shell/frame/mediaModel.js.map +1 -1
  81. package/dist/shell/frame/menuMessages.en.json +43 -1
  82. package/dist/shell/frame/menuModel.d.ts +42 -0
  83. package/dist/shell/frame/menuModel.js +8 -0
  84. package/dist/shell/frame/menuModel.js.map +1 -1
  85. package/dist/shell/frame/nativeMenu.d.ts +5 -0
  86. package/dist/shell/frame/nativeMenu.js +178 -0
  87. package/dist/shell/frame/nativeMenu.js.map +1 -0
  88. package/dist/shell/frame/nativeMenuModel.d.ts +102 -0
  89. package/dist/shell/frame/nativeMenuModel.js +186 -0
  90. package/dist/shell/frame/nativeMenuModel.js.map +1 -0
  91. package/dist/shell/frame/paletteModel.js +3 -1
  92. package/dist/shell/frame/paletteModel.js.map +1 -1
  93. package/dist/shell/frame/paneTheme.css +1 -1
  94. package/dist/shell/frame/reportModel.d.ts +1 -1
  95. package/dist/shell/frame/reportModel.js +1 -1
  96. package/dist/shell/frame/reportModel.js.map +1 -1
  97. package/dist/shell/frame/shell.css +56 -51
  98. package/dist/shell/frame/state.d.ts +2 -0
  99. package/dist/shell/frame/state.js +3 -1
  100. package/dist/shell/frame/state.js.map +1 -1
  101. package/dist/shell/frame/themeStore.js +21 -6
  102. package/dist/shell/frame/themeStore.js.map +1 -1
  103. package/dist/shell/frame/workspaces.css +12 -12
  104. package/dist/shell/installController.d.ts +16 -1
  105. package/dist/shell/installController.js +31 -5
  106. package/dist/shell/installController.js.map +1 -1
  107. package/dist/shell/panel/Appearance.js +12 -3
  108. package/dist/shell/panel/Appearance.js.map +1 -1
  109. package/dist/shell/panel/Settings.d.ts +1 -1
  110. package/dist/shell/panel/Settings.js +4 -3
  111. package/dist/shell/panel/Settings.js.map +1 -1
  112. package/dist/shell/panel/StoreSection.d.ts +2 -1
  113. package/dist/shell/panel/StoreSection.js +31 -1
  114. package/dist/shell/panel/StoreSection.js.map +1 -1
  115. package/dist/shell/panel/StringPacks.d.ts +12 -3
  116. package/dist/shell/panel/StringPacks.js +33 -8
  117. package/dist/shell/panel/StringPacks.js.map +1 -1
  118. package/dist/shell/panel/backup.css +1 -1
  119. package/dist/shell/panel/builder.css +48 -40
  120. package/dist/shell/panel/make.css +8 -7
  121. package/dist/shell/panel/panel.css +401 -401
  122. package/dist/shell/panel/panelMessages.d.ts +41 -1
  123. package/dist/shell/panel/panelMessages.js +13 -1
  124. package/dist/shell/panel/panelMessages.js.map +1 -1
  125. package/dist/shell/panel/plugins.css +5 -5
  126. package/dist/shell/panel/strings.css +1 -1
  127. package/dist/shell/store.css +34 -14
  128. package/dist/shell/storeMessages.d.ts +24 -0
  129. package/dist/shell/storeMessages.js +24 -0
  130. package/dist/shell/storeMessages.js.map +1 -1
  131. package/dist/shell/storeModel.d.ts +31 -7
  132. package/dist/shell/storeModel.js +15 -2
  133. package/dist/shell/storeModel.js.map +1 -1
  134. package/dist/shell/stringPacksModel.js +7 -2
  135. package/dist/shell/stringPacksModel.js.map +1 -1
  136. package/icons.schema.json +9 -0
  137. package/locales/ar.json +109 -2
  138. package/locales/bg.json +109 -2
  139. package/locales/bn.json +109 -2
  140. package/locales/ca.json +109 -2
  141. package/locales/cs.json +109 -2
  142. package/locales/da.json +109 -2
  143. package/locales/de.json +109 -2
  144. package/locales/el.json +109 -2
  145. package/locales/en.json +108 -2
  146. package/locales/es.json +109 -2
  147. package/locales/eu.json +109 -2
  148. package/locales/fa.json +109 -2
  149. package/locales/fi.json +109 -2
  150. package/locales/fil.json +109 -2
  151. package/locales/fr.json +109 -2
  152. package/locales/ga.json +109 -2
  153. package/locales/gl.json +109 -2
  154. package/locales/gu.json +109 -2
  155. package/locales/ha.json +109 -2
  156. package/locales/he.json +109 -2
  157. package/locales/hi.json +109 -2
  158. package/locales/hr.json +109 -2
  159. package/locales/hu.json +110 -2
  160. package/locales/id.json +109 -2
  161. package/locales/ig.json +109 -2
  162. package/locales/it.json +109 -2
  163. package/locales/ja.json +109 -2
  164. package/locales/kn.json +109 -2
  165. package/locales/ko.json +109 -2
  166. package/locales/mr.json +109 -2
  167. package/locales/ms.json +109 -2
  168. package/locales/nb.json +109 -2
  169. package/locales/nl.json +109 -2
  170. package/locales/pl.json +109 -2
  171. package/locales/pt.json +109 -2
  172. package/locales/ro.json +109 -2
  173. package/locales/ru.json +109 -2
  174. package/locales/sk.json +109 -2
  175. package/locales/sr.json +109 -2
  176. package/locales/sv.json +109 -2
  177. package/locales/ta.json +109 -2
  178. package/locales/th.json +109 -2
  179. package/locales/tr.json +109 -2
  180. package/locales/uk.json +109 -2
  181. package/locales/ur.json +109 -2
  182. package/locales/vi.json +109 -2
  183. package/locales/yo.json +109 -2
  184. package/locales/zh-Hans.json +109 -2
  185. package/locales/zh-Hant.json +109 -2
  186. package/package.json +2 -3
  187. package/store-catalog.schema.json +18 -5
  188. package/theme.schema.json +9 -0
  189. package/windows/installer.nsi +22 -2
  190. package/cli/privacy.mjs +0 -768
@@ -1,10 +1,12 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- // The app menu: the titlebar's "Settings and more" button, at the far end of the titlebar cluster
3
- // (before the caption buttons on Windows and Linux; macOS has none there). It opens on click, on
4
- // Alt+F and F10 (Shell.tsx; a focused tab forwards the keys), from the command palette, from the
5
- // last row of the account menu, and from the app's logo at the start of the titlebar (the same
6
- // menu, hung under the logo on its side of the window). Its rows are the app's (`ShellSite.menu`, menuModel.ts): built-in
7
- // ones by name, each shown only when the app serves what it needs, and the app's own.
2
+ // The app menu ("Settings and more"). Its trigger is the app's choice (`ShellMenu.trigger`): by
3
+ // default each pane's wayBetter button at the end of its toolbar (`PaneMenuButton`; the menu hangs
4
+ // under the button that was clicked, so in split view each pane opens it under itself), else a
5
+ // button at the end of the titlebar or the titlebar's logo. It also opens on Alt+F and F10
6
+ // (Shell.tsx; a focused tab forwards the keys), from the command palette and from the last row of
7
+ // the account menu: under the focused pane's button (the first one on screen when no pane is
8
+ // focused). Its rows are the app's (`ShellSite.menu`, menuModel.ts): built-in ones by name, each
9
+ // shown only when the app serves what it needs, and the app's own.
8
10
  //
9
11
  // Like every shell menu it is an overlay: while open the shell is raised above the tab webviews
10
12
  // (`setOverlay`), panes stay visible and nothing is dimmed. Keyboard: arrows, Home/End, type-ahead,
@@ -21,7 +23,7 @@ import { refreshDev } from "./devHost.js";
21
23
  import { useDev, usePaneTick, useT } from "./hooks.js";
22
24
  import { formatNumber } from "./i18n.js";
23
25
  import { Icon } from "./icons.js";
24
- import { DEFAULT_MENU, ZOOM_MAX, ZOOM_MIN, findSpec, joinGroups, languageChoice, languageRows, menuText, moveStop, planMenu, shortcutText, specRows, submenuKeys, submenuLeft, typeahead, updateRow, } from "./menuModel.js";
26
+ import { DEFAULT_MENU, ZOOM_MAX, ZOOM_MIN, findSpec, joinGroups, languageChoice, languageRows, menuText, moveStop, planMenu, resolveTrigger, shortcutText, specRows, submenuKeys, submenuLeft, typeahead, updateRow, } from "./menuModel.js";
25
27
  import { ReportDialog } from "./ReportDialog.js";
26
28
  import { safe, setSafeMode } from "./safeStore.js";
27
29
  import { hasFeature, site } from "./site.js";
@@ -33,19 +35,40 @@ import { readZoom, stepZoom, zoomOf, zoomTarget } from "./zoomStore.js";
33
35
  import './appMenu.css';
34
36
  /** Opens the app menu (Alt+F / F10, the palette, the account menu's last row). */
35
37
  export const openAppMenu = (request = {}) => window.dispatchEvent(new CustomEvent('bt-app-menu', { detail: request }));
38
+ /** Opens the "Report a problem" summary (the native menu bar's Help item). */
39
+ export const openReport = () => window.dispatchEvent(new CustomEvent('bt-report'));
36
40
  const mac = os === 'macos';
37
41
  /** The keys that open the menu, as the button's tooltip and the account menu write them. */
38
42
  export const APP_MENU_KEYS = mac ? 'F10' : 'Alt+F';
39
43
  const getError = (error) => (error instanceof Error ? error.message : String(error)).replace(/^Error: /, '');
40
44
  const percent = (factor) => formatNumber(factor, { style: 'percent', maximumFractionDigits: 0 });
45
+ /** One trigger, as a key: `pane:<id>` for a pane's button. */
46
+ const triggerKey = (at, pane) => (at === 'pane' ? `pane:${pane ?? ''}` : at);
41
47
  /** Which trigger the open menu hangs from (null: closed), for the triggers' `aria-expanded`. */
42
48
  let openAt = null;
43
- function noteOpen(at) {
44
- if (openAt === at)
49
+ function noteOpen(key) {
50
+ if (openAt === key)
45
51
  return;
46
- openAt = at;
52
+ openAt = key;
47
53
  window.dispatchEvent(new CustomEvent('bt-app-menu-state'));
48
54
  }
55
+ /** `aria-expanded` of one trigger. */
56
+ function useExpanded(key) {
57
+ const [expanded, setExpanded] = useState(openAt === key);
58
+ useEffect(() => {
59
+ const update = () => setExpanded(openAt === key);
60
+ update();
61
+ window.addEventListener('bt-app-menu-state', update);
62
+ return () => window.removeEventListener('bt-app-menu-state', update);
63
+ }, [key]);
64
+ return expanded;
65
+ }
66
+ /** Pane `id`'s menu button on screen; else the focused pane's; else the first one on screen. */
67
+ function paneButton(id) {
68
+ const shown = Array.from(document.querySelectorAll('.bt-pane-menu[data-pane]')).filter(el => el.getClientRects().length > 0);
69
+ const find = (want) => (want ? shown.find(el => el.dataset.pane === want) : undefined);
70
+ return find(id) ?? find(shell.api?.activePanel?.id) ?? shown[0] ?? null;
71
+ }
49
72
  /** The rows of one level a key can stop on: enabled items; the zoom row counts once. */
50
73
  function stopsOf(menu) {
51
74
  const all = Array.from(menu.querySelectorAll('[data-item]:not(:disabled)'));
@@ -54,7 +77,11 @@ function stopsOf(menu) {
54
77
  const keep = zoom.find(el => el === active) ?? zoom.find(el => el.getAttribute('aria-disabled') !== 'true') ?? zoom[0];
55
78
  return all.filter(el => !('zoom' in el.dataset) || el === keep);
56
79
  }
57
- export function AppMenu({ trigger = true } = {}) {
80
+ /**
81
+ * The menu (mounted once, in the titlebar). `trigger`: where clicks and keys open it; default the
82
+ * app's `ShellMenu.trigger`, else `pane`. `true` / `false` are the older spellings of `button` / `logo`.
83
+ */
84
+ export function AppMenu({ trigger } = {}) {
58
85
  const t = useT();
59
86
  usePaneTick();
60
87
  const dev = useDev();
@@ -93,16 +120,36 @@ export function AppMenu({ trigger = true } = {}) {
93
120
  dispose?.();
94
121
  };
95
122
  }, []);
96
- /** Where a request without an anchor opens: the button when this app shows one, else the logo. */
97
- const home = () => (trigger ? 'button' : 'logo');
123
+ /** Where a request without an anchor opens: the app's trigger. */
124
+ const home = () => menuTrigger(trigger);
125
+ /** The element a request hangs the menu under (null: none on screen). */
126
+ function triggerOf(at, pane) {
127
+ if (at === 'pane')
128
+ return paneButton(pane);
129
+ if (at === 'logo')
130
+ return document.querySelector('button.bt-titlebar-logo');
131
+ return button.current;
132
+ }
98
133
  function show(request = {}) {
99
134
  const at = request.anchor ?? home();
135
+ const el = triggerOf(at, request.pane);
136
+ const pane = at === 'pane' ? el?.dataset.pane : undefined;
100
137
  diag(`menu open: anchor=${at} keyboard=${!!request.keyboard} hasFocus=${document.hasFocus()} active=${describe(document.activeElement)}`);
101
- const logo = at === 'logo' ? document.querySelector('.bt-titlebar-logo') : null;
102
- const rect = (logo ?? button.current)?.getBoundingClientRect();
138
+ const rect = el?.getBoundingClientRect();
103
139
  if (rect) {
104
140
  const top = Math.round(rect.bottom + 6);
105
- setAnchor(logo ? { at: 'logo', top, left: Math.max(8, Math.round(rect.left - 3)) } : { at: 'button', top, right: Math.max(8, Math.round(window.innerWidth - rect.right)) });
141
+ const rtl = getComputedStyle(el).direction === 'rtl';
142
+ if (at === 'logo')
143
+ setAnchor({ at, top, left: Math.max(8, Math.round(rect.left - 3)) });
144
+ // The menu's end edge lines up with its button's, so it opens over the pane the button is in.
145
+ else if (rtl)
146
+ setAnchor({ at, pane, top, left: Math.max(8, Math.round(rect.left)) });
147
+ else
148
+ setAnchor({ at, pane, top, right: Math.max(8, Math.round(window.innerWidth - rect.right)) });
149
+ }
150
+ else {
151
+ // No trigger on screen (only the app's own tab, a deck): under the titlebar, at its end.
152
+ setAnchor({ at, pane, top: 50, right: 8 });
106
153
  }
107
154
  const active = document.activeElement;
108
155
  before.current = active && active !== document.body && document.hasFocus() ? active : null;
@@ -161,8 +208,10 @@ export function AppMenu({ trigger = true } = {}) {
161
208
  useEffect(() => {
162
209
  const onRequest = (event) => {
163
210
  const request = event.detail ?? {};
164
- // Asked again from the other trigger, the open menu moves there instead of closing.
165
- if (live.current.open && request.toggle && (request.anchor ?? home()) === (openAt ?? home()))
211
+ // Asked again from another trigger, the open menu moves there instead of closing.
212
+ const at = request.anchor ?? home();
213
+ const key = triggerKey(at, at === 'pane' ? request.pane ?? paneButton()?.dataset.pane : undefined);
214
+ if (live.current.open && request.toggle && key === openAt)
166
215
  close('escape');
167
216
  else
168
217
  show(request);
@@ -170,12 +219,16 @@ export function AppMenu({ trigger = true } = {}) {
170
219
  window.addEventListener('bt-app-menu', onRequest);
171
220
  return () => window.removeEventListener('bt-app-menu', onRequest);
172
221
  }, [version]);
222
+ useEffect(() => {
223
+ const show = () => setReporting(true);
224
+ window.addEventListener('bt-report', show);
225
+ return () => window.removeEventListener('bt-report', show);
226
+ }, []);
173
227
  useEffect(() => {
174
228
  if (!open)
175
229
  return;
176
230
  // Native tab webviews would cover the menu.
177
231
  setOverlay(1);
178
- noteOpen(anchor.at);
179
232
  // The window lost the keyboard: the menu closes, unless the focus is back a moment later (taking the
180
233
  // keyboard from a tab for Alt+F / F10 can pass through a blur on its way to the shell).
181
234
  let leaving;
@@ -206,13 +259,31 @@ export function AppMenu({ trigger = true } = {}) {
206
259
  window.addEventListener('blur', away);
207
260
  window.addEventListener('resize', resized);
208
261
  window.addEventListener('keydown', onKey);
209
- return () => { clearTimeout(leaving); setOverlay(-1); noteOpen(null); window.removeEventListener('blur', away); window.removeEventListener('resize', resized); window.removeEventListener('keydown', onKey); };
262
+ return () => { clearTimeout(leaving); setOverlay(-1); window.removeEventListener('blur', away); window.removeEventListener('resize', resized); window.removeEventListener('keydown', onKey); };
210
263
  }, [open, anchor.at]);
264
+ // The trigger the menu hangs from shows it is open.
265
+ useEffect(() => {
266
+ noteOpen(open ? triggerKey(anchor.at, anchor.pane) : null);
267
+ return () => noteOpen(null);
268
+ }, [open, anchor.at, anchor.pane]);
211
269
  const layer = (level) => layers.current?.querySelector(`[data-level="${level}"]`) ?? null;
212
270
  // After every render: submenus sit beside their row, inside the window; then the focus that was asked for.
213
271
  useLayoutEffect(() => {
214
272
  if (!open)
215
273
  return;
274
+ // The menu stays inside the window: a button near the start edge (a narrow pane in split view)
275
+ // would push it out, so it slides back in.
276
+ const root = layer(0);
277
+ if (root) {
278
+ const edge = 8;
279
+ root.style.left = anchor.left === undefined ? '' : `${anchor.left}px`;
280
+ root.style.right = anchor.right === undefined ? '' : `${anchor.right}px`;
281
+ const box = root.getBoundingClientRect();
282
+ if (box.left < edge || box.right > window.innerWidth - edge) {
283
+ root.style.left = `${Math.round(Math.max(edge, Math.min(box.left, window.innerWidth - edge - box.width)))}px`;
284
+ root.style.right = 'auto';
285
+ }
286
+ }
216
287
  for (let level = 1; level <= path.length; level += 1) {
217
288
  const el = layer(level);
218
289
  const parent = layer(level - 1);
@@ -599,7 +670,7 @@ export function AppMenu({ trigger = true } = {}) {
599
670
  return levels.map(({ rows, label }, level) => _jsx("div", { role: "menu", "aria-label": label, tabIndex: -1, "data-level": level, className: level === 0 ? `bt-menu bt-app-menu bt-pop${anchor.at === 'logo' ? '' : ' bt-pop--right'}` : 'bt-menu bt-app-menu bt-app-sub', style: level === 0 ? { top: anchor.top, left: anchor.left, right: anchor.right, maxHeight: `calc(100vh - ${anchor.top + 8}px)` } : undefined, children: rows.map((row, index) => renderRow(row, level, index)) }, level === 0 ? 'root' : `${level}:${path[level - 1]}`));
600
671
  }
601
672
  const label = menuText('label');
602
- return _jsxs(_Fragment, { children: [trigger && _jsx("button", { ref: button, type: "button", className: "bt-app-button", "aria-haspopup": "menu", "aria-expanded": open && anchor.at === 'button', "aria-label": label, title: `${label} (${APP_MENU_KEYS})`, onClick: event => (open && anchor.at === 'button' ? close('escape') : show({ keyboard: event.detail === 0 })), children: _jsxs("svg", { viewBox: "0 0 24 24", "aria-hidden": "true", children: [_jsx("circle", { cx: "12", cy: "5.75", r: "1.6" }), _jsx("circle", { cx: "12", cy: "12", r: "1.6" }), _jsx("circle", { cx: "12", cy: "18.25", r: "1.6" })] }) }), open && createPortal(_jsx("div", { className: "bt-menu-backdrop", onMouseDown: () => close(), onContextMenu: event => { event.preventDefault(); close(); }, children: _jsx("div", { ref: layers, className: "bt-app-layers", onMouseDown: event => event.stopPropagation(), onContextMenu: event => { event.preventDefault(); event.stopPropagation(); }, onKeyDown: onKey, children: renderMenus() }) }), document.body), reporting && _jsx(ReportDialog, { onClose: () => setReporting(false) })] });
673
+ return _jsxs(_Fragment, { children: [home() === 'button' && _jsx("button", { ref: button, type: "button", className: "bt-app-button", "aria-haspopup": "menu", "aria-expanded": open && anchor.at === 'button', "aria-label": label, title: `${label} (${APP_MENU_KEYS})`, onClick: event => (open && anchor.at === 'button' ? close('escape') : show({ keyboard: event.detail === 0 })), children: _jsxs("svg", { viewBox: "0 0 24 24", "aria-hidden": "true", children: [_jsx("circle", { cx: "12", cy: "5.75", r: "1.6" }), _jsx("circle", { cx: "12", cy: "12", r: "1.6" }), _jsx("circle", { cx: "12", cy: "18.25", r: "1.6" })] }) }), open && createPortal(_jsx("div", { className: "bt-menu-backdrop", onMouseDown: () => close(), onContextMenu: event => { event.preventDefault(); close(); }, children: _jsx("div", { ref: layers, className: "bt-app-layers", onMouseDown: event => event.stopPropagation(), onContextMenu: event => { event.preventDefault(); event.stopPropagation(); }, onKeyDown: onKey, children: renderMenus() }) }), document.body), reporting && _jsx(ReportDialog, { onClose: () => setReporting(false) })] });
603
674
  }
604
675
  /**
605
676
  * The app's logo at the start of the titlebar: a second trigger of the same menu, hung under the
@@ -608,13 +679,8 @@ export function AppMenu({ trigger = true } = {}) {
608
679
  */
609
680
  export function AppMenuLogo() {
610
681
  useT();
611
- const [expanded, setExpanded] = useState(openAt === 'logo');
682
+ const expanded = useExpanded('logo');
612
683
  const press = useRef(null);
613
- useEffect(() => {
614
- const update = () => setExpanded(openAt === 'logo');
615
- window.addEventListener('bt-app-menu-state', update);
616
- return () => window.removeEventListener('bt-app-menu-state', update);
617
- }, []);
618
684
  const { product, logo } = site();
619
685
  const label = menuText('label');
620
686
  return _jsx("button", { type: "button", className: "bt-titlebar-logo", "aria-haspopup": "menu", "aria-expanded": expanded, "aria-label": `${product}: ${label}`, title: `${product} · ${label}`, onPointerDown: event => { if (event.button === 0)
@@ -639,4 +705,17 @@ export function AppMenuLogo() {
639
705
  systemMenu(Math.round(event.screenX * scale), Math.round(event.screenY * scale));
640
706
  }, children: _jsx("img", { src: logo, alt: "", draggable: false }) });
641
707
  }
708
+ /** The app's menu trigger: `trigger` as `AppMenu` takes it, else `ShellMenu.trigger`, else `pane`. */
709
+ export const menuTrigger = (trigger) => resolveTrigger(trigger, site().menu?.trigger);
710
+ /**
711
+ * One pane's menu button, at the end of its toolbar (the app's mark inside): opens the app menu
712
+ * under itself, or closes it when it is open there. Shown when the app's trigger is `pane`.
713
+ */
714
+ export function PaneMenuButton({ pane, children }) {
715
+ useT();
716
+ const expanded = useExpanded(triggerKey('pane', pane));
717
+ const label = menuText('label');
718
+ const { product } = site();
719
+ return _jsx("button", { type: "button", className: "bt-brand-button bt-pane-menu", "data-pane": pane, "aria-haspopup": "menu", "aria-expanded": expanded, "aria-label": `${product}: ${label}`, title: `${product} · ${label} (${APP_MENU_KEYS})`, onClick: event => openAppMenu({ anchor: 'pane', pane, toggle: true, keyboard: event.detail === 0 }), children: children });
720
+ }
642
721
  //# sourceMappingURL=AppMenu.js.map