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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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