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);