mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-07-26 11:49:16 +00:00
fix(search): don't open the command palette via Cmd/Ctrl+K while typing (#1414)
The global command palette's Cmd/Ctrl+K shortcut fired even when a text field or the code editor held focus, stealing focus mid-edit and, on macOS, clobbering the native Ctrl+K delete-to-end-of-line. Guard the handler with isInputFocused() so the shortcut opens search only when the user is not typing in a field; the toolbar search icon still opens it from anywhere. Add unit tests for the keyboard path.
This commit is contained in:
@@ -18,6 +18,7 @@ import {
|
||||
} from '@/components/ui/command';
|
||||
import { DialogDescription, DialogTitle } from '@/components/ui/dialog';
|
||||
import { useNodes, type Node } from '@/context/NodeContext';
|
||||
import { isInputFocused } from '@/lib/keyboard-guards';
|
||||
import { cn } from '@/lib/utils';
|
||||
import {
|
||||
useCrossNodeStackSearch,
|
||||
@@ -51,6 +52,10 @@ export function GlobalCommandPaletteProvider({ children }: { children: ReactNode
|
||||
// Let cmdk's own Ctrl+K handling take precedence when focus is already inside a palette
|
||||
const target = e.target as HTMLElement | null;
|
||||
if (target?.closest('[cmdk-root]')) return;
|
||||
// Don't hijack Cmd/Ctrl+K while the user is typing in a field or the code
|
||||
// editor: stealing focus mid-edit is surprising, and on macOS Ctrl+K is the
|
||||
// native delete-to-end-of-line. Search still opens elsewhere, or via the icon.
|
||||
if (isInputFocused()) return;
|
||||
e.preventDefault();
|
||||
setOpen(prev => !prev);
|
||||
}
|
||||
|
||||
@@ -62,6 +62,18 @@ function type(value: string) {
|
||||
fireEvent.change(screen.getByPlaceholderText('Search the app...'), { target: { value } });
|
||||
}
|
||||
|
||||
// Focus a real text input for the duration of `run`, then always detach it.
|
||||
function withFocusedInput(run: (input: HTMLInputElement) => void) {
|
||||
const input = document.createElement('input');
|
||||
document.body.appendChild(input);
|
||||
input.focus();
|
||||
try {
|
||||
run(input);
|
||||
} finally {
|
||||
document.body.removeChild(input);
|
||||
}
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
hookReturn = { hits: [], failedNodes: [], loading: false };
|
||||
nodesValue = {
|
||||
@@ -208,4 +220,36 @@ describe('GlobalCommandPalette', () => {
|
||||
type('zzzz');
|
||||
expect(screen.getByText('Searching...')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('opens on Ctrl+K when no field is focused', () => {
|
||||
renderPalette();
|
||||
fireEvent.keyDown(document.body, { key: 'k', ctrlKey: true });
|
||||
expect(screen.getByPlaceholderText('Search the app...')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('ignores Ctrl+K while a text field is focused (no focus hijack)', () => {
|
||||
renderPalette();
|
||||
withFocusedInput(input => {
|
||||
fireEvent.keyDown(input, { key: 'k', ctrlKey: true });
|
||||
expect(screen.queryByPlaceholderText('Search the app...')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('ignores Cmd+K (metaKey) while a text field is focused', () => {
|
||||
renderPalette();
|
||||
withFocusedInput(input => {
|
||||
fireEvent.keyDown(input, { key: 'k', metaKey: true });
|
||||
expect(screen.queryByPlaceholderText('Search the app...')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('does not close the open palette when Ctrl+K is pressed inside it', () => {
|
||||
// The [cmdk-root] early-return must run before the input-focus guard, so the
|
||||
// app handler never toggles the palette closed while focus is in its own input.
|
||||
renderPalette();
|
||||
open();
|
||||
const paletteInput = screen.getByPlaceholderText('Search the app...');
|
||||
fireEvent.keyDown(paletteInput, { key: 'k', ctrlKey: true });
|
||||
expect(screen.getByPlaceholderText('Search the app...')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user