spotlight on ctrl+space, theme browser on ctrl+shift+up/down, no backdrop behind the theme list
This commit is contained in:
@@ -24,10 +24,10 @@ const {
|
||||
*
|
||||
* ── The overlay ──
|
||||
*
|
||||
* `ctrl+shift+space` opens a list in the middle of the screen; `j`/`k` move through it and `l` applies the
|
||||
* focused theme to the app BEHIND it. That is the whole idea, and it is why this is a portal over a
|
||||
* translucent backdrop rather than a modal dialog: you are meant to watch the application change while
|
||||
* you browse, so as little of it as possible should be covered.
|
||||
* `ctrl+shift+↑/↓` opens a list in the middle of the screen; `j`/`k` move through it and `l` applies the
|
||||
* focused theme to the app BEHIND it. That is the whole idea, and it is why this is a portal with no
|
||||
* backdrop rather than a modal dialog: you are meant to watch the application change while you browse,
|
||||
* so nothing of it should be covered.
|
||||
*
|
||||
* Ported from the owner's `vimazing`, keys and all.
|
||||
*/
|
||||
@@ -156,9 +156,9 @@ export const ThemeSwitcher = () => {
|
||||
{open &&
|
||||
createPortal(
|
||||
<div
|
||||
// No backdrop at all — the whole point of the browse is watching the app change behind the
|
||||
// list, so nothing may sit between them. The full-screen div stays for click-outside-to-close.
|
||||
className="fixed inset-0 z-[900] flex items-center justify-center"
|
||||
// Deliberately light: the point is to see the app change behind it.
|
||||
style={{ backgroundColor: 'color-mix(in srgb, var(--background) 55%, transparent)' }}
|
||||
onClick={() => setOpen(false)}
|
||||
>
|
||||
<div
|
||||
|
||||
@@ -218,18 +218,23 @@ export const Spotlight = () => {
|
||||
// anywhere — including inside a panel that has its own key handling.
|
||||
useEffect(() => {
|
||||
const onKey = (ev: KeyboardEvent) => {
|
||||
// `code`, not `key`: shift+space reports `key: ' '` on most layouts and something else on a few, while
|
||||
// `code`, not `key`: ctrl+space reports `key: ' '` on most layouts and something else on a few, while
|
||||
// the physical key is `Space` everywhere.
|
||||
//
|
||||
// Every other modifier must be ABSENT. `ctrl+shift+space` is the theme browser, and one chord opening
|
||||
// both is the bug that took a capture-phase listener to fix on the theme side.
|
||||
// Every other modifier must be ABSENT. `ctrl+shift+space` and `ctrl+alt+space` are the theme
|
||||
// browser, and one chord opening both is the bug that took a capture-phase listener to fix on the
|
||||
// theme side.
|
||||
//
|
||||
// Was shift+space until 2026-08-25: typing fires shift+space constantly (any capitalized word
|
||||
// followed by a space), so the launcher kept popping open mid-sentence. Ctrl+space doesn't happen in
|
||||
// normal typing.
|
||||
//
|
||||
// No focus check. There was one, inherited from shift-space where it was mandatory, and keeping it
|
||||
// "as belt-and-braces" was wrong in the plainest way: focus is usually IN the chat composer, so the
|
||||
// guard meant the launcher did not open from the place it is most often wanted. `preventDefault`
|
||||
// below covers the only thing the guard was protecting against — a layout that would otherwise type
|
||||
// a character.
|
||||
if (ev.shiftKey && !ev.ctrlKey && !ev.altKey && !ev.metaKey && ev.code === 'Space') {
|
||||
if (ev.ctrlKey && !ev.shiftKey && !ev.altKey && !ev.metaKey && ev.code === 'Space') {
|
||||
ev.preventDefault();
|
||||
// Take focus off whatever had it, explicitly, before the overlay mounts. The effect below focuses
|
||||
// this panel's own input, and on paper that is enough — but "on paper" is what produced arrow keys
|
||||
|
||||
@@ -478,11 +478,11 @@
|
||||
"codeEditor": "Code Editor"
|
||||
},
|
||||
"theme": {
|
||||
"pillTitle": "Theme: {{name}} · ctrl+shift+←/→ cycle · ctrl+shift+space browse · ctrl+shift+f {{starAction}}",
|
||||
"pillTitle": "Theme: {{name}} · ctrl+shift+←/→ cycle · ctrl+shift+↑/↓ browse · ctrl+shift+f {{starAction}}",
|
||||
"pillLabel": "Theme: {{name}}",
|
||||
"star": "star",
|
||||
"unstar": "unstar",
|
||||
"cycleHint": "ctrl+shift+←/→ cycle · ctrl+shift+space browse",
|
||||
"cycleHint": "ctrl+shift+←/→ cycle · ctrl+shift+↑/↓ browse",
|
||||
"everyShortcutHint": "ctrl+? for every shortcut",
|
||||
"favouritesOnly": "Favourites only ({{count}})",
|
||||
"favourites": "Favourites ({{count}})",
|
||||
|
||||
@@ -478,11 +478,11 @@
|
||||
"codeEditor": "Editor de código"
|
||||
},
|
||||
"theme": {
|
||||
"pillTitle": "Tema: {{name}} · ctrl+shift+←/→ recorrer · ctrl+shift+space explorar · ctrl+shift+f {{starAction}}",
|
||||
"pillTitle": "Tema: {{name}} · ctrl+shift+←/→ recorrer · ctrl+shift+↑/↓ explorar · ctrl+shift+f {{starAction}}",
|
||||
"pillLabel": "Tema: {{name}}",
|
||||
"star": "marcar",
|
||||
"unstar": "desmarcar",
|
||||
"cycleHint": "ctrl+shift+←/→ recorrer · ctrl+shift+space explorar",
|
||||
"cycleHint": "ctrl+shift+←/→ recorrer · ctrl+shift+↑/↓ explorar",
|
||||
"everyShortcutHint": "ctrl+? para todos los atajos",
|
||||
"favouritesOnly": "Solo favoritos ({{count}})",
|
||||
"favourites": "Favoritos ({{count}})",
|
||||
|
||||
@@ -478,11 +478,11 @@
|
||||
"codeEditor": "Éditeur de code"
|
||||
},
|
||||
"theme": {
|
||||
"pillTitle": "Thème : {{name}} · ctrl+shift+←/→ parcourir · ctrl+shift+space explorer · ctrl+shift+f {{starAction}}",
|
||||
"pillTitle": "Thème : {{name}} · ctrl+shift+←/→ parcourir · ctrl+shift+↑/↓ explorer · ctrl+shift+f {{starAction}}",
|
||||
"pillLabel": "Thème : {{name}}",
|
||||
"star": "marquer",
|
||||
"unstar": "démarquer",
|
||||
"cycleHint": "ctrl+shift+←/→ parcourir · ctrl+shift+space explorer",
|
||||
"cycleHint": "ctrl+shift+←/→ parcourir · ctrl+shift+↑/↓ explorer",
|
||||
"everyShortcutHint": "ctrl+? pour tous les raccourcis",
|
||||
"favouritesOnly": "Favoris uniquement ({{count}})",
|
||||
"favourites": "Favoris ({{count}})",
|
||||
|
||||
@@ -478,11 +478,11 @@
|
||||
"codeEditor": "Editor de código"
|
||||
},
|
||||
"theme": {
|
||||
"pillTitle": "Tema: {{name}} · ctrl+shift+←/→ percorrer · ctrl+shift+space explorar · ctrl+shift+f {{starAction}}",
|
||||
"pillTitle": "Tema: {{name}} · ctrl+shift+←/→ percorrer · ctrl+shift+↑/↓ explorar · ctrl+shift+f {{starAction}}",
|
||||
"pillLabel": "Tema: {{name}}",
|
||||
"star": "marcar",
|
||||
"unstar": "desmarcar",
|
||||
"cycleHint": "ctrl+shift+←/→ percorrer · ctrl+shift+space explorar",
|
||||
"cycleHint": "ctrl+shift+←/→ percorrer · ctrl+shift+↑/↓ explorar",
|
||||
"everyShortcutHint": "ctrl+? para todos os atalhos",
|
||||
"favouritesOnly": "Apenas favoritos ({{count}})",
|
||||
"favourites": "Favoritos ({{count}})",
|
||||
|
||||
@@ -478,11 +478,11 @@
|
||||
"codeEditor": "Kod-editor"
|
||||
},
|
||||
"theme": {
|
||||
"pillTitle": "Tema: {{name}} · ctrl+shift+←/→ bläddra · ctrl+shift+space bläddra i galleri · ctrl+shift+f {{starAction}}",
|
||||
"pillTitle": "Tema: {{name}} · ctrl+shift+←/→ bläddra · ctrl+shift+↑/↓ bläddra i galleri · ctrl+shift+f {{starAction}}",
|
||||
"pillLabel": "Tema: {{name}}",
|
||||
"star": "stjärnmärk",
|
||||
"unstar": "avmarkera stjärna",
|
||||
"cycleHint": "ctrl+shift+←/→ bläddra · ctrl+shift+space galleri",
|
||||
"cycleHint": "ctrl+shift+←/→ bläddra · ctrl+shift+↑/↓ galleri",
|
||||
"everyShortcutHint": "ctrl+? för alla kortkommandon",
|
||||
"favouritesOnly": "Endast favoriter ({{count}})",
|
||||
"favourites": "Favoriter ({{count}})",
|
||||
|
||||
@@ -42,7 +42,7 @@ export const SHORTCUT_SECTIONS: ShortcutSection[] = [
|
||||
titleKey: 'shortcuts.navigation',
|
||||
shortcuts: [
|
||||
{
|
||||
keys: 'Shift+Space',
|
||||
keys: 'Ctrl+Space',
|
||||
descriptionKey: 'shortcuts.spotlight',
|
||||
implementedIn: 'Layout/Spotlight/Spotlight.tsx',
|
||||
},
|
||||
@@ -54,7 +54,7 @@ export const SHORTCUT_SECTIONS: ShortcutSection[] = [
|
||||
noteKey: 'shortcuts.themesNote',
|
||||
shortcuts: [
|
||||
{
|
||||
keys: 'Ctrl+Shift+Space',
|
||||
keys: 'Ctrl+Shift+↑ / ↓',
|
||||
descriptionKey: 'shortcuts.openBrowser',
|
||||
implementedIn: 'theme/useThemeKeys.ts',
|
||||
},
|
||||
|
||||
@@ -9,7 +9,7 @@ import type { ThemeId } from './themes';
|
||||
*
|
||||
* ctrl+shift+→ next theme, from anywhere, no UI
|
||||
* ctrl+shift+← previous theme
|
||||
* ctrl+shift+space open the list, then browse it
|
||||
* ctrl+shift+↑/↓ open the list, then browse it
|
||||
* ctrl+shift+f star or unstar the current theme, from anywhere
|
||||
*
|
||||
* One modifier for all three, which took some choosing. `ctrl+alt+arrows` switches WORKSPACE on GNOME and
|
||||
@@ -78,18 +78,22 @@ export function useThemeKeys(options: ThemeKeysOptions = {}) {
|
||||
const onKeyDown = (event: KeyboardEvent) => {
|
||||
const { themeId: current, focused: focus, open: isOpen } = state.current;
|
||||
|
||||
// Toggle the list. `ctrl+alt+space` is still accepted alongside `ctrl+shift+space` — it is what this
|
||||
// shipped with first, and fingers are quicker to learn than to unlearn.
|
||||
if (event.ctrlKey && (event.shiftKey || event.altKey) && event.code === 'Space') {
|
||||
// Toggle the list. Vertical arrows on the SAME modifier as the horizontal cycle — one family:
|
||||
// `ctrl+shift+←/→` steps the theme, `ctrl+shift+↑/↓` opens the browse. Was `ctrl+shift+space`
|
||||
// (with a `ctrl+alt+space` legacy) until 2026-08-25; both space chords are gone, not kept as
|
||||
// aliases, because `ctrl+space` became Spotlight the same day and space chords near it invite
|
||||
// the wrong reflex.
|
||||
if (event.ctrlKey && event.shiftKey && !event.altKey && (event.key === 'ArrowUp' || event.key === 'ArrowDown')) {
|
||||
event.preventDefault();
|
||||
/*
|
||||
Two problems solved by two lines, and both were reported from the same keystroke.
|
||||
|
||||
`stopPropagation` stops the header's dropdown ALSO opening. The theme pill is a Radix
|
||||
DropdownMenuTrigger, and a focused trigger opens on Space — so if the pill happened to have focus,
|
||||
the chord opened the overlay and the menu behind it. React attaches its handlers at the root, which
|
||||
bubbles before window, so the trigger saw the key first no matter what this listener did with it.
|
||||
This listener now runs in the CAPTURE phase and stops the event dead.
|
||||
DropdownMenuTrigger, and a focused trigger opens on ArrowDown (it did on Space too, when Space
|
||||
was the chord) — so if the pill happened to have focus, the chord opened the overlay and the
|
||||
menu behind it. React attaches its handlers at the root, which bubbles before window, so the
|
||||
trigger saw the key first no matter what this listener did with it. This listener now runs in
|
||||
the CAPTURE phase and stops the event dead.
|
||||
|
||||
The blur stops the overlay opening with focus still in a text field, which made j/k/l do nothing:
|
||||
the guard below correctly refuses to steal keys from an input, and the input never lost focus.
|
||||
|
||||
Reference in New Issue
Block a user