mirror of
https://github.com/yewprint/yewprint
synced 2024-11-21 19:13:05 +00:00
HtmlSelect: fix handling of unchanged option selection (#167)
This commit is contained in:
parent
b71f69ade9
commit
f91f5810be
2 changed files with 53 additions and 35 deletions
|
@ -24,6 +24,7 @@ implicit-clone = "0.3.5"
|
|||
wasm-bindgen = "0.2"
|
||||
web-sys = { version = "0.3", features = ["DomRect", "Element", "Event", "HtmlSelectElement", "DomTokenList"] }
|
||||
yew = "0.20"
|
||||
yew-callbacks = "0.2.1"
|
||||
|
||||
[workspace]
|
||||
members = ["yewprint-css", "yewprint-doc", "xtask"]
|
||||
|
|
|
@ -1,15 +1,13 @@
|
|||
use implicit_clone::{unsync::IArray, ImplicitClone};
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use crate::Icon;
|
||||
use implicit_clone::{unsync::IArray, ImplicitClone};
|
||||
use web_sys::HtmlSelectElement;
|
||||
use yew::prelude::*;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct HtmlSelect<T: Clone + PartialEq + 'static> {
|
||||
pub struct HtmlSelect<T: ImplicitClone + PartialEq + 'static> {
|
||||
select_element: NodeRef,
|
||||
update_selected: bool,
|
||||
phantom: PhantomData<T>,
|
||||
cb: HtmlSelectMessageCallbacks<Self>,
|
||||
phantom: std::marker::PhantomData<T>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Properties)]
|
||||
|
@ -33,35 +31,50 @@ pub struct HtmlSelectProps<T: ImplicitClone + PartialEq + 'static> {
|
|||
pub class: Classes,
|
||||
}
|
||||
|
||||
#[derive(Debug, yew_callbacks::Callbacks)]
|
||||
pub enum HtmlSelectMessage {
|
||||
OnChange(Event),
|
||||
// NOTE: the component becomes tainted when a value is selected because there is no way to
|
||||
// prevent the selection of the option in an <input> HTML component. This means the
|
||||
// actual value and the visible value might differ.
|
||||
Untaint,
|
||||
}
|
||||
|
||||
impl<T: ImplicitClone + PartialEq + 'static> Component for HtmlSelect<T> {
|
||||
type Message = Event;
|
||||
type Message = HtmlSelectMessage;
|
||||
type Properties = HtmlSelectProps<T>;
|
||||
|
||||
fn create(_ctx: &Context<Self>) -> Self {
|
||||
fn create(ctx: &Context<Self>) -> Self {
|
||||
Self {
|
||||
select_element: NodeRef::default(),
|
||||
update_selected: false,
|
||||
phantom: PhantomData,
|
||||
cb: ctx.link().into(),
|
||||
phantom: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
|
||||
let i = if let Some(select) = msg.target_dyn_into::<HtmlSelectElement>() {
|
||||
select.selected_index()
|
||||
} else {
|
||||
unreachable!("unexpected Event: {:?}", msg);
|
||||
};
|
||||
if i >= 0 {
|
||||
let i = i as usize;
|
||||
let variant = ctx.props().options[i].0.clone();
|
||||
ctx.props().onchange.emit(variant);
|
||||
match msg {
|
||||
HtmlSelectMessage::OnChange(event) => {
|
||||
let i = if let Some(select) = event.target_dyn_into::<HtmlSelectElement>() {
|
||||
select.selected_index()
|
||||
} else {
|
||||
unreachable!("unexpected Event: {:?}", event);
|
||||
};
|
||||
if i >= 0 {
|
||||
let i = i as usize;
|
||||
let variant = ctx.props().options[i].0.clone();
|
||||
ctx.props().onchange.emit(variant);
|
||||
// NOTE: register option selection update for later even if the parent
|
||||
// component is not going to re-render
|
||||
ctx.link().send_message(HtmlSelectMessage::Untaint);
|
||||
}
|
||||
false
|
||||
}
|
||||
HtmlSelectMessage::Untaint => {
|
||||
self.select_option(ctx);
|
||||
false
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn changed(&mut self, _ctx: &Context<Self>, _old_props: &Self::Properties) -> bool {
|
||||
self.update_selected = true;
|
||||
true
|
||||
}
|
||||
|
||||
fn view(&self, ctx: &Context<Self>) -> Html {
|
||||
|
@ -104,7 +117,7 @@ impl<T: ImplicitClone + PartialEq + 'static> Component for HtmlSelect<T> {
|
|||
<select
|
||||
value={String::new()}
|
||||
disabled={*disabled}
|
||||
onchange={ctx.link().callback(|x| x)}
|
||||
onchange={self.cb.on_change()}
|
||||
{title}
|
||||
ref={self.select_element.clone()}
|
||||
>
|
||||
|
@ -116,15 +129,19 @@ impl<T: ImplicitClone + PartialEq + 'static> Component for HtmlSelect<T> {
|
|||
}
|
||||
|
||||
fn rendered(&mut self, ctx: &Context<Self>, _first_render: bool) {
|
||||
if self.update_selected {
|
||||
self.update_selected = false;
|
||||
if let Some(value) = ctx.props().value.as_ref() {
|
||||
if let Some(select) = self.select_element.cast::<HtmlSelectElement>() {
|
||||
if let Some(i) = ctx.props().options.iter().position(|(x, _)| &x == value) {
|
||||
if let Ok(i) = i.try_into() {
|
||||
if select.selected_index() != i {
|
||||
select.set_selected_index(i);
|
||||
}
|
||||
// NOTE: ensure correct option is selected
|
||||
self.select_option(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ImplicitClone + PartialEq + 'static> HtmlSelect<T> {
|
||||
fn select_option(&self, ctx: &Context<Self>) {
|
||||
if let Some(value) = ctx.props().value.as_ref() {
|
||||
if let Some(select) = self.select_element.cast::<HtmlSelectElement>() {
|
||||
if let Some(i) = ctx.props().options.iter().position(|(x, _)| &x == value) {
|
||||
if let Ok(i) = i.try_into() {
|
||||
if select.selected_index() != i {
|
||||
select.set_selected_index(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue