chore: Move searchable to ListState and SelectState. (#1507)

This commit is contained in:
Jason Lee 2025-11-04 21:26:11 +08:00 committed by GitHub
parent 087cb70d00
commit d037f074dc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 50 additions and 54 deletions

View file

@ -243,7 +243,7 @@ impl DrawerStory {
matches: items.clone(),
};
let list = cx.new(|cx| {
let mut list = ListState::new(delegate, window, cx);
let mut list = ListState::new(delegate, window, cx).searchable(true);
list.focus(window, cx);
list
});
@ -304,7 +304,6 @@ impl DrawerStory {
)
.child(
List::new(&list)
.searchable(true)
.border_1()
.border_color(cx.theme().border)
.rounded(cx.theme().radius)

View file

@ -367,7 +367,7 @@ impl ListStory {
};
delegate.extend_more(100);
let company_list = cx.new(|cx| ListState::new(delegate, window, cx));
let company_list = cx.new(|cx| ListState::new(delegate, window, cx).searchable(true));
let _subscriptions =
vec![
@ -536,7 +536,6 @@ impl Render for ListStory {
)
.child(
List::new(&self.company_list)
.searchable(true)
.p(px(8.))
.flex_1()
.w_full()

View file

@ -85,6 +85,7 @@ impl SelectStory {
window,
cx,
)
.searchable(true)
});
let appearance_select = cx.new(|cx| {
SelectState::new(
@ -111,7 +112,7 @@ impl SelectStory {
"Watermelon & This is a long long long long long long long long long title",
"Avocado",
]);
let fruit_select = cx.new(|cx| SelectState::new(fruits, None, window, cx));
let fruit_select = cx.new(|cx| SelectState::new(fruits, None, window, cx).searchable(true));
cx.new(|cx| {
cx.subscribe_in(&country_select, window, Self::on_select_event)
@ -191,7 +192,6 @@ impl Render for SelectStory {
.child(
section("Select").max_w_128().child(
Select::new(&self.country_select)
.searchable(true)
.search_placeholder("Search country by name or code")
.cleanable()
.disabled(self.disabled),
@ -200,7 +200,6 @@ impl Render for SelectStory {
.child(
section("Searchable").max_w_128().child(
Select::new(&self.fruit_select)
.searchable(true)
.disabled(self.disabled)
.icon(IconName::Search)
.w(px(320.))

View file

@ -327,11 +327,7 @@ impl Render for CodeActionMenu {
.top(pos.y)
.max_w(max_width)
.min_w(px(120.))
.child(
List::new(&self.list)
.searchable(false)
.max_h(MAX_MENU_HEIGHT),
)
.child(List::new(&self.list).max_h(MAX_MENU_HEIGHT))
.child(
canvas(
move |bounds, _, cx| view.update(cx, |r, _| r.bounds = bounds),

View file

@ -428,11 +428,7 @@ impl Render for CompletionMenu {
editor_popover("completion-menu", cx)
.max_w(max_width)
.min_w(px(120.))
.child(
List::new(&self.list)
.searchable(false)
.max_h(MAX_MENU_HEIGHT),
)
.child(List::new(&self.list).max_h(MAX_MENU_HEIGHT))
.child(
canvas(
move |bounds, _, cx| view.update(cx, |r, _| r.bounds = bounds),

View file

@ -47,8 +47,6 @@ pub enum ListEvent {
struct ListOptions {
size: Size,
scrollbar_visible: bool,
selectable: bool,
searchable: bool,
search_placeholder: Option<SharedString>,
max_height: Option<Length>,
paddings: EdgesRefinement<DefiniteLength>,
@ -60,8 +58,6 @@ impl Default for ListOptions {
size: Size::default(),
scrollbar_visible: true,
max_height: None,
selectable: true,
searchable: false,
search_placeholder: None,
paddings: EdgesRefinement::default(),
}
@ -83,6 +79,8 @@ pub struct ListState<D: ListDelegate> {
deferred_scroll_to_index: Option<(IndexPath, ScrollStrategy)>,
mouse_right_clicked_index: Option<IndexPath>,
reset_on_cancel: bool,
searchable: bool,
selectable: bool,
_search_task: Task<()>,
_load_more_task: Task<()>,
_query_input_subscription: Subscription,
@ -107,6 +105,8 @@ where
query_input,
last_query: None,
selected_index: None,
selectable: true,
searchable: false,
item_to_measure_index: IndexPath::default(),
deferred_scroll_to_index: None,
mouse_right_clicked_index: None,
@ -119,6 +119,25 @@ where
}
}
/// Sets whether the list is searchable, default is `false`.
///
/// When `true`, there will be a search input at the top of the list.
pub fn searchable(mut self, searchable: bool) -> Self {
self.searchable = searchable;
self
}
pub fn set_searchable(&mut self, searchable: bool, cx: &mut Context<Self>) {
self.searchable = searchable;
cx.notify();
}
/// Sets whether the list is selectable, default is true.
pub fn selectable(mut self, selectable: bool) -> Self {
self.selectable = selectable;
self
}
pub fn delegate(&self) -> &D {
&self.delegate
}
@ -402,7 +421,7 @@ where
window: &mut Window,
cx: &mut Context<Self>,
) -> impl IntoElement {
let selectable = self.options.selectable;
let selectable = self.selectable;
let selected = self.selected_index.map(|s| s.eq_row(ix)).unwrap_or(false);
let mouse_right_clicked = self
.mouse_right_clicked_index
@ -526,7 +545,7 @@ where
D: ListDelegate,
{
fn focus_handle(&self, cx: &App) -> FocusHandle {
if self.options.searchable {
if self.searchable {
self.query_input.focus_handle(cx)
} else {
self.focus_handle.clone()
@ -549,7 +568,7 @@ where
}
let loading = self.delegate().loading(cx);
let query_input = if self.options.searchable {
let query_input = if self.searchable {
// sync placeholder
if let Some(placeholder) = &self.options.search_placeholder {
self.query_input.update(cx, |input, cx| {
@ -659,20 +678,6 @@ where
self
}
/// Sets whether the list is selectable, default is true.
pub fn selectable(mut self, selectable: bool) -> Self {
self.options.selectable = selectable;
self
}
/// Sets whether the list is searchable, default is `false`.
///
/// When `true`, there will be a search input at the top of the list.
pub fn searchable(mut self, searchable: bool) -> Self {
self.options.searchable = searchable;
self
}
/// Sets the placeholder text for the search input.
pub fn search_placeholder(mut self, placeholder: impl Into<SharedString>) -> Self {
self.options.search_placeholder = Some(placeholder.into());

View file

@ -303,7 +303,6 @@ struct SelectOptions {
cleanable: bool,
placeholder: Option<SharedString>,
title_prefix: Option<SharedString>,
searchable: bool,
search_placeholder: Option<SharedString>,
empty: Option<AnyElement>,
menu_width: Length,
@ -324,7 +323,6 @@ impl Default for SelectOptions {
menu_width: Length::Auto,
disabled: false,
appearance: true,
searchable: false,
search_placeholder: None,
}
}
@ -334,6 +332,7 @@ impl Default for SelectOptions {
pub struct SelectState<D: SelectDelegate + 'static> {
focus_handle: FocusHandle,
options: SelectOptions,
searchable: bool,
list: Entity<ListState<SelectListDelegate<D>>>,
empty: Option<Box<dyn Fn(&Window, &App) -> AnyElement>>,
/// Store the bounds of the input
@ -571,6 +570,7 @@ where
let mut this = Self {
focus_handle,
options: SelectOptions::default(),
searchable: false,
list,
selected_value: None,
open: false,
@ -583,6 +583,14 @@ where
this
}
/// Sets whether the dropdown menu is searchable, default is `false`.
///
/// When `true`, there will be a search input at the top of the dropdown menu.
pub fn searchable(mut self, searchable: bool) -> Self {
self.searchable = searchable;
self
}
/// Set the selected index for the select.
pub fn set_selected_index(
&mut self,
@ -772,6 +780,7 @@ where
D: SelectDelegate + 'static,
{
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let searchable = self.searchable;
let is_focused = self.focus_handle.is_focused(window);
let show_clean = self.options.cleanable && self.selected_index(cx).is_some();
let bounds = self.bounds;
@ -779,6 +788,9 @@ where
let outline_visible = self.open || is_focused && !self.options.disabled;
let popup_radius = cx.theme().radius.min(px(8.));
self.list
.update(cx, |list, cx| list.set_searchable(searchable, cx));
div()
.size_full()
.relative()
@ -888,7 +900,6 @@ where
.shadow_md()
.child(
List::new(&self.list)
.searchable(self.options.searchable)
.when_some(
self.options.search_placeholder.clone(),
|this, placeholder| {
@ -957,14 +968,6 @@ where
self
}
/// Sets whether the dropdown menu is searchable, default is `false`.
///
/// When `true`, there will be a search input at the top of the dropdown menu.
pub fn searchable(mut self, searchable: bool) -> Self {
self.options.searchable = searchable;
self
}
/// Sets the placeholder text for the search input.
pub fn search_placeholder(mut self, placeholder: impl Into<SharedString>) -> Self {
self.options.search_placeholder = Some(placeholder.into());

View file

@ -171,7 +171,7 @@ fn render_item(
The list automatically includes a search input by default. Implement `perform_search` to handle queries:
And you should use `searchable(true)` when creating the list to show search input.
And you should use `searchable(true)` when creating the `ListState` to show search input.
```rust
impl ListDelegate for MyListDelegate {
@ -192,8 +192,8 @@ impl ListDelegate for MyListDelegate {
}
}
let state = cx.new(|cx| ListState::new(delegate, window, cx));
List::new(&state).searchable(true)
let state = cx.new(|cx| ListState::new(delegate, window, cx).searchable(true));
List::new(&state)
```
### List with Loading State

View file

@ -73,11 +73,10 @@ let fruits = SearchableVec::new(vec![
]);
let state = cx.new(|cx| {
SelectState::new(fruits, None, window, cx)
SelectState::new(fruits, None, window, cx).searchable(true)
});
Select::new(&state)
.searchable(true)
.icon(IconName::Search) // Shows search icon
```