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"
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"]

View file

@ -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>() {
match msg {
HtmlSelectMessage::OnChange(event) => {
let i = if let Some(select) = event.target_dyn_into::<HtmlSelectElement>() {
select.selected_index()
} else {
unreachable!("unexpected Event: {:?}", msg);
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
}
fn changed(&mut self, _ctx: &Context<Self>, _old_props: &Self::Properties) -> bool {
self.update_selected = true;
true
HtmlSelectMessage::Untaint => {
self.select_option(ctx);
false
}
}
}
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,8 +129,13 @@ 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;
// 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) {
@ -131,4 +149,3 @@ impl<T: ImplicitClone + PartialEq + 'static> Component for HtmlSelect<T> {
}
}
}
}