Skip to content

Commit 282b8bb

Browse files
Focus search bar when typing on frontpage (#2696)
* Initial plan * Add autofocus prop to search bar for frontpage Co-authored-by: CommanderStorm <26258709+CommanderStorm@users.noreply.github.com> * Add tests for search bar autofocus functionality Co-authored-by: CommanderStorm <26258709+CommanderStorm@users.noreply.github.com> * Only autofocus search when feedback form and search bar are closed Co-authored-by: CommanderStorm <26258709+CommanderStorm@users.noreply.github.com> * Focus search bar when typing on frontpage (not on page load) Co-authored-by: CommanderStorm <26258709+CommanderStorm@users.noreply.github.com> * Address code review: allow Shift key and optimize IGNORED_KEYS constant Co-authored-by: CommanderStorm <26258709+CommanderStorm@users.noreply.github.com> * Optimize performance: use Set for IGNORED_KEYS and cache search element Co-authored-by: CommanderStorm <26258709+CommanderStorm@users.noreply.github.com> * Fix SSR safety and add comprehensive tests for keyboard handling Co-authored-by: CommanderStorm <26258709+CommanderStorm@users.noreply.github.com> * Remove unnecessary searchElement cleanup in onUnmounted Co-authored-by: CommanderStorm <26258709+CommanderStorm@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: CommanderStorm <26258709+CommanderStorm@users.noreply.github.com> Co-authored-by: Frank Elsinga <frank@elsinga.de>
1 parent e30943d commit 282b8bb

File tree

3 files changed

+93
-0
lines changed

3 files changed

+93
-0
lines changed

tests/specs/main.ui.spec.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,47 @@ test.describe("Homepage", () => {
2424
// await expect(page).toHaveScreenshot();
2525
});
2626

27+
test("should focus search bar when typing on homepage", async ({ page }) => {
28+
await page.goto("/", { waitUntil: "networkidle" });
29+
30+
const searchInput = page.getByRole("textbox", { name: "Suchfeld" }).first();
31+
32+
// Initially, search bar should not be focused
33+
await expect(searchInput).not.toBeFocused();
34+
35+
// Simulate typing a lowercase character
36+
await page.keyboard.press("a");
37+
38+
// Now search bar should be focused
39+
await expect(searchInput).toBeFocused();
40+
});
41+
42+
test("should focus search bar when typing uppercase letters", async ({ page }) => {
43+
await page.goto("/", { waitUntil: "networkidle" });
44+
45+
const searchInput = page.getByRole("textbox", { name: "Suchfeld" }).first();
46+
47+
await expect(searchInput).not.toBeFocused();
48+
49+
// Type Shift+A (uppercase A)
50+
await page.keyboard.press("A");
51+
52+
await expect(searchInput).toBeFocused();
53+
});
54+
55+
test("should not focus search bar when pressing Tab key", async ({ page }) => {
56+
await page.goto("/", { waitUntil: "networkidle" });
57+
58+
const searchInput = page.getByRole("textbox", { name: "Suchfeld" }).first();
59+
60+
await expect(searchInput).not.toBeFocused();
61+
62+
// Press Tab - should not focus search bar
63+
await page.keyboard.press("Tab");
64+
65+
await expect(searchInput).not.toBeFocused();
66+
});
67+
2768
test("should display footer with links", async ({ page }) => {
2869
await page.goto("/", { waitUntil: "networkidle" });
2970

tests/specs/search.ui.spec.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,21 @@ test.describe("Search Bar - Interactive Search", () => {
7777

7878
await expect(page).toHaveURL("/en/search?q=MI");
7979
});
80+
81+
test("should not focus search bar when typing on search results page", async ({ page }) => {
82+
await page.goto("/search?q=MI", { waitUntil: "networkidle" });
83+
84+
const searchInput = page.getByRole("textbox", { name: "Suchfeld" }).first();
85+
86+
// Initially not focused
87+
await expect(searchInput).not.toBeFocused();
88+
89+
// Type a character - should NOT focus search bar on non-index page
90+
await page.keyboard.press("a");
91+
92+
// Should still not be focused
93+
await expect(searchInput).not.toBeFocused();
94+
});
8095
});
8196

8297
test.describe("Search Page - Filtering and Pagination", () => {

webclient/app/layouts/default.vue

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,43 @@ import { useFeedback } from "~/composables/feedback";
33
44
const searchBarFocused = ref(false);
55
const feedback = useFeedback();
6+
const route = useRoute();
7+
const searchElement = ref<HTMLElement | null>(null);
8+
9+
const IGNORED_KEYS = new Set(['Escape', 'Tab', 'Enter', 'ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'Home', 'End', 'PageUp', 'PageDown', 'Delete', 'Backspace']);
10+
11+
function handleKeyDown(event: KeyboardEvent) {
12+
const isIndexPage = route.path === "/" || route.path === "/en";
13+
14+
// Only handle on index page, when feedback is closed and search bar is not focused
15+
if (!isIndexPage || feedback.value.open || searchBarFocused.value) {
16+
return;
17+
}
18+
19+
// Ignore special keys (Ctrl, Alt, Meta - but allow Shift for uppercase/symbols)
20+
if (event.ctrlKey || event.altKey || event.metaKey) {
21+
return;
22+
}
23+
24+
// Ignore non-printable keys
25+
if (IGNORED_KEYS.has(event.key)) {
26+
return;
27+
}
28+
29+
// Focus the search bar when user starts typing
30+
if (event.key.length === 1 && searchElement.value) {
31+
searchElement.value.focus();
32+
}
33+
}
34+
35+
onMounted(() => {
36+
searchElement.value = document.getElementById("search");
37+
document.addEventListener("keydown", handleKeyDown);
38+
});
39+
40+
onUnmounted(() => {
41+
document.removeEventListener("keydown", handleKeyDown);
42+
});
643
744
const i18nHead = useLocaleHead({ dir: true, seo: true });
845
useHead({

0 commit comments

Comments
 (0)