From 63b05ff5ff5780d1884141780716401b6bed2b8e Mon Sep 17 00:00:00 2001 From: PathGao Date: Mon, 10 Aug 2026 18:23:53 +0800 Subject: [PATCH] fix(find): re-focus the preview find bar on a repeated Cmd/Ctrl+F (#559) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pressing the shortcut again while the find bar was already open did nothing: the handler only ran `findOpen = true`, which writes the value the state already holds, so the `$effect` that focuses the input never re-ran. After clicking into the document the field could only be reached with the mouse. Focus is now requested explicitly, and the existing `select()` brings the previous query back highlighted so typing replaces it — what Chrome, Firefox and VS Code all do on a repeated find shortcut. Co-Authored-By: Claude Opus 5 --- scripts/findRefocus.test.ts | 35 +++++++++++++++++++++++++++++++ src/lib/MarkdownViewer.svelte | 10 ++++++++- src/lib/components/FindBar.svelte | 19 +++++++++++++---- 3 files changed, 59 insertions(+), 5 deletions(-) create mode 100644 scripts/findRefocus.test.ts diff --git a/scripts/findRefocus.test.ts b/scripts/findRefocus.test.ts new file mode 100644 index 00000000..6080236f --- /dev/null +++ b/scripts/findRefocus.test.ts @@ -0,0 +1,35 @@ +import assert from 'node:assert/strict'; +import test from 'node:test'; + +import { functionSource, readSource } from './sourceTree.js'; + +// #559: Cmd/Ctrl+F in the preview only ever set `findOpen = true`. Once the bar +// was open, clicking into the document moved focus out of the input and the +// shortcut became inert — the assignment writes the value the state already +// has, so the `$effect` that focuses on open never re-runs. The fix is an +// explicit focus call on the component, so the second press behaves like the +// first in every browser find bar: caret back in the field, previous query +// selected so typing replaces it. + +const viewer = readSource('src/lib/MarkdownViewer.svelte'); +const findBar = readSource('src/lib/components/FindBar.svelte'); + +test('the preview find shortcut focuses the bar rather than only opening it', () => { + const trigger = functionSource(viewer, 'triggerFindAction'); + assert.match(trigger, /findBar\?\.focusInput\(\)/); +}); + +test('focusInput selects the previous query instead of clearing it', () => { + const focusInput = functionSource(findBar, 'focusInput'); + assert.match(focusInput, /inputEl\?\.focus\(\)/); + assert.match(focusInput, /inputEl\?\.select\(\)/); + // Clearing here would defeat the point: the issue asks for the previous + // search to come back lit, not for an empty field. + assert.doesNotMatch(focusInput, /query\s*=/); +}); + +test('opening the bar and re-focusing it share one implementation', () => { + // Two copies would drift; the one that stopped selecting would be the one + // nobody re-reads. + assert.equal(findBar.match(/inputEl\?\.focus\(\)/g)?.length, 1); +}); diff --git a/src/lib/MarkdownViewer.svelte b/src/lib/MarkdownViewer.svelte index 3e4a82f2..c5e99145 100644 --- a/src/lib/MarkdownViewer.svelte +++ b/src/lib/MarkdownViewer.svelte @@ -160,7 +160,11 @@ import { createDocumentSession, type LoadMarkdownOptions } from './sessions/docu let liveMode = $state(false); let findOpen = $state(false); - let findBar = $state<{ reapply: () => void; clearHighlights: () => void } | null>(null); + let findBar = $state<{ + reapply: () => void; + clearHighlights: () => void; + focusInput: () => void; + } | null>(null); // Decide where Cmd/Ctrl+F should land based on what's visible and where // focus is. The in-window shortcut remains the canonical route on every @@ -172,7 +176,11 @@ import { createDocumentSession, type LoadMarkdownOptions } from './sessions/docu if (editorHasFocus || !previewVisible) { editorPane?.triggerFind?.(); } else if (markdownBody) { + // Focus explicitly: once the bar is open, `findOpen = true` changes + // nothing, so a repeated shortcut after clicking into the document + // used to be swallowed (#559). findOpen = true; + findBar?.focusInput(); } } diff --git a/src/lib/components/FindBar.svelte b/src/lib/components/FindBar.svelte index 3393b38c..e64d7c18 100644 --- a/src/lib/components/FindBar.svelte +++ b/src/lib/components/FindBar.svelte @@ -288,6 +288,20 @@ }, DEBOUNCE_MS); } + /** + * Put the caret back in the input with the previous query selected. + * Re-opening an already-open bar is a no-op — `open` never changes, so + * the effect below does not re-run — which left Cmd/Ctrl+F doing nothing + * once the user had clicked into the document (#559). Chrome, Firefox and + * VS Code all re-focus and re-select on a repeated find shortcut. + */ + export function focusInput() { + tick().then(() => { + inputEl?.focus(); + inputEl?.select(); + }); + } + export function reapply() { // Public hook for parent: call after the preview HTML is replaced // so existing matches survive across re-renders. @@ -324,10 +338,7 @@ return; } // On open, focus and select the input so typing replaces. - tick().then(() => { - inputEl?.focus(); - inputEl?.select(); - }); + focusInput(); }); function handleKeydown(e: KeyboardEvent) {