diff --git a/src/components/documents/vault/use-vault-filters.ts b/src/components/documents/vault/use-vault-filters.ts index ab1aa697f..b5db3b48b 100644 --- a/src/components/documents/vault/use-vault-filters.ts +++ b/src/components/documents/vault/use-vault-filters.ts @@ -62,8 +62,12 @@ export function useVaultFilters() { ) { return; } + // No field on screen (loading, an error, nothing recorded yet): the + // key stays the browser's. + const input = searchInputRef.current; + if (!input || !input.isConnected) return; event.preventDefault(); - searchInputRef.current?.focus(); + input.focus(); }; window.addEventListener("keydown", onKeyDown); return () => window.removeEventListener("keydown", onKeyDown); diff --git a/src/components/labs/__tests__/slash-shortcut-without-field.browser.test.tsx b/src/components/labs/__tests__/slash-shortcut-without-field.browser.test.tsx new file mode 100644 index 000000000..379653d7d --- /dev/null +++ b/src/components/labs/__tests__/slash-shortcut-without-field.browser.test.tsx @@ -0,0 +1,51 @@ +import { describe, expect, it } from "vitest"; + +import { renderWithApp } from "../../../../tests/browser/render"; +import { useVaultFilters } from "../../documents/vault/use-vault-filters"; +import { useLabListFilters } from "../use-lab-list-filters"; + +/** + * "/" focuses a list's search field. While the page has none on screen (still + * loading, an error, nothing recorded yet) the key must stay the browser's, + * such as Firefox's quick find, and not be swallowed for a field that is not + * there. + */ + +function LabsWithoutRail() { + useLabListFilters(); + return ( + + ); +} + +function VaultWithoutSearch() { + useVaultFilters(); + return ( + + ); +} + +function pressSlash(target: Element): boolean { + const event = new KeyboardEvent("keydown", { + key: "/", + bubbles: true, + cancelable: true, + }); + target.dispatchEvent(event); + return event.defaultPrevented; +} + +describe('"/" with no search field on screen', () => { + it.each([ + ["labs", LabsWithoutRail], + ["documents", VaultWithoutSearch], + ])("leaves the key alone on %s", async (_name, View) => { + const { screen } = await renderWithApp(); + const button = screen.getByRole("button", { name: "Elsewhere" }).element(); + expect(pressSlash(button)).toBe(false); + }); +}); diff --git a/src/components/labs/use-lab-list-filters.ts b/src/components/labs/use-lab-list-filters.ts index 60e8cd675..8eae6d35c 100644 --- a/src/components/labs/use-lab-list-filters.ts +++ b/src/components/labs/use-lab-list-filters.ts @@ -81,8 +81,12 @@ export function useLabListFilters() { ) { return; } + // No field on screen (loading, an error, nothing recorded yet): the + // key stays the browser's. + const input = searchInputRef.current; + if (!input || !input.isConnected) return; event.preventDefault(); - searchInputRef.current?.focus(); + input.focus(); }; window.addEventListener("keydown", onKeyDown); return () => window.removeEventListener("keydown", onKeyDown);