Close Dropdown menu on blur. (#157)

Closes #156
This commit is contained in:
Jason Lee 2024-08-16 14:03:45 +08:00 committed by GitHub
parent b3f62bf096
commit e86f90d309
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 83 additions and 16 deletions

View file

@ -1,15 +1,25 @@
use gpui::{
px, IntoElement, ParentElement, Render, SharedString, Styled, View, ViewContext, VisualContext,
WindowContext,
actions, px, AppContext, InteractiveElement, IntoElement, KeyBinding, ParentElement, Render,
SharedString, Styled, View, ViewContext, VisualContext, WindowContext,
};
use ui::{
dropdown::{Dropdown, DropdownEvent, DropdownItem, SearchableVec},
h_flex,
theme::ActiveTheme,
v_flex, IconName, Sizable,
v_flex, FocusableCycle, IconName, Sizable,
};
actions!(dropdown_story, [Tab, TabPrev]);
const CONTEXT: &str = "DropdownStory";
pub fn init(cx: &mut AppContext) {
cx.bind_keys([
KeyBinding::new("shift-tab", TabPrev, Some(CONTEXT)),
KeyBinding::new("tab", Tab, Some(CONTEXT)),
])
}
struct Country {
name: SharedString,
code: SharedString,
@ -138,11 +148,39 @@ impl DropdownStory {
DropdownEvent::Confirm(value) => println!("Selected country: {:?}", value),
}
}
fn on_key_tab(&mut self, _: &Tab, cx: &mut ViewContext<Self>) {
self.cycle_focus(true, cx);
cx.notify();
}
fn on_key_shift_tab(&mut self, _: &TabPrev, cx: &mut ViewContext<Self>) {
self.cycle_focus(false, cx);
cx.notify();
}
}
impl FocusableCycle for DropdownStory {
fn cycle_focus_handles(&self, cx: &mut ViewContext<Self>) -> Vec<gpui::FocusHandle>
where
Self: Sized,
{
vec![
self.country_dropdown.focus_handle(cx),
self.fruit_dropdown.focus_handle(cx),
self.simple_dropdown1.focus_handle(cx),
self.simple_dropdown2.focus_handle(cx),
self.simple_dropdown3.focus_handle(cx),
]
}
}
impl Render for DropdownStory {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
v_flex()
.key_context(CONTEXT)
.on_action(cx.listener(Self::on_key_tab))
.on_action(cx.listener(Self::on_key_shift_tab))
.size_full()
.gap_4()
.child(

View file

@ -17,10 +17,12 @@ use crate::section;
actions!(input_story, [Tab, TabPrev]);
const CONTEXT: &str = "InputStory";
pub fn init(cx: &mut AppContext) {
cx.bind_keys([
KeyBinding::new("shift-tab", TabPrev, Some("InputStory")),
KeyBinding::new("tab", Tab, Some("InputStory")),
KeyBinding::new("shift-tab", TabPrev, Some(CONTEXT)),
KeyBinding::new("tab", Tab, Some(CONTEXT)),
])
}
@ -204,7 +206,7 @@ impl FocusableCycle for InputStory {
impl Render for InputStory {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
v_flex()
.key_context("InputStory")
.key_context(CONTEXT)
.on_action(cx.listener(Self::tab))
.on_action(cx.listener(Self::tab_prev))
.size_full()

View file

@ -50,6 +50,7 @@ use ui::{divider::Divider, h_flex, label::Label, v_flex};
pub fn init(cx: &mut AppContext) {
input_story::init(cx);
dropdown_story::init(cx);
}
pub fn section(title: impl IntoElement, cx: &WindowContext) -> Div {

View file

@ -81,3 +81,8 @@ DatePicker:
en: Select date
zh-CN: 选择日期
zh-HK: 選擇日期
Dropdown:
placeholder:
en: "Please select"
zh-CN: "请选择"
zh-HK: "請選擇"

View file

@ -5,6 +5,7 @@ use gpui::{
Render, SharedString, StatefulInteractiveElement, Styled, Task, View, ViewContext,
VisualContext, WeakView, WindowContext,
};
use rust_i18n::t;
use crate::{
h_flex,
@ -16,13 +17,14 @@ use crate::{
actions!(dropdown, [Up, Down, Enter, Escape]);
const CONTEXT: &str = "Dropdown";
pub fn init(cx: &mut AppContext) {
let context = Some("Dropdown");
cx.bind_keys([
KeyBinding::new("up", Up, context),
KeyBinding::new("down", Down, context),
KeyBinding::new("enter", Enter, context),
KeyBinding::new("escape", Escape, context),
KeyBinding::new("up", Up, Some(CONTEXT)),
KeyBinding::new("down", Down, Some(CONTEXT)),
KeyBinding::new("enter", Enter, Some(CONTEXT)),
KeyBinding::new("escape", Escape, Some(CONTEXT)),
])
}
@ -301,6 +303,7 @@ where
selected_index: Option<usize>,
cx: &mut ViewContext<Self>,
) -> Self {
let focus_handle = cx.focus_handle();
let delegate = DropdownListDelegate {
delegate,
dropdown: cx.view().downgrade(),
@ -316,10 +319,14 @@ where
}
list
});
cx.on_blur(&list.focus_handle(cx), Self::on_blur).detach();
cx.on_blur(&focus_handle, Self::on_blur).detach();
let mut this = Self {
id: id.into(),
focus_handle: cx.focus_handle(),
placeholder: "Select...".into(),
focus_handle,
placeholder: t!("Dropdown.placeholder").into(),
list,
size: Size::Medium,
icon: None,
@ -427,6 +434,16 @@ where
self.focus_handle.focus(cx);
}
fn on_blur(&mut self, cx: &mut ViewContext<Self>) {
// When the dropdown and dropdown menu are both not focused, close the dropdown menu.
if self.list.focus_handle(cx).is_focused(cx) || self.focus_handle.is_focused(cx) {
return;
}
self.open = false;
cx.notify();
}
fn up(&mut self, _: &Up, cx: &mut ViewContext<Self>) {
if !self.open {
return;
@ -514,8 +531,12 @@ impl<D> FocusableView for Dropdown<D>
where
D: DropdownDelegate + 'static,
{
fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
self.focus_handle.clone()
fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
if self.open {
self.list.focus_handle(cx)
} else {
self.focus_handle.clone()
}
}
}
@ -531,7 +552,7 @@ where
div()
.id(self.id.clone())
.key_context("Dropdown")
.key_context(CONTEXT)
.track_focus(&self.focus_handle)
.on_action(cx.listener(Self::up))
.on_action(cx.listener(Self::down))