From 7b9f0e4c3d7313e7e329baec58b84ffadbd2cd38 Mon Sep 17 00:00:00 2001 From: Nariman Jelveh Date: Thu, 17 Sep 2026 13:10:54 -0700 Subject: [PATCH] fix: keep the recipient field focused when the picker is wired onto it Both sharing dialogs focus the recipient field as they open, and the picker then moves that field into its wrapper. Moving an element takes it out of the document for an instant, which drops its focus, so every dialog opened with the caret in the field opened with the caret on instead. --- src/gui/src/helpers/shareRecipientPicker.js | 5 +++++ src/gui/src/helpers/shareRecipientPicker.test.js | 13 ++++++++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/gui/src/helpers/shareRecipientPicker.js b/src/gui/src/helpers/shareRecipientPicker.js index 16223703e..b7271ee4a 100644 --- a/src/gui/src/helpers/shareRecipientPicker.js +++ b/src/gui/src/helpers/shareRecipientPicker.js @@ -76,8 +76,13 @@ export default function shareRecipientPicker ({ const list_id = `share-suggest-list-${++picker_seq}`; const $field = $('
'); + // Both dialogs focus this field as they open, and an element that leaves + // the document — which is what moving it into the wrapper does — takes its + // focus with it. Put it back rather than leaving the caller to notice. + const had_focus = document.activeElement === $input.get(0); $input.before($field); $field.append($input); + if ( had_focus ) $input.get(0)?.focus({ preventScroll: true }); const $clear = $(``); $field.append($clear); diff --git a/src/gui/src/helpers/shareRecipientPicker.test.js b/src/gui/src/helpers/shareRecipientPicker.test.js index 761cac1cd..4160d35b5 100644 --- a/src/gui/src/helpers/shareRecipientPicker.test.js +++ b/src/gui/src/helpers/shareRecipientPicker.test.js @@ -38,7 +38,7 @@ beforeAll(async () => { let mounted = []; /** Renders the markup both dialogs share and attaches a picker to it. */ -const mount = (opts = {}) => { +const mount = ({ focus_first = false, ...opts } = {}) => { document.body.innerHTML = `
@@ -47,6 +47,8 @@ const mount = (opts = {}) => {
`; const $input = $('.recipient'); + // Both dialogs focus the field before the picker is wired onto it. + if ( focus_first ) $input.get(0).focus(); const picker = shareRecipientPicker({ $input, $row: $('.row'), @@ -94,6 +96,15 @@ afterEach(() => { document.body.innerHTML = ''; }); +describe('wiring onto the field', () => { + it('keeps the focus the dialog put there before wiring', () => { + const { $input } = mount({ focus_first: true }); + // Moving the field into the wrapper takes it out of the document for + // an instant, which is enough to drop focus if nobody puts it back. + expect(document.activeElement).toBe($input.get(0)); + }); +}); + describe('opening the list', () => { it('stays shut until the field is actually used', async () => { const { $input } = mount();