From 087cb70d00edc27abbd56dec53d7b3bf853ef0dc Mon Sep 17 00:00:00 2001 From: Jason Lee Date: Tue, 4 Nov 2025 20:59:38 +0800 Subject: [PATCH] select, list: Update `searchable` default to false. (#1504) Continue #1503 to update the `searchable` default to `false`. --- crates/story/src/drawer_story.rs | 1 + crates/story/src/list_story.rs | 1 + crates/story/src/select_story.rs | 2 ++ crates/ui/src/list/list.rs | 45 ++++++++++++++------------------ crates/ui/src/select.rs | 11 +++++--- docs/docs/components/list.md | 5 ++-- docs/docs/components/select.md | 3 +++ 7 files changed, 37 insertions(+), 31 deletions(-) diff --git a/crates/story/src/drawer_story.rs b/crates/story/src/drawer_story.rs index dba97668..4b17320e 100644 --- a/crates/story/src/drawer_story.rs +++ b/crates/story/src/drawer_story.rs @@ -304,6 +304,7 @@ impl DrawerStory { ) .child( List::new(&list) + .searchable(true) .border_1() .border_color(cx.theme().border) .rounded(cx.theme().radius) diff --git a/crates/story/src/list_story.rs b/crates/story/src/list_story.rs index ace79ba1..fa3ab046 100644 --- a/crates/story/src/list_story.rs +++ b/crates/story/src/list_story.rs @@ -536,6 +536,7 @@ impl Render for ListStory { ) .child( List::new(&self.company_list) + .searchable(true) .p(px(8.)) .flex_1() .w_full() diff --git a/crates/story/src/select_story.rs b/crates/story/src/select_story.rs index 4ef94054..182dc5c7 100644 --- a/crates/story/src/select_story.rs +++ b/crates/story/src/select_story.rs @@ -191,6 +191,7 @@ 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), @@ -199,6 +200,7 @@ 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.)) diff --git a/crates/ui/src/list/list.rs b/crates/ui/src/list/list.rs index 1238569b..9a876896 100644 --- a/crates/ui/src/list/list.rs +++ b/crates/ui/src/list/list.rs @@ -61,7 +61,7 @@ impl Default for ListOptions { scrollbar_visible: true, max_height: None, selectable: true, - searchable: true, + searchable: false, search_placeholder: None, paddings: EdgesRefinement::default(), } @@ -70,9 +70,9 @@ impl Default for ListOptions { /// The state for List. pub struct ListState { - focus_handle: FocusHandle, + pub(crate) focus_handle: FocusHandle, + pub(crate) query_input: Entity, options: ListOptions, - query_input: Option>, delegate: D, last_query: Option, scroll_handle: VirtualListScrollHandle, @@ -104,7 +104,7 @@ where options: ListOptions::default(), delegate, rows_cache: RowsCache::default(), - query_input: Some(query_input), + query_input, last_query: None, selected_index: None, item_to_measure_index: IndexPath::default(), @@ -131,6 +131,11 @@ where self.focus_handle(cx).focus(window); } + /// Return true if either the list or the search input is focused. + pub(crate) fn is_focused(&self, window: &Window, cx: &App) -> bool { + self.focus_handle.is_focused(window) || self.query_input.focus_handle(cx).is_focused(window) + } + /// Set the selected index of the list, /// this will also scroll to the selected item. pub(crate) fn _set_selected_index( @@ -254,10 +259,8 @@ where } fn set_searching(&mut self, searching: bool, window: &mut Window, cx: &mut Context) { - if let Some(input) = &self.query_input { - input.update(cx, |input, cx| input.set_loading(searching, window, cx)) - } - cx.notify(); + self.query_input + .update(cx, |input, cx| input.set_loading(searching, window, cx)); } /// Dispatch delegate's `load_more` method when the @@ -523,12 +526,8 @@ where D: ListDelegate, { fn focus_handle(&self, cx: &App) -> FocusHandle { - if !self.options.searchable { - return self.focus_handle.clone(); - } - - if let Some(query_input) = &self.query_input { - query_input.focus_handle(cx) + if self.options.searchable { + self.query_input.focus_handle(cx) } else { self.focus_handle.clone() } @@ -552,16 +551,12 @@ where let loading = self.delegate().loading(cx); let query_input = if self.options.searchable { // sync placeholder - if let Some(query_input) = &self.query_input { - if let Some(placeholder) = &self.options.search_placeholder { - query_input.update(cx, |input, cx| { - input.set_placeholder(placeholder.clone(), window, cx); - }); - } - Some(query_input.clone()) - } else { - None + if let Some(placeholder) = &self.options.search_placeholder { + self.query_input.update(cx, |input, cx| { + input.set_placeholder(placeholder.clone(), window, cx); + }); } + Some(self.query_input.clone()) } else { None }; @@ -591,7 +586,7 @@ where .size_full() .relative() .overflow_hidden() - .when_some(query_input.clone(), |this, input| { + .when_some(query_input, |this, input| { this.child( div() .map(|this| match self.options.size { @@ -670,7 +665,7 @@ where self } - /// Sets whether the list is searchable, default is `true`. + /// 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 { diff --git a/crates/ui/src/select.rs b/crates/ui/src/select.rs index c6ce542b..82e218bb 100644 --- a/crates/ui/src/select.rs +++ b/crates/ui/src/select.rs @@ -324,7 +324,7 @@ impl Default for SelectOptions { menu_width: Length::Auto, disabled: false, appearance: true, - searchable: true, + searchable: false, search_placeholder: None, } } @@ -559,9 +559,12 @@ where }; let list = cx.new(|cx| ListState::new(delegate, window, cx).reset_on_cancel(false)); + let list_focus_handle = list.read(cx).focus_handle.clone(); + let list_search_focus_handle = list.read(cx).query_input.focus_handle(cx); let _subscriptions = vec![ - cx.on_blur(&list.focus_handle(cx), window, Self::on_blur), + cx.on_blur(&list_focus_handle, window, Self::on_blur), + cx.on_blur(&list_search_focus_handle, window, Self::on_blur), cx.on_blur(&focus_handle, window, Self::on_blur), ]; @@ -645,7 +648,7 @@ where fn on_blur(&mut self, window: &mut Window, cx: &mut Context) { // When the select and dropdown menu are both not focused, close the dropdown menu. - if self.list.focus_handle(cx).is_focused(window) || self.focus_handle.is_focused(window) { + if self.list.read(cx).is_focused(window, cx) || self.focus_handle.is_focused(window) { return; } @@ -954,7 +957,7 @@ where self } - /// Sets whether the dropdown menu is searchable, default is `true`. + /// 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 { diff --git a/docs/docs/components/list.md b/docs/docs/components/list.md index 3fc3e78d..3d5a1c94 100644 --- a/docs/docs/components/list.md +++ b/docs/docs/components/list.md @@ -171,6 +171,8 @@ 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. + ```rust impl ListDelegate for MyListDelegate { fn perform_search( @@ -190,9 +192,8 @@ impl ListDelegate for MyListDelegate { } } -// Create list without search input let state = cx.new(|cx| ListState::new(delegate, window, cx)); -List::new(&state).searchable(false) +List::new(&state).searchable(true) ``` ### List with Loading State diff --git a/docs/docs/components/select.md b/docs/docs/components/select.md index fd2534d1..a47f1da8 100644 --- a/docs/docs/components/select.md +++ b/docs/docs/components/select.md @@ -65,6 +65,8 @@ Select::new(&state) ### Searchable +Use `searchable(true)` to enable search functionality within the dropdown. + ```rust let fruits = SearchableVec::new(vec![ "Apple", "Orange", "Banana", "Grape", "Pineapple", @@ -75,6 +77,7 @@ let state = cx.new(|cx| { }); Select::new(&state) + .searchable(true) .icon(IconName::Search) // Shows search icon ```