diff --git a/src/components/ai-edition/v4/EditorTopBar.tsx b/src/components/ai-edition/v4/EditorTopBar.tsx index ed391cb55..6968b3661 100644 --- a/src/components/ai-edition/v4/EditorTopBar.tsx +++ b/src/components/ai-edition/v4/EditorTopBar.tsx @@ -16,13 +16,14 @@ import { Sun, Undo2, } from "lucide-react"; -import { type KeyboardEvent as ReactKeyboardEvent, useEffect, useRef, useState } from "react"; +import { useEffect, useRef, useState } from "react"; import logoMark from "@/assets/openscreen-mark.png"; import { Tooltip } from "@/components/ui/tooltip"; import { useI18n, useScopedT } from "@/contexts/I18nContext"; import { useShortcuts } from "@/contexts/ShortcutsContext"; import { useTheme } from "@/hooks/useTheme"; import { getAvailableLocales, getLocaleName } from "@/i18n/loader"; +import { moveMenuFocus } from "@/lib/menuKeyboard"; import { formatFirstFixedBinding } from "@/lib/shortcuts"; import { StylePresetsMenu } from "../StylePresetsMenu"; import styles from "./EditorShellV4.module.css"; @@ -361,21 +362,6 @@ function AppMenu({ actions }: { actions: TopBarActions }) { menuRef.current?.querySelector('[role="menuitem"]')?.focus(); }, [open]); - const onMenuKeyDown = (e: ReactKeyboardEvent) => { - if (e.key !== "ArrowDown" && e.key !== "ArrowUp") return; - e.preventDefault(); - const items = Array.from( - menuRef.current?.querySelectorAll( - '[role="menuitem"], [role="menuitemradio"]', - ) ?? [], - ); - if (items.length === 0) return; - const at = items.indexOf(document.activeElement as HTMLButtonElement); - const next = e.key === "ArrowDown" ? at + 1 : at - 1; - // Wraps both ways; `at` is -1 when focus escaped the list, and ArrowDown then lands on 0. - items[(next + items.length) % items.length]?.focus(); - }; - const run = (action: () => void) => () => { // Unlike Escape, a click does not hand focus back to the trigger: the pointer user did not // come from there, and a focus ring appearing under the cursor reads as a bug. @@ -401,7 +387,7 @@ function AppMenu({ actions }: { actions: TopBarActions }) { {open ? ( -
+
{/* The file actions that used to be three icons in the bar. Their labels are the keys those icons carried as tooltips; Ctrl+N / Ctrl+O / Ctrl+S still reach them through the native menu's accelerators. */} diff --git a/src/components/launch/HudControls.tsx b/src/components/launch/HudControls.tsx index 6000d5ab7..2c5fa7df1 100644 --- a/src/components/launch/HudControls.tsx +++ b/src/components/launch/HudControls.tsx @@ -1,5 +1,14 @@ import { Check, Languages, NotepadText, Settings } from "lucide-react"; -import { createContext, memo, type ReactElement, useContext, useRef, useState } from "react"; +import { + createContext, + memo, + type ReactElement, + useContext, + useEffect, + useRef, + useState, +} from "react"; +import { moveMenuFocus } from "@/lib/menuKeyboard"; import { formatTimePadded } from "../../utils/timeUtils"; import { Button } from "../ui/button"; import { TOOLTIP_GAP_PX, Tooltip } from "../ui/tooltip"; @@ -663,12 +672,19 @@ export const HudLanguageMenu = memo(function HudLanguageMenu({ panelRef: (el: HTMLDivElement | null) => void; onEnsureInteractive: () => void; }) { + // Into the list as it opens, on the language in use, so the arrows work straight away. + const activeItemRef = useRef(null); + useEffect(() => { + activeItemRef.current?.focus(); + }, []); + return (
event.stopPropagation()} onPointerEnter={onEnsureInteractive} @@ -680,6 +696,7 @@ export const HudLanguageMenu = memo(function HudLanguageMenu({ {locales.map((loc) => (