diff --git a/src/app.css b/src/app.css index 3bace5e..86199b4 100644 --- a/src/app.css +++ b/src/app.css @@ -6579,6 +6579,11 @@ input:focus, textarea:focus, select:focus { box-shadow: 0 0 0 3px color-mix(in s linear-gradient(180deg, color-mix(in srgb, var(--color-success) 2.5%, transparent), transparent 120px), var(--color-surface-solid); } +/* The lane holding the keyboard focus is the one Ctrl/Cmd + A selects in. */ +.status-lane:focus { outline: none; } +.status-lane:focus-within { + box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--color-primary) 34%, transparent); +} .status-flow-divider { position: relative; display: grid; diff --git a/src/lib/components/HelpOverlay.svelte b/src/lib/components/HelpOverlay.svelte index e063d31..94ffc18 100644 --- a/src/lib/components/HelpOverlay.svelte +++ b/src/lib/components/HelpOverlay.svelte @@ -249,7 +249,8 @@ summary: "Die Hilfe ist überall erreichbar. Dialoge lassen sich konsistent schließen und Suchfelder direkt fokussieren.", commands: [ { command: "Ctrl + /", description: "Diese Hilfe öffnen" }, - { command: "Escape", description: "Aktuelles Overlay oder Dialogfenster schließen" }, + { command: "Ctrl + A", description: "Alle Dateien in der aktiven Statusliste („Ungestaged“ oder „Gestaged“) auswählen" }, + { command: "Escape", description: "Aktuelles Overlay oder Dialogfenster schließen – in der Statusliste die aktuelle Auswahl aufheben" }, { command: "Tab / Shift + Tab", description: "Zwischen Bedienelementen wechseln" }, { command: "Enter / Leertaste", description: "Fokussierte Aktion ausführen" }, ], @@ -459,7 +460,8 @@ summary: "Help is available everywhere. Dialogs close consistently and search fields receive focus automatically.", commands: [ { command: "Ctrl + /", description: "Open this help center" }, - { command: "Escape", description: "Close the current overlay or dialog" }, + { command: "Ctrl + A", description: "Select every file in the focused status list (Unstaged or Staged)" }, + { command: "Escape", description: "Close the current overlay or dialog – in the status list, clear the current selection" }, { command: "Tab / Shift + Tab", description: "Move between controls" }, { command: "Enter / Space", description: "Activate the focused control" }, ], diff --git a/src/lib/components/StatusPanel.svelte b/src/lib/components/StatusPanel.svelte index cab0363..04e94a6 100644 --- a/src/lib/components/StatusPanel.svelte +++ b/src/lib/components/StatusPanel.svelte @@ -323,8 +323,44 @@ onStopTracking(targets, target.kind); } + function focusStatusLane(event: PointerEvent) { + const target = event.target; + if (target instanceof Element && !target.closest("input, textarea, select, a, [contenteditable]")) { + (event.currentTarget as HTMLElement).focus({ preventScroll: true }); + } + } + + function clearStatusSelection(): boolean { + if (selectedStatusPaths.size === 0 && !selectionAnchorKey) return false; + selectedStatusPaths = new Set(); + selectionAnchorKey = ""; + return true; + } + function handleStatusWindowKeydown(event: KeyboardEvent) { - if (event.key === "Escape") closeStatusContextMenu(); + // Escape backs out one step at a time: first the context menu, then the + // current multi-selection. No preventDefault, so an open dialog still + // closes on the same keypress. + if (event.key === "Escape") { + if (statusContextTarget) { + closeStatusContextMenu(); + return; + } + const escapeTarget = event.target; + if (escapeTarget instanceof Element && escapeTarget.closest("input, textarea, select, [contenteditable]")) return; + clearStatusSelection(); + return; + } + if (event.defaultPrevented || isBusy || !hasRepository || !(event.ctrlKey || event.metaKey) || event.altKey || event.shiftKey || event.key.toLowerCase() !== "a") return; + const target = event.target; + if (!(target instanceof Element) || target.closest("input, textarea, select, [contenteditable]")) return; + const lane = target.closest(".status-lane"); + if (!lane) return; + const files = lane.classList.contains("unstaged-lane") ? unstagedFiles : stagedFiles; + event.preventDefault(); + selectedStatusPaths = new Set(files.map(fileKey)); + selectionAnchorKey = files.length ? fileKey(files[0]) : ""; + closeStatusContextMenu(); } function isStatusSelected(file: GitFileStatus): boolean { @@ -459,8 +495,8 @@