feat: extract the Listbox component

This commit is contained in:
luoxiao 2024-07-22 14:51:19 +08:00
parent 900b949fa2
commit 0a8e98576a
11 changed files with 188 additions and 98 deletions

View file

@ -16,5 +16,5 @@ thaw_components = { version = "0.1.1", path = "./thaw_components" }
thaw_macro = { version = "0.1.0", path = "./thaw_macro" }
thaw_utils = { version = "0.0.3", path = "./thaw_utils" }
leptos = { git = "https://github.com/leptos-rs/leptos", rev = "db02d3f5" }
leptos_meta = { git = "https://github.com/leptos-rs/leptos", rev = "db02d3f5" }
leptos = { git = "https://github.com/leptos-rs/leptos", rev = "f8c7a237" }
leptos_meta = { git = "https://github.com/leptos-rs/leptos", rev = "f8c7a237" }

View file

@ -9,7 +9,7 @@ edition = "2021"
[dependencies]
leptos = { workspace = true }
leptos_meta = { workspace = true }
leptos_router = { git = "https://github.com/leptos-rs/leptos", rev = "db02d3f5" }
leptos_router = { git = "https://github.com/leptos-rs/leptos", rev = "f8c7a237" }
thaw = { path = "../thaw" }
demo_markdown = { path = "../demo_markdown" }
icondata = "0.3.0"

View file

@ -1,4 +1,4 @@
mod use_active_descendant;
mod use_option_walker;
pub use use_active_descendant::use_active_descendant;
pub use use_active_descendant::{use_active_descendant, ActiveDescendantController};

View file

@ -11,11 +11,12 @@ const ACTIVEDESCENDANT_FOCUSVISIBLE_ATTRIBUTE: &str = "data-activedescendant-foc
pub fn use_active_descendant<MF>(
match_option: MF,
) -> (Arc<dyn Fn(Node)>, ActiveDescendantController)
) -> (Arc<dyn Fn(Node) + Send + Sync>, ActiveDescendantController)
where
MF: Fn(HtmlElement) -> bool + 'static,
MF: Fn(HtmlElement) -> bool + Send + Sync + 'static,
{
let (set_listbox, option_walker) = use_option_walker(match_option);
//TODO
let set_listbox = Arc::new(move |node| {
set_listbox(&node);
});

View file

@ -4,9 +4,9 @@ use std::sync::Arc;
use wasm_bindgen::{closure::Closure, JsCast, UnwrapThrowExt};
use web_sys::{HtmlElement, Node, NodeFilter, TreeWalker};
pub fn use_option_walker<MF>(match_option: MF) -> (Box<dyn Fn(&Node)>, OptionWalker)
pub fn use_option_walker<MF>(match_option: MF) -> (Box<dyn Fn(&Node) + Send + Sync>, OptionWalker)
where
MF: Fn(HtmlElement) -> bool + 'static,
MF: Fn(HtmlElement) -> bool + Send + Sync + 'static,
{
let tree_walker = StoredValue::new(
None::<(

View file

@ -103,30 +103,6 @@
overflow-y: auto;
}
.thaw-combobox__listbox.fade-in-scale-up-transition-leave-active {
transform-origin: inherit;
transition: opacity 0.2s cubic-bezier(0.4, 0, 1, 1),
transform 0.2s cubic-bezier(0.4, 0, 1, 1);
}
.thaw-combobox__listbox.fade-in-scale-up-transition-enter-active {
transform-origin: inherit;
transition: opacity 0.2s cubic-bezier(0, 0, 0.2, 1),
transform 0.2s cubic-bezier(0, 0, 0.2, 1);
}
.thaw-combobox__listbox.fade-in-scale-up-transition-enter-from,
.thaw-combobox__listbox.fade-in-scale-up-transition-leave-to {
opacity: 0;
transform: scale(0.9);
}
.thaw-combobox__listbox.fade-in-scale-up-transition-leave-from,
.thaw-combobox__listbox.fade-in-scale-up-transition-enter-to {
opacity: 1;
transform: scale(1);
}
.thaw-combobox-option {
column-gap: var(--spacingHorizontalXS);
position: relative;

View file

@ -1,8 +1,8 @@
use super::utils::{get_dropdown_action_from_key, DropdownAction};
use crate::{ConfigInjection, _aria::use_active_descendant};
use super::listbox::{listbox_keyboard_event, Listbox};
use crate::_aria::use_active_descendant;
use leptos::{context::Provider, ev, html, prelude::*};
use std::collections::HashMap;
use thaw_components::{Binder, CSSTransition, Follower, FollowerPlacement, FollowerWidth};
use thaw_components::{Binder, Follower, FollowerPlacement, FollowerWidth};
use thaw_utils::{add_event_listener, mount_style, Model};
#[component]
@ -14,7 +14,6 @@ pub fn Combobox(
children: Children,
) -> impl IntoView {
mount_style("combobox", include_str!("./combobox.css"));
let config_provider = ConfigInjection::use_();
let trigger_ref = NodeRef::<html::Div>::new();
let input_ref = NodeRef::<html::Input>::new();
let listbox_ref = NodeRef::<html::Div>::new();
@ -115,51 +114,20 @@ pub fn Combobox(
}
};
let effect = RenderEffect::new(move |_| {
if let Some(listbox_el) = listbox_ref.get() {
set_listbox(listbox_el.into());
}
});
on_cleanup(move || {
drop(effect);
});
let on_keydown = move |e| {
let open = is_show_listbox.get_untracked();
let action = get_dropdown_action_from_key(e, open, multiselect);
let active_option = active_descendant_controller.active();
match action {
DropdownAction::Type | DropdownAction::Open => {
if !open {
is_show_listbox.set(true);
}
}
DropdownAction::CloseSelect | DropdownAction::Close => {
if let Some(option) = active_option {
combobox_injection.options.with_value(|options| {
if let Some((value, text)) = options.get(&option.id()) {
combobox_injection.select_option(value, text);
}
});
}
}
DropdownAction::Next => {
if active_option.is_some() {
active_descendant_controller.next();
} else {
active_descendant_controller.first();
}
}
DropdownAction::Previous => {
if active_option.is_some() {
active_descendant_controller.prev();
} else {
active_descendant_controller.first();
}
}
DropdownAction::None => {}
};
listbox_keyboard_event(
e,
is_show_listbox,
multiselect,
&active_descendant_controller,
move |option| {
combobox_injection.options.with_value(|options| {
if let Some((value, text)) = options.get(&option.id()) {
combobox_injection.select_option(value, text);
}
});
},
);
};
view! {
@ -228,23 +196,9 @@ pub fn Combobox(
width=FollowerWidth::MinTarget
>
<Provider value=combobox_injection>
<CSSTransition
node_ref=listbox_ref
name="fade-in-scale-up-transition"
appear=is_show_listbox.get_untracked()
show=is_show_listbox
let:display
>
<div
class="thaw-config-provider thaw-combobox__listbox"
style=move || display.get().unwrap_or_default()
data-thaw-id=config_provider.id().clone()
node_ref=listbox_ref
role="listbox"
>
{children()}
</div>
</CSSTransition>
<Listbox open=is_show_listbox.read_only() set_listbox listbox_ref class="thaw-combobox__listbox">
{children()}
</Listbox>
</Provider>
</Follower>
</Binder>

View file

@ -0,0 +1,23 @@
.thaw-listbox.fade-in-scale-up-transition-leave-active {
transform-origin: inherit;
transition: opacity 0.2s cubic-bezier(0.4, 0, 1, 1),
transform 0.2s cubic-bezier(0.4, 0, 1, 1);
}
.thaw-listbox.fade-in-scale-up-transition-enter-active {
transform-origin: inherit;
transition: opacity 0.2s cubic-bezier(0, 0, 0.2, 1),
transform 0.2s cubic-bezier(0, 0, 0.2, 1);
}
.thaw-listbox.fade-in-scale-up-transition-enter-from,
.thaw-listbox.fade-in-scale-up-transition-leave-to {
opacity: 0;
transform: scale(0.9);
}
.thaw-listbox.fade-in-scale-up-transition-leave-from,
.thaw-listbox.fade-in-scale-up-transition-enter-to {
opacity: 1;
transform: scale(1);
}

View file

@ -0,0 +1,104 @@
use std::sync::Arc;
use super::utils::{get_dropdown_action_from_key, DropdownAction};
use crate::{ConfigInjection, _aria::ActiveDescendantController};
use leptos::{ev, html, prelude::*};
use thaw_components::CSSTransition;
use thaw_utils::mount_style;
use web_sys::{HtmlElement, Node};
#[component]
pub fn Listbox(
open: ReadSignal<bool>,
class: &'static str,
set_listbox: Arc<dyn Fn(Node) + Send + Sync>,
listbox_ref: NodeRef<html::Div>,
children: Children,
) -> impl IntoView {
mount_style("listbox", include_str!("./listbox.css"));
let config_provider = ConfigInjection::expect_context();
let effect = RenderEffect::new(move |_| {
if let Some(listbox_el) = listbox_ref.get() {
set_listbox(listbox_el.into());
}
});
on_cleanup(move || {
drop(effect);
});
view! {
<CSSTransition
node_ref=listbox_ref
name="fade-in-scale-up-transition"
appear=open.get_untracked()
show=open
let:display
>
<div
class=format!("thaw-config-provider thaw-listbox {class}")
style=move || display.get().unwrap_or_default()
data-thaw-id=config_provider.id().clone()
node_ref=listbox_ref
role="listbox"
>
{children()}
</div>
</CSSTransition>
}
}
pub fn listbox_keyboard_event(
e: ev::KeyboardEvent,
open: RwSignal<bool>,
multiselect: bool,
active_descendant_controller: &ActiveDescendantController,
select_option: impl Fn(HtmlElement),
) {
let (open, set_open) = open.split();
let open = open.get_untracked();
let action = get_dropdown_action_from_key(&e, open, multiselect);
let active_option = active_descendant_controller.active();
match action {
DropdownAction::Type | DropdownAction::Open => {
if !open {
set_open.set(true);
}
if action == DropdownAction::Open {
e.prevent_default();
}
}
DropdownAction::CloseSelect | DropdownAction::Select => {
e.prevent_default();
if let Some(option) = active_option {
select_option(option);
}
}
DropdownAction::Next => {
e.prevent_default();
if active_option.is_some() {
active_descendant_controller.next();
} else {
active_descendant_controller.first();
}
}
DropdownAction::Previous => {
e.prevent_default();
if active_option.is_some() {
active_descendant_controller.prev();
} else {
active_descendant_controller.first();
}
}
DropdownAction::First | DropdownAction::PageUp => {
e.prevent_default();
active_descendant_controller.first();
}
DropdownAction::Last | DropdownAction::PageDown => {
e.prevent_default();
active_descendant_controller.last();
}
DropdownAction::Tab | DropdownAction::Close | DropdownAction::None => {}
};
}

View file

@ -1,5 +1,6 @@
mod combobox;
mod combobox_option;
mod listbox;
mod utils;
pub use combobox::*;

View file

@ -1,7 +1,7 @@
use leptos::ev;
pub fn get_dropdown_action_from_key(
e: ev::KeyboardEvent,
e: &ev::KeyboardEvent,
open: bool,
multiselect: bool,
) -> DropdownAction {
@ -29,24 +29,43 @@ pub fn get_dropdown_action_from_key(
{
DropdownAction::CloseSelect
} else if multiselect && KeyboardKey::Space == code {
DropdownAction::Select
} else if KeyboardKey::Escape == code {
DropdownAction::Close
} else if KeyboardKey::ArrowDown == code {
DropdownAction::Next
} else if KeyboardKey::ArrowUp == code {
DropdownAction::Previous
} else if KeyboardKey::Home == code {
DropdownAction::First
} else if KeyboardKey::End == code {
DropdownAction::Last
} else if KeyboardKey::PageUp == code {
DropdownAction::PageUp
} else if KeyboardKey::PageDown == code {
DropdownAction::PageDown
} else if KeyboardKey::Tab == code {
DropdownAction::Tab
} else {
DropdownAction::None
}
}
#[derive(PartialEq)]
pub enum DropdownAction {
None,
Type,
Open,
CloseSelect,
Select,
Close,
Next,
Previous,
First,
Last,
PageUp,
PageDown,
Tab,
}
enum KeyboardKey {
@ -54,6 +73,12 @@ enum KeyboardKey {
ArrowUp,
Enter,
Space,
Escape,
Home,
End,
PageUp,
PageDown,
Tab,
}
impl PartialEq<String> for KeyboardKey {
@ -63,6 +88,12 @@ impl PartialEq<String> for KeyboardKey {
Self::ArrowUp => other == "ArrowUp",
Self::Enter => other == "Enter",
Self::Space => other == "Space",
Self::Escape => other == "Escape",
Self::Home => other == "Home",
Self::End => other == "End",
Self::PageUp => other == "PageUp",
Self::PageDown => other == "PageDown",
Self::Tab => other == "Tab",
}
}
}