Remove InputData & ChangeData (#2000)

This commit is contained in:
mc1098 2021-08-16 17:02:14 +01:00 committed by GitHub
parent 0afc6952cf
commit c6099cf875
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 240 additions and 162 deletions

View File

@ -1,5 +1,8 @@
use std::cell::Cell;
use yew::{html, Callback, Component, ComponentLink, Html, InputData, Properties, ShouldRender};
use yew::{
html, web_sys::HtmlInputElement, Callback, Component, ComponentLink, Html, InputEvent,
Properties, ShouldRender, TargetCast,
};
thread_local! {
static SLIDER_ID: Cell<usize> = Cell::default();
@ -78,6 +81,11 @@ impl Component for Slider {
10f64.powi(-(p as i32))
});
let oninput = onchange.reform(|e: InputEvent| {
let input: HtmlInputElement = e.target_unchecked_into();
input.value_as_number()
});
html! {
<div class="slider">
<label for={id.clone()} class="slider__label">{ label }</label>
@ -85,8 +93,7 @@ impl Component for Slider {
{id}
class="slider__input"
min={min.to_string()} max={max.to_string()} step={step.to_string()}
oninput={onchange.reform(|data: InputData| data.value.parse().unwrap())}
value={value.to_string()}
{oninput}
/>
<span class="slider__value">{ display_value }</span>
</div>

View File

@ -1,6 +1,8 @@
use crate::Client;
use yew::{
classes, html, Callback, Component, ComponentLink, Html, InputData, Properties, ShouldRender,
classes, html,
web_sys::{Event, HtmlInputElement, HtmlTextAreaElement},
Callback, Component, ComponentLink, Html, Properties, ShouldRender, TargetCast,
};
#[derive(Debug)]
@ -72,26 +74,36 @@ impl Component for AddClientForm {
fn view(&self) -> Html {
let Self { link, client, .. } = self;
let update_name = |f: fn(String) -> Msg| {
link.callback(move |e: Event| {
let input: HtmlInputElement = e.target_unchecked_into();
f(input.value())
})
};
let update_desc = link.callback(|e: Event| {
let textarea: HtmlTextAreaElement = e.target_unchecked_into();
Msg::UpdateDescription(textarea.value())
});
html! {
<>
<div class="names">
<input
class={classes!("new-client", "firstname")}
placeholder="First name"
value={client.first_name.clone()}
oninput={link.callback(|e: InputData| Msg::UpdateFirstName(e.value))}
onchange={update_name(Msg::UpdateFirstName)}
/>
<input
class={classes!("new-client", "lastname")}
placeholder="Last name"
value={client.last_name.clone()}
oninput={link.callback(|e: InputData| Msg::UpdateLastName(e.value))}
onchange={update_name(Msg::UpdateLastName)}
/>
<textarea
class={classes!("new-client", "description")}
placeholder="Description"
value={client.description.clone()}
oninput={link.callback(|e: InputData| Msg::UpdateDescription(e.value))}
onchange={update_desc}
/>
</div>

View File

@ -1,4 +1,5 @@
use yew::{html, ChangeData, Component, ComponentLink, Html, ShouldRender};
use web_sys::{Event, HtmlInputElement};
use yew::{html, html::TargetCast, Component, ComponentLink, Html, ShouldRender};
use gloo::file::callbacks::FileReader;
use gloo::file::File;
@ -87,9 +88,11 @@ impl Component for Model {
<div>
<div>
<p>{ "Choose a file to upload to see the uploaded bytes" }</p>
<input type="file" multiple=true onchange={self.link.callback(move |value| {
<input type="file" multiple=true onchange={self.link.callback(move |e: Event| {
let mut result = Vec::new();
if let ChangeData::Files(files) = value {
let input: HtmlInputElement = e.target_unchecked_into();
if let Some(files) = input.files() {
let files = js_sys::try_iter(&files)
.unwrap()
.unwrap()

View File

@ -1,5 +1,5 @@
use wasm_bindgen::prelude::*;
use yew::prelude::*;
use yew::{prelude::*, web_sys::HtmlTextAreaElement};
mod bindings;
@ -57,7 +57,10 @@ impl Component for Model {
<>
<textarea
class="code-block"
oninput={self.link.callback(|input: InputData| Msg::Payload(input.value))}
oninput={self.link.callback(|e: InputEvent| {
let input: HtmlTextAreaElement = e.target_unchecked_into();
Msg::Payload(input.value())
})}
value={self.payload.clone()}
/>
<button onclick={self.link.callback(|_| Msg::Payload(bindings::get_payload()))}>

View File

@ -2,7 +2,7 @@ use instant::Instant;
use person::PersonType;
use yew::prelude::*;
use yew::utils::NeqAssign;
use yew::web_sys::HtmlElement;
use yew::web_sys::{HtmlElement, HtmlInputElement};
mod person;
mod random;
@ -10,7 +10,7 @@ mod random;
pub enum Msg {
CreatePersons(usize),
CreatePersonsPrepend(usize),
ChangeRatio(String),
ChangeRatio(f64),
DeletePersonById(usize),
DeleteEverybody,
SwapRandom,
@ -70,7 +70,6 @@ impl Component for Model {
true
}
Msg::ChangeRatio(ratio) => {
let ratio: f64 = ratio.parse().unwrap_or(0.5);
if self.build_component_ratio.neq_assign(ratio) {
log::info!("Ratio changed: {}", ratio);
true
@ -169,8 +168,10 @@ impl Model {
{ self.build_component_ratio }
</p>
<input name="ratio" type="range" class="form-control-range" min="0.0" max="1.0" step="any"
value={self.build_component_ratio.to_string()}
oninput={self.link.callback(|e: InputData| Msg::ChangeRatio(e.value))}
oninput={self.link.callback(|e: InputEvent| {
let input: HtmlInputElement = e.target_unchecked_into();
Msg::ChangeRatio(input.value_as_number())
})}
/>
</div>
</div>

View File

@ -1,6 +1,8 @@
use wasm_bindgen::JsValue;
use web_sys::{CanvasRenderingContext2d, Document, HtmlCanvasElement};
use yew::{html, Component, ComponentLink, Html, InputData, ShouldRender};
use web_sys::{
CanvasRenderingContext2d, Document, HtmlCanvasElement, HtmlInputElement, InputEvent,
};
use yew::{html, Component, ComponentLink, Html, ShouldRender, TargetCast};
pub enum Msg {
UpdateName(String),
@ -40,7 +42,10 @@ impl Component for Model {
<div>
<input
value={self.name.clone()}
oninput={self.link.callback(|e: InputData| Msg::UpdateName(e.value))}
oninput={self.link.callback(|e: InputEvent| {
let input = e.target_unchecked_into::<HtmlInputElement>();
Msg::UpdateName(input.value())
})}
/>
<p>{ self.name.chars().rev().collect::<String>() }</p>
</div>

View File

@ -1,8 +1,8 @@
use weblog::web_sys::HtmlInputElement;
use yew::prelude::*;
pub enum Msg {
SetText(String),
Submit,
Submit(String),
}
#[derive(Properties, Clone, PartialEq)]
@ -31,12 +31,7 @@ impl Component for TextInput {
fn update(&mut self, msg: Self::Message) -> ShouldRender {
match msg {
Msg::SetText(text) => {
self.text = text;
true
}
Msg::Submit => {
let text = std::mem::replace(&mut self.text, self.props.value.clone());
Msg::Submit(text) => {
self.props.onsubmit.emit(text);
true
}
@ -54,15 +49,23 @@ impl Component for TextInput {
}
fn view(&self) -> Html {
let onkeydown = self.link.batch_callback(|e: KeyboardEvent| {
e.stop_propagation();
if e.key() == "Enter" {
let input: HtmlInputElement = e.target_unchecked_into();
let value = input.value();
input.set_value("");
Some(Msg::Submit(value))
} else {
None
}
});
html! {
<input
placeholder={self.props.value.clone()}
type="text"
value={self.text.clone()}
oninput={self.link.callback(|e: InputData| Msg::SetText(e.value))}
onkeydown={self.link.batch_callback(move |e: KeyboardEvent| {
e.stop_propagation();
if e.key() == "Enter" { Some(Msg::Submit) } else { None }
})}
{onkeydown}
/>
}
}

View File

@ -2,7 +2,9 @@ use gloo::storage::{LocalStorage, Storage};
use state::{Entry, Filter, State};
use strum::IntoEnumIterator;
use yew::web_sys::HtmlInputElement as InputElement;
use yew::{classes, html, Component, ComponentLink, Html, InputData, NodeRef, ShouldRender};
use yew::{
classes, html, Component, ComponentLink, FocusEvent, Html, NodeRef, ShouldRender, TargetCast,
};
use yew::{events::KeyboardEvent, Classes};
mod state;
@ -10,10 +12,8 @@ mod state;
const KEY: &str = "yew.todomvc.self";
pub enum Msg {
Add,
Edit(usize),
Update(String),
UpdateEdit(String),
Add(String),
Edit((usize, String)),
Remove(usize),
SetFilter(Filter),
ToggleAll,
@ -38,7 +38,6 @@ impl Component for Model {
let state = State {
entries,
filter: Filter::All,
value: "".into(),
edit_value: "".into(),
};
let focus_ref = NodeRef::default();
@ -51,31 +50,20 @@ impl Component for Model {
fn update(&mut self, msg: Self::Message) -> ShouldRender {
match msg {
Msg::Add => {
let description = self.state.value.trim();
Msg::Add(description) => {
if !description.is_empty() {
let entry = Entry {
description: description.to_string(),
description: description.trim().to_string(),
completed: false,
editing: false,
};
self.state.entries.push(entry);
}
self.state.value = "".to_string();
}
Msg::Edit(idx) => {
let edit_value = self.state.edit_value.trim().to_string();
self.state.complete_edit(idx, edit_value);
Msg::Edit((idx, edit_value)) => {
self.state.complete_edit(idx, edit_value.trim().to_string());
self.state.edit_value = "".to_string();
}
Msg::Update(val) => {
println!("Input: {}", val);
self.state.value = val;
}
Msg::UpdateEdit(val) => {
println!("Input: {}", val);
self.state.edit_value = val;
}
Msg::Remove(idx) => {
self.state.remove(idx);
}
@ -180,17 +168,23 @@ impl Model {
}
fn view_input(&self) -> Html {
let onkeypress = self.link.batch_callback(|e: KeyboardEvent| {
if e.key() == "Enter" {
let input: InputElement = e.target_unchecked_into();
let value = input.value();
input.set_value("");
Some(Msg::Add(value))
} else {
None
}
});
html! {
// You can use standard Rust comments. One line:
// <li></li>
<input
class="new-todo"
placeholder="What needs to be done?"
value={self.state.value.clone()}
oninput={self.link.callback(|e: InputData| Msg::Update(e.value))}
onkeypress={self.link.batch_callback(|e: KeyboardEvent| {
if e.key() == "Enter" { Some(Msg::Add) } else { None }
})}
{onkeypress}
/>
/* Or multiline:
<ul>
@ -226,6 +220,20 @@ impl Model {
}
fn view_entry_edit_input(&self, (idx, entry): (usize, &Entry)) -> Html {
let edit = move |input: InputElement| {
let value = input.value();
input.set_value("");
Msg::Edit((idx, value))
};
let onblur = self
.link
.callback(move |e: FocusEvent| edit(e.target_unchecked_into()));
let onkeypress = self.link.batch_callback(move |e: KeyboardEvent| {
(e.key() == "Enter").then(|| edit(e.target_unchecked_into()))
});
if entry.editing {
html! {
<input
@ -234,11 +242,8 @@ impl Model {
ref={self.focus_ref.clone()}
value={self.state.edit_value.clone()}
onmouseover={self.link.callback(|_| Msg::Focus)}
oninput={self.link.callback(|e: InputData| Msg::UpdateEdit(e.value))}
onblur={self.link.callback(move |_| Msg::Edit(idx))}
onkeypress={self.link.batch_callback(move |e: KeyboardEvent| {
if e.key() == "Enter" { Some(Msg::Edit(idx)) } else { None }
})}
{onblur}
{onkeypress}
/>
}
} else {

View File

@ -5,7 +5,6 @@ use strum_macros::{EnumIter, ToString};
pub struct State {
pub entries: Vec<Entry>,
pub filter: Filter,
pub value: String,
pub edit_value: String,
}

View File

@ -27,11 +27,11 @@ help: consider importing one of these items
83 | use crate::t9::foo;
|
error[E0277]: the trait bound `t1::Value: std::default::Default` is not satisfied
error[E0277]: the trait bound `Value: std::default::Default` is not satisfied
--> $DIR/fail.rs:9:21
|
9 | #[derive(Clone, Properties)]
| ^^^^^^^^^^ the trait `std::default::Default` is not implemented for `t1::Value`
| ^^^^^^^^^^ the trait `std::default::Default` is not implemented for `Value`
|
= note: required by `Option::<T>::unwrap_or_default`
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)

View File

@ -9,7 +9,7 @@ use std::{cell::RefCell, rc::Rc};
///
/// # Example
/// ```rust
/// # use yew::prelude::*;
/// # use yew::{prelude::*, web_sys::{Event, HtmlInputElement}};
/// # use std::rc::Rc;
/// # use std::cell::RefCell;
/// # use std::ops::{Deref, DerefMut};
@ -32,10 +32,9 @@ use std::{cell::RefCell, rc::Rc};
///
/// let onchange = {
/// let message = message.clone();
/// Callback::from(move |e| {
/// if let ChangeData::Value(value) = e {
/// message.set(value)
/// }
/// Callback::from(move |e: Event| {
/// let input: HtmlInputElement = e.target_unchecked_into();
/// message.set(input.value())
/// })
/// };
///

View File

@ -6,7 +6,7 @@ impl_action! {
oncancel(name: "cancel", event: Event) -> web_sys::Event => |_, event| { event }
oncanplay(name: "canplay", event: Event) -> web_sys::Event => |_, event| { event }
oncanplaythrough(name: "canplaythrough", event: Event) -> web_sys::Event => |_, event| { event }
onchange(name: "change", event: Event) -> ChangeData => |this: &Element, _| { onchange_handler(this) }
onchange(name: "change", event: Event) -> web_sys::Event => |_, event| { event }
onclick(name: "click", event: MouseEvent) -> web_sys::MouseEvent => |_, event| { event }
onclose(name: "close", event: Event) -> web_sys::Event => |_, event| { event }
oncontextmenu(name: "contextmenu", event: MouseEvent) -> web_sys::MouseEvent => |_, event| { event }
@ -29,7 +29,7 @@ impl_action! {
onfocusout(name: "focusout", event: FocusEvent) -> web_sys::FocusEvent => |_, event| { event }
// web_sys doesn't have a struct for `FormDataEvent`
onformdata(name: "formdata", event: Event) -> web_sys::Event => |_, event| { event }
oninput(name: "input", event: InputEvent) -> InputData => |this: &Element, event| { oninput_handler(this, event) }
oninput(name: "input", event: InputEvent) -> web_sys::InputEvent => |_, event| { event }
oninvalid(name: "invalid", event: Event) -> web_sys::Event => |_, event| { event }
onkeydown(name: "keydown", event: KeyboardEvent) -> web_sys::KeyboardEvent => |_, event| { event }
onkeypress(name: "keypress", event: KeyboardEvent) -> web_sys::KeyboardEvent => |_, event| { event }

View File

@ -3,89 +3,128 @@ mod macros;
mod events;
use wasm_bindgen::JsCast;
use web_sys::{
Element, FileList, HtmlInputElement as InputElement, HtmlSelectElement as SelectElement,
HtmlTextAreaElement as TextAreaElement, InputEvent,
};
use web_sys::{Event, EventTarget};
use crate::Callback;
pub use events::*;
/// A type representing data from `oninput` event.
#[derive(Debug)]
pub struct InputData {
/// Inserted characters. Contains value from
/// [InputEvent](https://developer.mozilla.org/en-US/docs/Web/API/InputEvent/data).
pub value: String,
/// The InputEvent received.
pub event: InputEvent,
}
/// A trait to obtain a generic event target.
///
/// The methods in this trait are convenient helpers that use the [`JsCast`] trait internally
/// to do the conversion.
pub trait TargetCast
where
Self: AsRef<Event>,
{
/// Performs a dynamic cast (checked at runtime) of this events target into the type `T`.
///
/// This method can return [`None`] for two reasons:
/// - The event's target was [`None`]
/// - The event's target type did not match `T`
///
/// # Example
///
/// ```
/// use yew::{prelude::*, web_sys::{Event, HtmlTextAreaElement}};
/// # enum Msg {
/// # Value(String),
/// # }
/// # struct Comp {
/// # link: ComponentLink<Self>,
/// # }
/// # impl Component for Comp {
/// # type Properties = ();
/// # type Message = Msg;
/// # fn create(_: Self::Properties, link: ComponentLink<Self>) -> Self {
/// # Self { link }
/// # }
/// # fn update(&mut self, _: Self::Message) -> ShouldRender { false }
/// # fn change(&mut self, _: Self::Properties) -> ShouldRender { false }
///
/// fn view(&self) -> Html {
/// html! {
/// <div
/// onchange={self.link.batch_callback(|e: Event| {
/// if let Some(input) = e.target_dyn_into::<HtmlTextAreaElement>() {
/// Some(Msg::Value(input.value()))
/// } else {
/// None
/// }
/// })}
/// >
/// <textarea />
/// <input type="text" />
/// </div>
/// }
/// }
/// # }
/// ```
/// _Note: if you can apply the [`Callback`] directly onto an element which doesn't have a child
/// consider using [`TargetCast::target_unchecked_into<T>`]_
#[inline]
fn target_dyn_into<T>(&self) -> Option<T>
where
T: AsRef<EventTarget> + JsCast,
{
self.as_ref()
.target()
.and_then(|target| target.dyn_into().ok())
}
// There is no '.../Web/API/ChangeEvent/data' (for onchange) similar to
// https://developer.mozilla.org/en-US/docs/Web/API/InputEvent/data (for oninput).
// ChangeData actually contains the value of the InputElement/TextAreaElement
// after `change` event occured or contains the SelectElement (see more at the
// variant ChangeData::Select)
/// A type representing change of value(s) of an element after committed by user
/// ([onchange event](https://developer.mozilla.org/en-US/docs/Web/Events/change)).
#[derive(Debug)]
pub enum ChangeData {
/// Value of the element in cases of `<input>`, `<textarea>`
Value(String),
/// SelectElement in case of `<select>` element. You can use one of methods of SelectElement
/// to collect your required data such as `value` and `selected_index`.
/// You can also iterate throught `selected_options` yourself, this does require adding the
/// [web-sys](https://crates.io/crates/web-sys) crate with the `HtmlCollection` feature.
Select(SelectElement),
/// Files
Files(FileList),
}
fn oninput_handler(this: &Element, event: InputEvent) -> InputData {
// Normally only InputElement or TextAreaElement can have an oninput event listener. In
// practice though any element with `contenteditable=true` may generate such events,
// therefore here we fall back to just returning the text content of the node.
// See https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/input_event.
let (v1, v2) = (
this.dyn_ref().map(|input: &InputElement| input.value()),
this.dyn_ref().map(|input: &TextAreaElement| input.value()),
);
let v3 = this.text_content();
let value = v1.or(v2).or(v3)
.expect("only an InputElement or TextAreaElement or an element with contenteditable=true can have an oninput event listener");
InputData { value, event }
}
fn onchange_handler(this: &Element) -> ChangeData {
match this.node_name().as_ref() {
"INPUT" => {
let input = this.dyn_ref::<InputElement>().unwrap();
let is_file = input
.get_attribute("type")
.map(|value| value.eq_ignore_ascii_case("file"))
.unwrap_or(false);
if is_file {
let files: FileList = input.files().unwrap();
ChangeData::Files(files)
} else {
ChangeData::Value(input.value())
}
}
"TEXTAREA" => {
let tae = this.dyn_ref::<TextAreaElement>().unwrap();
ChangeData::Value(tae.value())
}
"SELECT" => {
let se = this.dyn_ref::<SelectElement>().unwrap().clone();
ChangeData::Select(se)
}
_ => {
panic!("only an InputElement, TextAreaElement or SelectElement can have an onchange event listener");
}
/// Performs a zero-cost unchecked cast of this events target into the type `T`.
///
/// This method **does not check whether the event target is an instance of `T`**. If used
/// incorrectly then this method may cause runtime exceptions in both Rust and JS, this should
/// be used with caution.
///
/// A common safe usage of this method is within a [`Callback`] that is applied directly to an
/// element that has no children, thus `T` will be the type of the element the [`Callback`] is
/// applied to.
///
/// # Example
///
/// ```
/// use yew::{prelude::*, web_sys::{Event, HtmlInputElement}};
/// # enum Msg {
/// # Value(String),
/// # }
/// # struct Comp {
/// # link: ComponentLink<Self>,
/// # }
/// # impl Component for Comp {
/// # type Properties = ();
/// # type Message = Msg;
/// # fn create(_: Self::Properties, link: ComponentLink<Self>) -> Self {
/// # Self { link }
/// # }
/// # fn update(&mut self, _: Self::Message) -> ShouldRender { false }
/// # fn change(&mut self, _: Self::Properties) -> ShouldRender { false }
///
/// fn view(&self) -> Html {
/// html! {
/// <input type="text"
/// onchange={self.link.callback(|e: Event| {
/// // Safe to use as callback is on an `input` element so this event can
/// // only come from this input!
/// let input: HtmlInputElement = e.target_unchecked_into();
/// Msg::Value(input.value())
/// })}
/// />
/// }
/// }
/// # }
/// ```
#[inline]
fn target_unchecked_into<T>(&self) -> T
where
T: AsRef<EventTarget> + JsCast,
{
self.as_ref().target().unwrap().unchecked_into()
}
}
impl<E: AsRef<Event>> TargetCast for E {}
/// A trait similar to `Into<T>` which allows conversion of a value into a [`Callback`].
/// This is used for event listeners.
pub trait IntoEventCallback<EVENT> {

View File

@ -279,7 +279,7 @@ pub use web_sys;
/// The module that contains all events available in the framework.
pub mod events {
pub use crate::html::{ChangeData, InputData};
pub use crate::html::TargetCast;
#[doc(no_inline)]
pub use web_sys::{

View File

@ -48,7 +48,7 @@ If you need the component to be re-rendered on state change, consider using [`us
```rust
#[function_component(UseRef)]
fn ref_hook() -> Html {
let (message, set_message) = use_state(|| "".to_string());
let message = use_state(|| "".to_string());
let message_count = use_ref(|| 0);
let onclick = Callback::from(move |e| {
@ -62,11 +62,13 @@ fn ref_hook() -> Html {
}
});
let onchange = Callback::from(move |e| {
if let ChangeData::Value(value) = e {
set_message(value)
}
});
let onchange = {
let message = message.clone();
Callback::from(move |e: Event| {
let input: HtmlInputElement = e.target_unchecked_into();
message.set(input.value());
})
};
html! {
<div>

View File

@ -222,7 +222,7 @@ end up using a version which conflicts with the version that Yew specifies.
| `oncancel` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
| `oncanplay` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
| `oncanplaythrough` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
| `onchange` | [ChangeData](https://docs.rs/yew/latest/yew/events/enum.ChangeData.html) |
| `onchange` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
| `onclick` | [MouseEvent](https://docs.rs/web-sys/latest/web_sys/struct.MouseEvent.html) |
| `onclose` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
| `oncontextmenu` | [MouseEvent](https://docs.rs/web-sys/latest/web_sys/struct.MouseEvent.html) |
@ -244,7 +244,7 @@ end up using a version which conflicts with the version that Yew specifies.
| `onfocusin` | [FocusEvent](https://docs.rs/web-sys/latest/web_sys/struct.FocusEvent.html) |
| `onfocusout` | [FocusEvent](https://docs.rs/web-sys/latest/web_sys/struct.FocusEvent.html) |
| `onformdata` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
| `oninput` | [InputData](https://docs.rs/yew/latest/yew/events/struct.InputData.html) |
| `oninput` | [InputEvent](https://docs.rs/web-sys/latest/web_sys/struct.InputEvent.html) |
| `oninvalid` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
| `onkeydown` | [KeyboardEvent](https://docs.rs/web-sys/latest/web_sys/struct.KeyboardEvent.html) |
| `onkeypress` | [KeyboardEvent](https://docs.rs/web-sys/latest/web_sys/struct.KeyboardEvent.html) |