search pills: Allow typing new query just after opening search.

This simulates the user experience of a text input with the existing
content selected/highlighted.
This commit is contained in:
evykassirer 2024-07-02 16:58:02 -07:00 committed by Tim Abbott
parent d655ab8570
commit 991c8451cd
2 changed files with 48 additions and 8 deletions

View File

@ -75,6 +75,22 @@ function narrow_or_search_for_term({on_narrow_search}: {on_narrow_search: OnNarr
return get_search_bar_text(); return get_search_bar_text();
} }
// When a pill is added or removed, or when text input is changed,
// we set `search_input_has_changed` to `false`. We also remove the
// `freshly-opened` styling on the search pills (which is added in
// `initiate_search` but not every time we open the search bar) after
// the first input change.
function on_search_contents_changed(): void {
assert(search_pill_widget !== null);
if (!search_input_has_changed && $(".search-input-and-pills").hasClass("freshly-opened")) {
const search_text = get_search_bar_text();
search_pill_widget.clear();
set_search_bar_text(search_text);
$(".search-input-and-pills").removeClass("freshly-opened");
}
search_input_has_changed = true;
}
export function initialize({on_narrow_search}: {on_narrow_search: OnNarrowSearch}): void { export function initialize({on_narrow_search}: {on_narrow_search: OnNarrowSearch}): void {
const $search_query_box = $<HTMLInputElement>("#search_query"); const $search_query_box = $<HTMLInputElement>("#search_query");
const $searchbox_form = $("#searchbox_form"); const $searchbox_form = $("#searchbox_form");
@ -90,7 +106,7 @@ export function initialize({on_narrow_search}: {on_narrow_search: OnNarrowSearch
search_pill_widget = search_pill.create_pills($pill_container); search_pill_widget = search_pill.create_pills($pill_container);
search_pill_widget.onPillRemove(() => { search_pill_widget.onPillRemove(() => {
search_input_has_changed = true; on_search_contents_changed();
}); });
$search_query_box.on("change", () => { $search_query_box.on("change", () => {
@ -111,7 +127,7 @@ export function initialize({on_narrow_search}: {on_narrow_search: OnNarrowSearch
search_typeahead = new Typeahead(bootstrap_typeahead_input, { search_typeahead = new Typeahead(bootstrap_typeahead_input, {
source(query: string): string[] { source(query: string): string[] {
if (query !== "") { if (query !== "") {
search_input_has_changed = true; on_search_contents_changed();
} }
assert(search_pill_widget !== null); assert(search_pill_widget !== null);
const query_from_pills = const query_from_pills =
@ -150,7 +166,7 @@ export function initialize({on_narrow_search}: {on_narrow_search: OnNarrowSearch
}, },
updater(search_string: string): string { updater(search_string: string): string {
if (search_string) { if (search_string) {
search_input_has_changed = true; on_search_contents_changed();
// Reset the search box and add the pills based on the selected // Reset the search box and add the pills based on the selected
// search suggestion. // search suggestion.
assert(search_pill_widget !== null); assert(search_pill_widget !== null);
@ -224,6 +240,10 @@ export function initialize({on_narrow_search}: {on_narrow_search: OnNarrowSearch
} else { } else {
typeahead_was_open_on_enter = false; typeahead_was_open_on_enter = false;
} }
if (e.key === "ArrowRight") {
$(".search-input-and-pills").removeClass("freshly-opened");
}
}) })
.on("keyup", (e: JQuery.KeyUpEvent): void => { .on("keyup", (e: JQuery.KeyUpEvent): void => {
if (is_using_input_method) { if (is_using_input_method) {
@ -254,14 +274,18 @@ export function initialize({on_narrow_search}: {on_narrow_search: OnNarrowSearch
} }
}); });
// We don't want to make this a focus handler because selecting the $("#searchbox-input-container").on("click", (): void => {
// We don't want to put this in a focus handler because selecting the
// typehead seems to trigger this (and we don't want to open search // typehead seems to trigger this (and we don't want to open search
// when an option is selected and we're closing search). // when an option is selected and we're closing search).
// Instead we explicitly initiate search on click and on specific keyboard // Instead we explicitly initiate search on click and on specific keyboard
// shortcuts. // shortcuts.
$("#searchbox-input-container").on("click", (): void => {
if ($("#searchbox .navbar-search.expanded").length === 0) { if ($("#searchbox .navbar-search.expanded").length === 0) {
initiate_search(); initiate_search();
} else if (!search_input_has_changed) {
// Clicking is a way to remove the freshly opened marker,
// similar to clearing text selection.
$(".search-input-and-pills").removeClass("freshly-opened");
} }
}); });
@ -282,6 +306,7 @@ export function initialize({on_narrow_search}: {on_narrow_search: OnNarrowSearch
if (get_search_bar_text() === "") { if (get_search_bar_text() === "") {
$("#search_query").empty(); $("#search_query").empty();
} }
on_search_contents_changed();
}); });
// register searchbar click handler // register searchbar click handler
@ -313,6 +338,14 @@ export function initialize({on_narrow_search}: {on_narrow_search: OnNarrowSearch
export function initiate_search(): void { export function initiate_search(): void {
open_search_bar_and_close_narrow_description(); open_search_bar_and_close_narrow_description();
// Sometimes a user opens the search bar and wants to start typing
// a fresh query, not add new pills onto the existing query. We handle
// this situation by adding a `freshly-opened` class that displays the
// pills in their "focused" state, indicating that if the user starts
// typing then the pills will go away. If the user presses right arrow
// or clicks on the search input instead, the selection goes away and
// the user can add new search input to the existing pills.
$(".search-input-and-pills").addClass("freshly-opened");
$("#search_query").trigger("focus"); $("#search_query").trigger("focus");
// Open the typeahead after opening the search bar, so that we don't // Open the typeahead after opening the search bar, so that we don't
@ -373,6 +406,7 @@ export function close_search_bar_and_open_narrow_description(): void {
} }
$(".navbar-search").removeClass("expanded"); $(".navbar-search").removeClass("expanded");
$(".search-input-and-pills").removeClass("freshly-opened");
$("#message_view_header").removeClass("hidden"); $("#message_view_header").removeClass("hidden");
if ($("#search_query").is(":focus")) { if ($("#search_query").is(":focus")) {

View File

@ -74,6 +74,12 @@
align-self: center; align-self: center;
} }
.search-input-and-pills.freshly-opened {
> .pill {
border-color: var(--focus-outline-input-pill);
}
}
.navbar-search:not(.expanded) { .navbar-search:not(.expanded) {
right: 0; right: 0;