From e635e9e5ea2876a293954970c6a00d6e09cb50a5 Mon Sep 17 00:00:00 2001 From: T <6601329+cookesan@users.noreply.github.com> Date: Sat, 27 Jun 2026 19:38:11 -0300 Subject: [PATCH] feat: add macOS local search navigation shortcuts (#5237) --- .../e2e/local-search/local-search.test.ts | 42 +++++++++++++++++++ .../components/VPLocalSearchBox.vue | 32 ++++++++++++-- 2 files changed, 71 insertions(+), 3 deletions(-) diff --git a/__tests__/e2e/local-search/local-search.test.ts b/__tests__/e2e/local-search/local-search.test.ts index 9496dc6b..6b6df97d 100644 --- a/__tests__/e2e/local-search/local-search.test.ts +++ b/__tests__/e2e/local-search/local-search.test.ts @@ -28,4 +28,46 @@ describe('local search', () => { .count() ).toBe(0) }) + + test('navigate results with macOS Ctrl shortcuts', async () => { + await page.evaluate(() => document.documentElement.classList.add('mac')) + await page.locator('.VPNavBarSearchButton').click() + + const input = await page.waitForSelector('input#localsearch-input') + await input.type('lorem') + + await page.waitForFunction(() => { + return ( + document.querySelectorAll('#localsearch-list li[role=option]').length > + 1 + ) + }) + + expect(await input.getAttribute('aria-activedescendant')).toBe( + 'localsearch-item-0' + ) + + await pressMacCtrl('n') + expect(await input.getAttribute('aria-activedescendant')).toBe( + 'localsearch-item-1' + ) + + await pressMacCtrl('p') + expect(await input.getAttribute('aria-activedescendant')).toBe( + 'localsearch-item-0' + ) + }) }) + +function pressMacCtrl(key: string) { + return page.evaluate((key) => { + window.dispatchEvent( + new KeyboardEvent('keydown', { + key, + ctrlKey: true, + bubbles: true, + cancelable: true + }) + ) + }, key) +} diff --git a/src/client/theme-default/components/VPLocalSearchBox.vue b/src/client/theme-default/components/VPLocalSearchBox.vue index 99abe1ec..81e7a324 100644 --- a/src/client/theme-default/components/VPLocalSearchBox.vue +++ b/src/client/theme-default/components/VPLocalSearchBox.vue @@ -285,7 +285,7 @@ function scrollToSelectedResult() { }) } -onKeyStroke('ArrowUp', (event) => { +function selectPreviousResult(event: KeyboardEvent) { event.preventDefault() selectedIndex.value-- if (selectedIndex.value < 0) { @@ -293,9 +293,9 @@ onKeyStroke('ArrowUp', (event) => { } disableMouseOver.value = true scrollToSelectedResult() -}) +} -onKeyStroke('ArrowDown', (event) => { +function selectNextResult(event: KeyboardEvent) { event.preventDefault() selectedIndex.value++ if (selectedIndex.value >= results.value.length) { @@ -303,6 +303,32 @@ onKeyStroke('ArrowDown', (event) => { } disableMouseOver.value = true scrollToSelectedResult() +} + +function isMacCtrlShortcut(event: KeyboardEvent) { + return ( + event.ctrlKey && + !event.altKey && + !event.metaKey && + !event.shiftKey && + document.documentElement.classList.contains('mac') + ) +} + +onKeyStroke('ArrowUp', selectPreviousResult) + +onKeyStroke('ArrowDown', selectNextResult) + +onKeyStroke(['p', 'P'], (event) => { + if (isMacCtrlShortcut(event)) { + selectPreviousResult(event) + } +}) + +onKeyStroke(['n', 'N'], (event) => { + if (isMacCtrlShortcut(event)) { + selectNextResult(event) + } }) const router = useRouter()