mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-07-26 11:49:16 +00:00
fix(global-search): close the command palette on Escape deterministically (#1256)
When the global command palette was opened with Ctrl+K, pressing Escape sometimes failed to close it. While the palette is open the cross-node stack search streams results in and re-renders the list; an Escape that landed during that re-render churn was intermittently dropped before the dialog dismissed, leaving the palette stuck open. The palette now handles Escape on its search input and closes itself, instead of depending only on the dialog's built-in dismissal. The close is idempotent, so nothing changes when the dialog already dismisses on the same key. Adds a unit test covering the Escape close.
This commit is contained in:
@@ -161,6 +161,19 @@ export function GlobalCommandPalette({ navItems, onNavigate, onSelectStack }: Gl
|
||||
placeholder="Search the app..."
|
||||
value={query}
|
||||
onValueChange={setQuery}
|
||||
// Own the Escape-to-close instead of relying solely on Radix
|
||||
// Dialog's dismissable layer. While the palette is open the
|
||||
// cross-node stack search streams results in and re-renders the
|
||||
// tree; an Escape that lands during that churn was observed to be
|
||||
// dropped before Radix's layer dismissed it. Closing here via
|
||||
// handleOpenChange(false) keeps Escape deterministic and is
|
||||
// idempotent if Radix also dismisses on the same key.
|
||||
onKeyDown={e => {
|
||||
if (e.key === 'Escape') {
|
||||
e.preventDefault();
|
||||
handleOpenChange(false);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<CommandList>
|
||||
{showEmptyState && (
|
||||
|
||||
@@ -166,6 +166,22 @@ describe('GlobalCommandPalette', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('closes the palette when Escape is pressed in the search input', async () => {
|
||||
renderPalette();
|
||||
open();
|
||||
const input = screen.getByPlaceholderText('Search the app...');
|
||||
fireEvent.keyDown(input, { key: 'Escape' });
|
||||
// Behavioral smoke test only: in jsdom Radix's dismissable layer already
|
||||
// closes a single-layer dialog on Escape, so this cannot isolate the
|
||||
// palette's own Escape handler from that fallback. The deterministic-close
|
||||
// regression the handler fixes (Escape dropped during streaming re-renders)
|
||||
// is guarded by the Playwright command-palette spec.
|
||||
await waitFor(() => {
|
||||
const dialog = screen.queryByRole('dialog');
|
||||
expect(dialog?.getAttribute('data-state') ?? 'unmounted').not.toBe('open');
|
||||
});
|
||||
});
|
||||
|
||||
it('disables an offline node row', () => {
|
||||
nodesValue = {
|
||||
nodes: [node(1, 'local'), node(2, 'opsix', 'offline')],
|
||||
|
||||
Reference in New Issue
Block a user