HtmlSelect: fix handling of unchanged option selection (#167)

This commit is contained in:
Cecile Tonglet 2023-03-01 15:50:38 +01:00 committed by GitHub
parent b71f69ade9
commit f91f5810be
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 53 additions and 35 deletions

View file

@ -24,6 +24,7 @@ implicit-clone = "0.3.5"
wasm-bindgen = "0.2" wasm-bindgen = "0.2"
web-sys = { version = "0.3", features = ["DomRect", "Element", "Event", "HtmlSelectElement", "DomTokenList"] } web-sys = { version = "0.3", features = ["DomRect", "Element", "Event", "HtmlSelectElement", "DomTokenList"] }
yew = "0.20" yew = "0.20"
yew-callbacks = "0.2.1"
[workspace] [workspace]
members = ["yewprint-css", "yewprint-doc", "xtask"] members = ["yewprint-css", "yewprint-doc", "xtask"]

View file

@ -1,15 +1,13 @@
use implicit_clone::{unsync::IArray, ImplicitClone};
use std::marker::PhantomData;
use crate::Icon; use crate::Icon;
use implicit_clone::{unsync::IArray, ImplicitClone};
use web_sys::HtmlSelectElement; use web_sys::HtmlSelectElement;
use yew::prelude::*; use yew::prelude::*;
#[derive(Debug)] #[derive(Debug)]
pub struct HtmlSelect<T: Clone + PartialEq + 'static> { pub struct HtmlSelect<T: ImplicitClone + PartialEq + 'static> {
select_element: NodeRef, select_element: NodeRef,
update_selected: bool, cb: HtmlSelectMessageCallbacks<Self>,
phantom: PhantomData<T>, phantom: std::marker::PhantomData<T>,
} }
#[derive(Debug, Clone, PartialEq, Properties)] #[derive(Debug, Clone, PartialEq, Properties)]
@ -33,35 +31,50 @@ pub struct HtmlSelectProps<T: ImplicitClone + PartialEq + 'static> {
pub class: Classes, 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> { impl<T: ImplicitClone + PartialEq + 'static> Component for HtmlSelect<T> {
type Message = Event; type Message = HtmlSelectMessage;
type Properties = HtmlSelectProps<T>; type Properties = HtmlSelectProps<T>;
fn create(_ctx: &Context<Self>) -> Self { fn create(ctx: &Context<Self>) -> Self {
Self { Self {
select_element: NodeRef::default(), select_element: NodeRef::default(),
update_selected: false, cb: ctx.link().into(),
phantom: PhantomData, phantom: Default::default(),
} }
} }
fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool { fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
let i = if let Some(select) = msg.target_dyn_into::<HtmlSelectElement>() { match msg {
select.selected_index() HtmlSelectMessage::OnChange(event) => {
} else { let i = if let Some(select) = event.target_dyn_into::<HtmlSelectElement>() {
unreachable!("unexpected Event: {:?}", msg); select.selected_index()
}; } else {
if i >= 0 { unreachable!("unexpected Event: {:?}", event);
let i = i as usize; };
let variant = ctx.props().options[i].0.clone(); if i >= 0 {
ctx.props().onchange.emit(variant); 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 { fn view(&self, ctx: &Context<Self>) -> Html {
@ -104,7 +117,7 @@ impl<T: ImplicitClone + PartialEq + 'static> Component for HtmlSelect<T> {
<select <select
value={String::new()} value={String::new()}
disabled={*disabled} disabled={*disabled}
onchange={ctx.link().callback(|x| x)} onchange={self.cb.on_change()}
{title} {title}
ref={self.select_element.clone()} 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) { fn rendered(&mut self, ctx: &Context<Self>, _first_render: bool) {
if self.update_selected { // NOTE: ensure correct option is selected
self.update_selected = false; self.select_option(ctx);
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() { impl<T: ImplicitClone + PartialEq + 'static> HtmlSelect<T> {
if select.selected_index() != i { fn select_option(&self, ctx: &Context<Self>) {
select.set_selected_index(i); 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);
} }
} }
} }