diff --git a/packages/command-palette/src/__tests__/command-palette.test.tsx b/packages/command-palette/src/__tests__/command-palette.test.tsx
index 84e1a5bf..e0f3d0e4 100644
--- a/packages/command-palette/src/__tests__/command-palette.test.tsx
+++ b/packages/command-palette/src/__tests__/command-palette.test.tsx
@@ -85,7 +85,7 @@ describe('CommandRegistryProvider', () => {
]);
return useCommands();
},
- { wrapper: wrapper() },
+ { wrapper: wrapper() }
);
expect(result.current.map(c => c.id).sort()).toEqual(['temp', 'temp2']);
unmount();
@@ -96,9 +96,7 @@ describe('CommandRegistryProvider', () => {
});
it('throws when used outside provider', () => {
- expect(() => renderHook(() => useCommands())).toThrow(
- /CommandRegistryProvider/,
- );
+ expect(() => renderHook(() => useCommands())).toThrow(/CommandRegistryProvider/);
});
});
@@ -128,7 +126,7 @@ describe('CommandPalette', () => {
render(
{}} />
- ,
+
);
expect(screen.queryByTestId('bl-cmdk')).toBeNull();
});
@@ -137,7 +135,7 @@ describe('CommandPalette', () => {
render(
{}} />
- ,
+
);
expect(screen.getByTestId('bl-cmdk-panel')).toBeDefined();
expect(screen.getByTestId('bl-cmdk-item-new-task')).toBeDefined();
@@ -149,7 +147,7 @@ describe('CommandPalette', () => {
render(
{}} />
- ,
+
);
expect(screen.queryByTestId('bl-cmdk-item-gated')).toBeNull();
});
@@ -158,7 +156,7 @@ describe('CommandPalette', () => {
render(
{}} />
- ,
+
);
const dialog = screen.getByTestId('bl-cmdk');
fireEvent.keyDown(dialog, { key: 'Tab' });
@@ -171,7 +169,7 @@ describe('CommandPalette', () => {
render(
{}} />
- ,
+
);
fireEvent.change(screen.getByTestId('bl-cmdk-input'), {
target: { value: 'task' },
@@ -184,10 +182,10 @@ describe('CommandPalette', () => {
render(
- ,
+
);
fireEvent.keyDown(screen.getByTestId('bl-cmdk'), { key: 'Enter' });
- expect((seed[0].run as ReturnType)).toHaveBeenCalledOnce();
+ expect(seed[0].run as ReturnType).toHaveBeenCalledOnce();
expect(onClose).toHaveBeenCalled();
});
@@ -197,7 +195,7 @@ describe('CommandPalette', () => {
render(
- ,
+
);
fireEvent.keyDown(screen.getByTestId('bl-cmdk'), { key: 'Tab' });
fireEvent.keyDown(screen.getByTestId('bl-cmdk'), { key: 'Enter' });
@@ -209,7 +207,7 @@ describe('CommandPalette', () => {
render(
{}} />
- ,
+
);
const dialog = screen.getByTestId('bl-cmdk');
fireEvent.keyDown(dialog, { key: 'ArrowDown' });
@@ -225,7 +223,7 @@ describe('CommandPalette', () => {
render(
- ,
+
);
fireEvent.keyDown(document, { key: 'Escape' });
expect(onClose).toHaveBeenCalledOnce();
@@ -235,7 +233,7 @@ describe('CommandPalette', () => {
const { rerender } = render(
{}} initialMode="ask-ai" />
- ,
+
);
expect(screen.getByTestId('bl-cmdk-ask-ai')).toBeDefined();
@@ -247,7 +245,7 @@ describe('CommandPalette', () => {
initialMode="ask-ai"
askAiPanel={q => {q || 'idle'}
}
/>
- ,
+
);
expect(screen.getByTestId('custom-ai').textContent).toBe('idle');
});
@@ -256,12 +254,12 @@ describe('CommandPalette', () => {
render(
{}} />
- ,
+
);
const row = screen.getByTestId('bl-cmdk-item-archive');
expect(row.getAttribute('aria-disabled')).toBe('true');
fireEvent.click(row);
- expect((seed[3].run as ReturnType)).not.toHaveBeenCalled();
+ expect(seed[3].run as ReturnType).not.toHaveBeenCalled();
});
it('recents persist to localStorage when a command is run', () => {
@@ -294,7 +292,7 @@ describe('CommandPalette', () => {
render(
{}} />
- ,
+
);
fireEvent.click(screen.getByTestId('bl-cmdk-item-new-task'));
expect(mem['bl-cmdk-recents']).toContain('new-task');
@@ -315,16 +313,12 @@ describe('useCommandPalette', () => {
expect(result.current.open).toBe(false);
act(() => {
- window.dispatchEvent(
- new KeyboardEvent('keydown', { key: 'k', metaKey: true }),
- );
+ window.dispatchEvent(new KeyboardEvent('keydown', { key: 'k', metaKey: true }));
});
expect(result.current.open).toBe(true);
act(() => {
- window.dispatchEvent(
- new KeyboardEvent('keydown', { key: 'k', metaKey: true }),
- );
+ window.dispatchEvent(new KeyboardEvent('keydown', { key: 'k', metaKey: true }));
});
expect(result.current.open).toBe(false);
@@ -335,6 +329,19 @@ describe('useCommandPalette', () => {
expect(result.current.open).toBe(false);
});
+ it('ignores keydown events with an undefined key (autofill/IME) without throwing', () => {
+ const { result } = renderHook(() => useCommandPalette());
+ expect(() =>
+ act(() => {
+ // Some events (autofill, IME) dispatch keydown with no `key` set.
+ const ev = new KeyboardEvent('keydown', { metaKey: true });
+ Object.defineProperty(ev, 'key', { value: undefined });
+ window.dispatchEvent(ev);
+ })
+ ).not.toThrow();
+ expect(result.current.open).toBe(false);
+ });
+
it('show/hide/toggle imperative helpers', () => {
const { result } = renderHook(() => useCommandPalette({ hotkey: null }));
act(() => result.current.show());
@@ -348,9 +355,7 @@ describe('useCommandPalette', () => {
});
it('respects defaultOpen', () => {
- const { result } = renderHook(() =>
- useCommandPalette({ defaultOpen: true, hotkey: null }),
- );
+ const { result } = renderHook(() => useCommandPalette({ defaultOpen: true, hotkey: null }));
expect(result.current.open).toBe(true);
});
});
diff --git a/packages/command-palette/src/useCommandPalette.ts b/packages/command-palette/src/useCommandPalette.ts
index f739287a..93fc78c5 100644
--- a/packages/command-palette/src/useCommandPalette.ts
+++ b/packages/command-palette/src/useCommandPalette.ts
@@ -32,10 +32,9 @@ export interface UseCommandPaletteHelpers {
* inputs unless the user explicitly holds the modifier.
*/
export function useCommandPalette(
- options: UseCommandPaletteOptions = {},
+ options: UseCommandPaletteOptions = {}
): UseCommandPaletteHelpers {
- const { hotkey = { key: 'k', meta: true, ctrl: true }, defaultOpen = false } =
- options;
+ const { hotkey = { key: 'k', meta: true, ctrl: true }, defaultOpen = false } = options;
const [open, setOpen] = useState(defaultOpen);
const show = useCallback(() => setOpen(true), []);
@@ -45,6 +44,9 @@ export function useCommandPalette(
useEffect(() => {
if (!hotkey) return;
const handler = (e: KeyboardEvent) => {
+ // Some keydown events (autofill, IME/composition, certain extensions) arrive
+ // with an undefined `key`; guard so we never crash on `.toLowerCase()`.
+ if (!e.key || !hotkey.key) return;
if (e.key.toLowerCase() !== hotkey.key.toLowerCase()) return;
const wantMeta = hotkey.meta ?? false;
const wantCtrl = hotkey.ctrl ?? false;