chore: Move searchable to ListState and SelectState. (#1507)
This commit is contained in:
parent
087cb70d00
commit
d037f074dc
9 changed files with 50 additions and 54 deletions
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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.))
|
||||||
|
|
|
||||||
|
|
@ -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),
|
||||||
|
|
|
||||||
|
|
@ -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),
|
||||||
|
|
|
||||||
|
|
@ -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());
|
||||||
|
|
|
||||||
|
|
@ -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());
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue