From cfad05e4e293f58b964ea7096fa2ee019ef05515 Mon Sep 17 00:00:00 2001 From: Karl Stolley Date: Mon, 27 Oct 2025 12:50:59 -0500 Subject: [PATCH] compose: Track recent focus on recipient box. --- web/src/compose_setup.ts | 53 +++++++++++++++++++++++++++++++++++----- web/styles/compose.css | 5 ++-- 2 files changed, 50 insertions(+), 8 deletions(-) diff --git a/web/src/compose_setup.ts b/web/src/compose_setup.ts index 3d3877ce2f..a8624ce355 100644 --- a/web/src/compose_setup.ts +++ b/web/src/compose_setup.ts @@ -599,6 +599,15 @@ export function initialize(): void { }); $("textarea#compose-textarea").on("focus", () => { + // To shortcut a delay otherwise introduced when the topic + // input is blurred, we immediately update the topic's + // displayed text and compose-area placeholder when the + // compose textarea is focused. We only do this in channels + // that allow topics. + if (!stream_data.is_empty_topic_only_channel(compose_state.stream_id())) { + const $input = $("input#stream_message_recipient_topic"); + compose_recipient.update_topic_displayed_text($input.val()); + } compose_recipient.update_compose_area_placeholder_text(); compose_fade.do_update_all(); if (narrow_state.narrowed_by_reply()) { @@ -637,20 +646,25 @@ export function initialize(): void { composebox_typeahead.private_message_recipient_typeahead.lookup(false, true); }); + // To track delayed effects originating from the "blur" event + // and its use of setTimeout, we need to set up a variable to + // reference the timeout's ID across events. + let recipient_focused_timeout: ReturnType; $("input#stream_message_recipient_topic").on("focus", () => { + // We don't want the `recently-focused` class removed via + // a setTimeout from the "blur" event, if we're suddenly + // focused again. + clearTimeout(recipient_focused_timeout); + const $compose_recipient = $("#compose-recipient"); const $input = $("input#stream_message_recipient_topic"); compose_recipient.update_topic_displayed_text($input.val(), true); compose_recipient.update_compose_area_placeholder_text(); // When the topic input is focused, we no longer treat // the recipient row as low attention, as we assume the user // is doing something that requires keeping attention called - // to the recipient row + // to the recipient row. compose_recipient.set_high_attention_recipient_row(); - - $("input#stream_message_recipient_topic").one("blur", () => { - compose_recipient.update_topic_displayed_text($input.val()); - compose_recipient.update_compose_area_placeholder_text(); - }); + $compose_recipient.addClass("recently-focused"); }); $("input#stream_message_recipient_topic").on("input", () => { @@ -659,14 +673,41 @@ export function initialize(): void { }); $("#private_message_recipient").on("focus", () => { + // We don't want the `.recently-focused` class removed via + // setTimeout from the "blur" event, if we're suddenly + // focused again. + clearTimeout(recipient_focused_timeout); + const $compose_recipient = $("#compose-recipient"); // When the DM input is focused, we no longer treat // the recipient row as low attention, as we assume the user // is doing something that requires keeping attention called // to the recipient row compose_recipient.set_high_attention_recipient_row(); + $compose_recipient.addClass("recently-focused"); }); $("input#stream_message_recipient_topic, #private_message_recipient").on("blur", () => { + const $compose_recipient = $("#compose-recipient"); + const $input = $("input#stream_message_recipient_topic"); + // To correct for an edge case when clearing the topic box + // via the left sidebar, we do the following actions after a + // delay; these will not have an effect for DMs, and so can + // safely be referenced here. Note, too, that if focus shifts + // immediately from the topic box to the compose textarea, + // we update these things immediately so that no delay is + // apparent on the topic's displayed text or the placeholder + // in the empty compose textarea. + // Also, in case a user quickly opens and closes the compose + // box, we need to clear a previously set timeout before + // setting a new one. Otherwise, the compose box can open + // in a strange state displaying *general chat* and italicizing + // the topic input. + clearTimeout(recipient_focused_timeout); + recipient_focused_timeout = setTimeout(() => { + compose_recipient.update_topic_displayed_text($input.val()); + compose_recipient.update_compose_area_placeholder_text(); + $compose_recipient.removeClass("recently-focused"); + }, 500); compose_recipient.update_recipient_row_attention_level(); }); diff --git a/web/styles/compose.css b/web/styles/compose.css index fa55e6301d..167a47f82d 100644 --- a/web/styles/compose.css +++ b/web/styles/compose.css @@ -1241,9 +1241,10 @@ textarea.new_message_textarea { interactions (e.g., Shift-Tabbing from the compose textarea to the topic box) show instant changes, so we don't need to accommodate them here, which we prevent by applying the - transitions only when focus isn't within the recipient row. */ + transitions only when focus isn't within the recipient row, + or hasn't recently been within the topic box. */ #compose.compose-box-open { - .low-attention-recipient-row:hover:not(:focus-within) { + .low-attention-recipient-row:not(.recently-focused, :focus-within) { #compose_select_recipient_widget, #compose_recipient_box, #compose-direct-recipient .pill-container {