From 695946746e045a6d687078d2445483de3aa37974 Mon Sep 17 00:00:00 2001 From: evykassirer Date: Mon, 17 Apr 2023 20:13:50 -0700 Subject: [PATCH] compose: Rename stream selection widget to "select_recipient". Previously this dropdown was only for selecting streams, but soon it will also be for switching to a private message. This name helps it be clearer that the dropdown is more general purpose. --- web/e2e-tests/compose.test.ts | 15 ++++++++------- web/e2e-tests/drafts.test.ts | 8 ++++---- web/e2e-tests/lib/common.ts | 4 ++-- web/e2e-tests/mention.test.ts | 2 +- web/src/click_handlers.js | 2 +- web/src/compose_actions.js | 14 +++++++------- web/src/compose_banner.ts | 4 ++-- web/src/compose_recipient.js | 24 ++++++++++++------------ web/src/compose_state.js | 10 +++++----- web/src/compose_ui.js | 4 ++-- web/src/compose_validate.js | 4 ++-- web/styles/compose.css | 18 +++++++----------- web/templates/compose.hbs | 4 ++-- web/tests/compose.test.js | 2 +- web/tests/compose_actions.test.js | 8 ++++---- web/tests/compose_fade.test.js | 2 +- web/tests/compose_state.test.js | 2 +- web/tests/compose_validate.test.js | 4 ++-- web/tests/composebox_typeahead.test.js | 2 +- web/tests/drafts.test.js | 2 +- web/tests/lib/compose.js | 2 +- web/tests/narrow.test.js | 4 ++-- 22 files changed, 69 insertions(+), 72 deletions(-) diff --git a/web/e2e-tests/compose.test.ts b/web/e2e-tests/compose.test.ts index bbc6e8045d..bb36cfbd80 100644 --- a/web/e2e-tests/compose.test.ts +++ b/web/e2e-tests/compose.test.ts @@ -13,11 +13,12 @@ async function check_compose_form_empty(page: Page): Promise { } async function close_compose_box(page: Page): Promise { - const stream_dropdown_visible = (await page.$("#compose_select_stream_widget .open")) !== null; + const recipient_dropdown_visible = + (await page.$("#compose_select_recipient_widget .open")) !== null; - if (stream_dropdown_visible) { + if (recipient_dropdown_visible) { await page.keyboard.press("Escape"); - await page.waitForSelector("#id_compose_select_stream.open", {hidden: true}); + await page.waitForSelector("#id_compose_select_recipient.open", {hidden: true}); } await page.keyboard.press("Escape"); await page.waitForSelector("#compose-textarea", {hidden: true}); @@ -40,7 +41,7 @@ async function test_send_messages(page: Page): Promise { async function test_stream_compose_keyboard_shortcut(page: Page): Promise { await page.keyboard.press("KeyC"); - await page.waitForSelector("#compose-stream-recipient", {visible: true}); + await page.waitForSelector("#stream_message_recipient_topic", {visible: true}); await check_compose_form_empty(page); await close_compose_box(page); } @@ -100,9 +101,9 @@ async function test_reply_with_r_shortcut(page: Page): Promise { } async function test_open_close_compose_box(page: Page): Promise { - await page.waitForSelector("#compose-stream-recipient", {visible: true}); + await page.waitForSelector("#stream_message_recipient_topic", {visible: true}); await close_compose_box(page); - await page.waitForSelector("#compose-stream-recipient", {hidden: true}); + await page.waitForSelector("#stream_message_recipient_topic", {hidden: true}); await page.keyboard.press("KeyX"); await page.waitForSelector("#compose-private-recipient", {visible: true}); @@ -125,7 +126,7 @@ async function test_narrow_to_private_messages_with_cordelia(page: Page): Promis await page.keyboard.press("KeyC"); await page.waitForSelector("#compose", {visible: true}); - await page.waitForSelector(".compose_table #id_compose_select_stream.open", { + await page.waitForSelector(".compose_table #id_compose_select_recipient.open", { visible: true, }); await close_compose_box(page); diff --git a/web/e2e-tests/drafts.test.ts b/web/e2e-tests/drafts.test.ts index b0807c53c6..6546015ef5 100644 --- a/web/e2e-tests/drafts.test.ts +++ b/web/e2e-tests/drafts.test.ts @@ -34,8 +34,8 @@ async function test_empty_drafts(page: Page): Promise { async function create_stream_message_draft(page: Page): Promise { console.log("Creating stream message draft"); await page.keyboard.press("KeyC"); - await page.waitForSelector("#compose-stream-recipient", {visible: true}); - await common.select_item_via_dropdown(page, "#compose_select_stream_widget", "Denmark"); + await page.waitForSelector("#stream_message_recipient_topic", {visible: true}); + await common.select_item_via_dropdown(page, "#compose_select_recipient_widget", "Denmark"); await common.fill_form(page, "form#send_message_form", { stream_message_recipient_topic: "tests", content: "Test stream message.", @@ -114,7 +114,7 @@ async function test_restore_message_draft_via_draft_overlay(page: Page): Promise console.log("Restoring stream message draft"); await page.click("#drafts_table .message_row:not(.private-message) .restore-draft"); await wait_for_drafts_to_disappear(page); - await page.waitForSelector("#compose-stream-recipient", {visible: true}); + await page.waitForSelector("#stream_message_recipient_topic", {visible: true}); await page.waitForSelector("#preview_message_area", {hidden: true}); await common.check_compose_state(page, { stream: "Denmark", @@ -129,7 +129,7 @@ async function test_restore_message_draft_via_draft_overlay(page: Page): Promise } async function edit_stream_message_draft(page: Page): Promise { - await common.select_item_via_dropdown(page, "#compose_select_stream_widget", "Denmark"); + await common.select_item_via_dropdown(page, "#compose_select_recipient_widget", "Denmark"); await common.fill_form(page, "form#send_message_form", { stream_message_recipient_topic: "tests", content: "Updated stream message", diff --git a/web/e2e-tests/lib/common.ts b/web/e2e-tests/lib/common.ts index 668223aef1..670b3a0722 100644 --- a/web/e2e-tests/lib/common.ts +++ b/web/e2e-tests/lib/common.ts @@ -210,7 +210,7 @@ export async function check_compose_state( const form_params: Record = {content: params.content}; if (params.stream) { assert.equal( - await get_text_from_selector(page, "#compose_select_stream_name"), + await get_text_from_selector(page, "#compose_select_recipient_name"), params.stream, ); } @@ -432,7 +432,7 @@ export async function send_message( } if (params.stream) { - await select_item_via_dropdown(page, "#compose_select_stream_widget", params.stream); + await select_item_via_dropdown(page, "#compose_select_recipient_widget", params.stream); delete params.stream; } diff --git a/web/e2e-tests/mention.test.ts b/web/e2e-tests/mention.test.ts index 53d83fc80f..d4834c2e09 100644 --- a/web/e2e-tests/mention.test.ts +++ b/web/e2e-tests/mention.test.ts @@ -11,7 +11,7 @@ async function test_mention(page: Page): Promise { await page.keyboard.press("KeyC"); await page.waitForSelector("#compose", {visible: true}); - await common.select_item_via_dropdown(page, "#compose_select_stream_widget", "Verona"); + await common.select_item_via_dropdown(page, "#compose_select_recipient_widget", "Verona"); await common.fill_form(page, 'form[action^="/json/messages"]', { stream_message_recipient_topic: "Test mention all", }); diff --git a/web/src/click_handlers.js b/web/src/click_handlers.js index 933f09d3ae..e96be3a501 100644 --- a/web/src/click_handlers.js +++ b/web/src/click_handlers.js @@ -695,7 +695,7 @@ export function initialize() { } // The dropdown menu needs to process clicks to open and close. - if ($target.parents("#compose_stream_selection_dropdown").length > 0) { + if ($target.parents("#compose_recipient_selection_dropdown").length > 0) { return; } diff --git a/web/src/compose_actions.js b/web/src/compose_actions.js index 3849d79f49..16a14219d8 100644 --- a/web/src/compose_actions.js +++ b/web/src/compose_actions.js @@ -40,7 +40,7 @@ function hide_box() { // This is the main hook for saving drafts when closing the compose box. drafts.update_draft(); blur_compose_inputs(); - $("#compose-stream-recipient").hide(); + $("#stream_message_recipient_topic").hide(); $("#compose-private-recipient").hide(); $(".new_message_textarea").css("min-height", ""); compose_fade.clear_compose(); @@ -63,7 +63,7 @@ function get_focus_area(msg_type, opts) { } if (msg_type === "stream") { - return "#compose_select_stream_widget"; + return "#compose_select_recipient_widget"; } return "#private_message_recipient"; } @@ -82,12 +82,12 @@ export function set_focus(msg_type, opts) { function show_compose_box(msg_type, opts) { if (msg_type === "stream") { $("#compose-private-recipient").hide(); - $("#compose-stream-recipient").show(); + $("#stream_message_recipient_topic").show(); $("#stream_toggle").addClass("active"); $("#private_message_toggle").removeClass("active"); } else { $("#compose-private-recipient").show(); - $("#compose-stream-recipient").hide(); + $("#stream_message_recipient_topic").hide(); $("#stream_toggle").removeClass("active"); $("#private_message_toggle").addClass("active"); } @@ -149,7 +149,7 @@ export function complete_starting_tasks(msg_type, opts) { maybe_scroll_up_selected_message(); compose_fade.start_compose(msg_type); - stream_bar.decorate(opts.stream, $("#compose-stream-recipient .message_header_stream")); + stream_bar.decorate(opts.stream, $("#stream_message_recipient_topic .message_header_stream")); $(document).trigger(new $.Event("compose_started.zulip", opts)); update_placeholder_text(); compose_recipient.update_narrow_to_recipient_visibility(); @@ -269,8 +269,8 @@ export function start(msg_type, opts) { clear_box(); } - compose_recipient.compose_stream_widget.render(opts.stream); - const $stream_header_colorblock = $("#compose_stream_selection_dropdown").find( + compose_recipient.compose_recipient_widget.render(opts.stream); + const $stream_header_colorblock = $("#compose_recipient_selection_dropdown").find( ".stream_header_colorblock", ); stream_bar.decorate(opts.stream, $stream_header_colorblock); diff --git a/web/src/compose_banner.ts b/web/src/compose_banner.ts index 8474b68537..4bea736f47 100644 --- a/web/src/compose_banner.ts +++ b/web/src/compose_banner.ts @@ -126,8 +126,8 @@ export function show_stream_does_not_exist_error(stream_name: string): void { // can't be imported due to typescript and import circles. // TODO: Once we use stream IDs, not names, as the fundamental // compose_state storage for streams, this error will be impossible. - if ($("#id_compose_select_stream").hasClass("open")) { + if ($("#id_compose_select_recipient").hasClass("open")) { return; } - $("#id_compose_select_stream button").trigger("click"); + $("#id_compose_select_recipient button").trigger("click"); } diff --git a/web/src/compose_recipient.js b/web/src/compose_recipient.js index 7d113762f8..04dfb58638 100644 --- a/web/src/compose_recipient.js +++ b/web/src/compose_recipient.js @@ -14,7 +14,7 @@ import * as stream_bar from "./stream_bar"; import * as stream_data from "./stream_data"; import * as util from "./util"; -export let compose_stream_widget; +export let compose_recipient_widget; function composing_to_current_topic_narrow() { return ( @@ -92,13 +92,13 @@ export function update_on_recipient_change() { } export function open_compose_stream_dropup() { - if ($("#id_compose_select_stream").hasClass("open")) { + if ($("#id_compose_select_recipient").hasClass("open")) { return; } // We trigger a click rather than directly toggling the element; // this is important to ensure the filter text gets cleared when // reopening the widget after previous use. - $("#id_compose_select_stream > .dropdown-toggle").trigger("click"); + $("#id_compose_select_recipient > .dropdown-toggle").trigger("click"); } export function check_stream_posting_policy_for_compose_box(stream_name) { @@ -121,8 +121,8 @@ export function check_stream_posting_policy_for_compose_box(stream_name) { } } -export function on_compose_select_stream_update(new_value) { - const $stream_header_colorblock = $("#compose_stream_selection_dropdown").find( +export function on_compose_select_recipient_update(new_value) { + const $stream_header_colorblock = $("#compose_recipient_selection_dropdown").find( ".stream_header_colorblock", ); stream_bar.decorate(new_value, $stream_header_colorblock); @@ -149,7 +149,7 @@ export function update_stream_dropdown_options() { } return 0; }); - compose_stream_widget.replace_data(streams_list); + compose_recipient_widget.replace_data(streams_list); } export function possibly_update_dropdown_selection(old_stream_name, new_stream_name) { @@ -177,16 +177,16 @@ export function initialize() { return 0; }); const opts = { - widget_name: "compose_select_stream", + widget_name: "compose_select_recipient", data: streams_list, default_text: $t({defaultMessage: "Select a stream"}), value: null, - on_update: on_compose_select_stream_update, + on_update: on_compose_select_recipient_update, }; - compose_stream_widget = new DropdownListWidget(opts); - compose_stream_widget.setup(); + compose_recipient_widget = new DropdownListWidget(opts); + compose_recipient_widget.setup(); - $("#compose_select_stream_widget").on("select", (e) => { + $("#compose_select_recipient_widget").on("select", (e) => { // We often focus on input fields to bring the user to fill it out. // In this situation, a focus on the dropdown div opens the dropdown // menu so that the user can select an option. @@ -200,7 +200,7 @@ export function initialize() { "keyup", update_on_recipient_change, ); - // changes for the stream dropdown are handled in on_compose_select_stream_update + // changes for the stream dropdown are handled in on_compose_select_recipient_update $("#stream_message_recipient_topic,#private_message_recipient").on("change", () => { update_on_recipient_change(); compose_state.set_recipient_edited_manually(true); diff --git a/web/src/compose_state.js b/web/src/compose_state.js index 43276c7d2b..be7acf7317 100644 --- a/web/src/compose_state.js +++ b/web/src/compose_state.js @@ -68,13 +68,13 @@ function get_or_set(fieldname, keep_leading_whitespace, no_trim) { } export function stream_name() { - return compose_recipient.compose_stream_widget.value(); + return compose_recipient.compose_recipient_widget.value(); } export function set_stream_name(newval) { - if (newval !== undefined && newval !== "" && compose_recipient.compose_stream_widget) { - compose_recipient.compose_stream_widget.render(newval); - compose_recipient.on_compose_select_stream_update(newval); + if (newval !== undefined && newval !== "" && compose_recipient.compose_recipient_widget) { + compose_recipient.compose_recipient_widget.render(newval); + compose_recipient.on_compose_select_recipient_update(newval); } } @@ -128,7 +128,7 @@ export function focus_in_empty_compose(consider_start_of_whitespace_message_empt return private_message_recipient().length === 0; case "stream_message_recipient_topic": return topic() === ""; - case "compose_select_stream_name": + case "compose_select_recipient_name": return stream_name() === ""; } diff --git a/web/src/compose_ui.js b/web/src/compose_ui.js index 16f203440a..44cc98b203 100644 --- a/web/src/compose_ui.js +++ b/web/src/compose_ui.js @@ -252,7 +252,7 @@ export function make_compose_box_full_size() { set_compose_box_top(true); // The compose select dropup should now open down because it's // at the top of the screen. - $("#id_compose_select_stream").removeClass("dropup").addClass("dropdown"); + $("#id_compose_select_recipient").removeClass("dropup").addClass("dropdown"); $(".collapse_composebox_button").show(); $(".expand_composebox_button").hide(); @@ -269,7 +269,7 @@ export function make_compose_box_original_size() { set_compose_box_top(false); // The compose select dropup should now open up because it's // near the bottom of the screen. - $("#id_compose_select_stream").removeClass("dropdown").addClass("dropup"); + $("#id_compose_select_recipient").removeClass("dropdown").addClass("dropup"); // Again initialise the compose textarea as it was destroyed // when compose box was made full screen diff --git a/web/src/compose_validate.js b/web/src/compose_validate.js index 5a8df22b2a..1f42c81777 100644 --- a/web/src/compose_validate.js +++ b/web/src/compose_validate.js @@ -394,7 +394,7 @@ export function validation_error(error_type, stream_name) { compose_banner.show_error_message( $t({defaultMessage: "Error checking subscription."}), compose_banner.CLASSNAMES.subscription_error, - $("#compose_select_stream_widget"), + $("#compose_select_recipient_widget"), ); return false; case "not-subscribed": { @@ -441,7 +441,7 @@ function validate_stream_message() { compose_banner.show_error_message( $t({defaultMessage: "Please specify a stream."}), compose_banner.CLASSNAMES.missing_stream, - $("#compose_select_stream_widget"), + $("#compose_select_recipient_widget"), ); return false; } diff --git a/web/styles/compose.css b/web/styles/compose.css index 0e0802c695..7fb3275450 100644 --- a/web/styles/compose.css +++ b/web/styles/compose.css @@ -102,7 +102,7 @@ } .right_part, - #compose-stream-recipient { + #stream_message_recipient_topic { padding: 0; display: flex; align-items: center; @@ -508,8 +508,8 @@ input.recipient_box { border-radius: 3px; } -#compose_select_stream_widget, -#compose_select_stream_widget .button { +#compose_select_recipient_widget, +#compose_select_recipient_widget .button { margin: 0; min-width: 0; flex: 1; @@ -616,11 +616,7 @@ input.recipient_box { } } -#compose-stream-recipient { - min-width: 0; -} - -#compose-stream-recipient, +#stream_message_recipient_topic, #compose-private-recipient { display: flex; } @@ -770,7 +766,7 @@ a.compose_control_button.hide { margin: auto; } -#compose_stream_selection_dropdown { +#compose_recipient_selection_dropdown { display: flex; justify-content: flex-start; flex: 0 1 200px; @@ -798,7 +794,7 @@ a.compose_control_button.hide { left: -10px; } - #compose_select_stream_name { + #compose_select_recipient_name { flex-grow: 1; overflow: hidden; text-overflow: ellipsis; @@ -826,7 +822,7 @@ a.compose_control_button.hide { min-width: 0; } -#id_compose_select_stream { +#id_compose_select_recipient { display: flex; } diff --git a/web/templates/compose.hbs b/web/templates/compose.hbs index b0e9ffeda6..412183e4ca 100644 --- a/web/templates/compose.hbs +++ b/web/templates/compose.hbs @@ -96,10 +96,10 @@
-
+
{{> settings/dropdown_list_widget - widget_name="compose_select_stream" + widget_name="compose_select_recipient" list_placeholder=(t 'Filter streams')}}
diff --git a/web/tests/compose.test.js b/web/tests/compose.test.js index d3c49cde23..698fa670a9 100644 --- a/web/tests/compose.test.js +++ b/web/tests/compose.test.js @@ -311,7 +311,7 @@ test_ui("enter_with_preview_open", ({override, override_rewire}) => { compose_recipient.open_compose_stream_dropup = noop; override_rewire(compose_recipient, "update_on_recipient_change", noop); let stream_value = ""; - compose_recipient.compose_stream_widget = { + compose_recipient.compose_recipient_widget = { value() { return stream_value; }, diff --git a/web/tests/compose_actions.test.js b/web/tests/compose_actions.test.js index 014d0fad75..ba7970a10d 100644 --- a/web/tests/compose_actions.test.js +++ b/web/tests/compose_actions.test.js @@ -69,7 +69,7 @@ const stream_bar = zrequire("stream_bar"); const compose_recipient = zrequire("compose_recipient"); let stream_value = ""; -compose_recipient.compose_stream_widget = { +compose_recipient.compose_recipient_widget = { value() { return stream_value; }, @@ -147,7 +147,7 @@ test("start", ({override, override_rewire}) => { let opts = {}; start("stream", opts); - assert_visible("#compose-stream-recipient"); + assert_visible("#stream_message_recipient_topic"); assert_hidden("#compose-private-recipient"); assert.equal(compose_state.stream_name(), "stream1"); @@ -208,7 +208,7 @@ test("start", ({override, override_rewire}) => { start("private", opts); - assert_hidden("#compose-stream-recipient"); + assert_hidden("#stream_message_recipient_topic"); assert_visible("#compose-private-recipient"); assert.equal(compose_state.private_message_recipient(), "foo@example.com"); @@ -444,7 +444,7 @@ test("get_focus_area", () => { }), "#compose-textarea", ); - assert.equal(get_focus_area("stream", {}), "#compose_select_stream_widget"); + assert.equal(get_focus_area("stream", {}), "#compose_select_recipient_widget"); assert.equal(get_focus_area("stream", {stream: "fun"}), "#stream_message_recipient_topic"); assert.equal(get_focus_area("stream", {stream: "fun", topic: "more"}), "#compose-textarea"); assert.equal( diff --git a/web/tests/compose_fade.test.js b/web/tests/compose_fade.test.js index 899be88c11..21cbdd790d 100644 --- a/web/tests/compose_fade.test.js +++ b/web/tests/compose_fade.test.js @@ -26,7 +26,7 @@ const compose_fade = zrequire("compose_fade"); const compose_recipient = zrequire("compose_recipient"); const compose_fade_helper = zrequire("compose_fade_helper"); -compose_recipient.compose_stream_widget = { +compose_recipient.compose_recipient_widget = { value() { return "social"; }, diff --git a/web/tests/compose_state.test.js b/web/tests/compose_state.test.js index b12742430b..46786c943d 100644 --- a/web/tests/compose_state.test.js +++ b/web/tests/compose_state.test.js @@ -17,7 +17,7 @@ const stream_bar = zrequire("stream_bar"); const noop = () => {}; let stream_value = ""; -compose_recipient.compose_stream_widget = { +compose_recipient.compose_recipient_widget = { value() { return stream_value; }, diff --git a/web/tests/compose_validate.test.js b/web/tests/compose_validate.test.js index 741aba8da1..c67b9604f4 100644 --- a/web/tests/compose_validate.test.js +++ b/web/tests/compose_validate.test.js @@ -26,7 +26,7 @@ const stream_data = zrequire("stream_data"); const compose_recipient = zrequire("compose_recipient"); let stream_value = ""; -compose_recipient.compose_stream_widget = { +compose_recipient.compose_recipient_widget = { value() { return stream_value; }, @@ -149,7 +149,7 @@ test_ui("validate_stream_message_address_info", ({mock_template}) => { test_ui("validate", ({mock_template}) => { compose_actions.update_placeholder_text = () => {}; - compose_recipient.on_compose_select_stream_update = () => {}; + compose_recipient.on_compose_select_recipient_update = () => {}; function initialize_pm_pill() { $.clear_all_elements(); diff --git a/web/tests/composebox_typeahead.test.js b/web/tests/composebox_typeahead.test.js index 8d2e79c405..68c311ee63 100644 --- a/web/tests/composebox_typeahead.test.js +++ b/web/tests/composebox_typeahead.test.js @@ -58,7 +58,7 @@ const ct = composebox_typeahead; ct.__Rewire__("max_num_items", 15); let stream_value = ""; -compose_recipient.compose_stream_widget = { +compose_recipient.compose_recipient_widget = { value() { return stream_value; }, diff --git a/web/tests/drafts.test.js b/web/tests/drafts.test.js index 0e74c276ae..a9831fe956 100644 --- a/web/tests/drafts.test.js +++ b/web/tests/drafts.test.js @@ -21,7 +21,7 @@ const stream_bar = zrequire("stream_bar"); const stream_data = zrequire("stream_data"); let stream_value = ""; -compose_recipient.compose_stream_widget = { +compose_recipient.compose_recipient_widget = { value() { return stream_value; }, diff --git a/web/tests/lib/compose.js b/web/tests/lib/compose.js index f7ca0e7781..953cd21fcc 100644 --- a/web/tests/lib/compose.js +++ b/web/tests/lib/compose.js @@ -3,7 +3,7 @@ const $ = require("./zjquery"); exports.mock_stream_header_colorblock = () => { - const $stream_selection_dropdown = $("#compose_stream_selection_dropdown"); + const $stream_selection_dropdown = $("#compose_recipient_selection_dropdown"); const $stream_header_colorblock = $(".stream_header_colorblock"); $(".stream_header_colorblock").css = () => {}; $stream_selection_dropdown.set_find_results( diff --git a/web/tests/narrow.test.js b/web/tests/narrow.test.js index 34df053a29..32a6bac7d3 100644 --- a/web/tests/narrow.test.js +++ b/web/tests/narrow.test.js @@ -28,7 +28,7 @@ const recent_topics_util = mock_esm("../src/recent_topics_util", { }); let stream_value = ""; -compose_recipient.compose_stream_widget = { +compose_recipient.compose_recipient_widget = { value() { return stream_value; }, @@ -669,7 +669,7 @@ run_test("show_invalid_narrow_message", ({mock_template}) => { }); run_test("narrow_to_compose_target errors", ({disallow_rewire}) => { - compose_recipient.on_compose_select_stream_update = () => {}; + compose_recipient.on_compose_select_recipient_update = () => {}; disallow_rewire(narrow, "activate"); // No-op when not composing.