diff --git a/crates/xilem_html/Cargo.toml b/crates/xilem_html/Cargo.toml index c0c42baf6..04307db77 100644 --- a/crates/xilem_html/Cargo.toml +++ b/crates/xilem_html/Cargo.toml @@ -32,7 +32,7 @@ typed = [ "web-sys/HtmlTableSectionElement", "web-sys/HtmlTemplateElement", "web-sys/HtmlTextAreaElement", "web-sys/HtmlTimeElement", "web-sys/HtmlTrackElement", "web-sys/HtmlUListElement", "web-sys/HtmlVideoElement", "web-sys/InputEvent", "web-sys/KeyboardEvent", "web-sys/MouseEvent", - "web-sys/PointerEvent", "web-sys/WheelEvent", + "web-sys/PointerEvent", "web-sys/WheelEvent", "web-sys/TouchEvent", ] [dependencies] diff --git a/crates/xilem_html/src/element/attributes.rs b/crates/xilem_html/src/element/attributes.rs index 9eb2a5dcd..48fbc3c0b 100644 --- a/crates/xilem_html/src/element/attributes.rs +++ b/crates/xilem_html/src/element/attributes.rs @@ -1,9 +1,18 @@ -use std::borrow::Cow; +use std::{borrow::Cow, collections::BTreeSet}; use crate::{diff::Diff, vecmap::VecMap}; type CowStr = Cow<'static, str>; +// TODO not sure how useful an extra enum for attribute keys is (comparison is probably a little bit faster...) +// #[derive(PartialEq, Eq)] +// enum AttrKey { +// Width, +// Height, +// Class, +// Untyped(Box>), +// } + #[derive(PartialEq, Debug)] pub enum AttributeValue { Null, @@ -13,6 +22,7 @@ pub enum AttributeValue { F32(f32), F64(f64), String(CowStr), + Classes(BTreeSet), } impl AttributeValue { @@ -24,6 +34,19 @@ impl AttributeValue { AttributeValue::F32(n) => n.to_string().into(), AttributeValue::F64(n) => n.to_string().into(), AttributeValue::String(s) => s.clone(), + // TODO maybe use Vec as backend (should probably be more performant for few classes, which seems to be the average case) + AttributeValue::Classes(set) => set + .iter() + .fold(String::new(), |mut acc, s| { + if !acc.is_empty() { + acc += " "; + } + if !s.is_empty() { + acc += s; + } + acc + }) + .into(), // AttributeValue::Null shouldn't be serialized within attribute serialization/diffing // as null values should never show up in the attributes map, but for completeness it's serialized to an empty string AttributeValue::Null => "".into(), @@ -114,6 +137,10 @@ impl Attributes { self.0.iter() } + pub fn get_mut(&mut self, key: &str) -> Option<&mut AttributeValue> { + self.0.get_mut(key) + } + /// Computes the diff between two attribute maps /// `other` is the "newer" map, /// i.e. when `other` contains an element that this map doesn't, the diff iterator outputs Diff::Add diff --git a/crates/xilem_html/src/element/elements.rs b/crates/xilem_html/src/element/elements.rs index 2d5c4b978..dc59f1ccb 100644 --- a/crates/xilem_html/src/element/elements.rs +++ b/crates/xilem_html/src/element/elements.rs @@ -1,19 +1,13 @@ //! Types that wrap [`Element`][super::Element] and represent specific element types. //! -use std::{ - any::Any, - borrow::{Borrow, Cow}, - cmp::Ordering, - collections::{BTreeMap, BTreeSet}, - iter::Peekable, - marker::PhantomData, -}; +use std::{any::Any, borrow::Cow, collections::BTreeSet, marker::PhantomData}; use gloo::events::{EventListenerOptions, EventListenerPhase}; use wasm_bindgen::JsCast; -use crate::{event::EventMsg, Event, Pod}; +use super::{remove_attribute, set_attribute, Attributes}; +use crate::{diff::Diff, event::EventMsg, AttributeValue, Event, Pod}; use wasm_bindgen::UnwrapThrowExt; macro_rules! debug_warn { @@ -23,461 +17,12 @@ macro_rules! debug_warn { }} } -use super::{remove_attribute, set_attribute}; -macro_rules! elements { - () => {}; - (($ty_name:ident, $name:ident, $web_sys_ty:ty), $($rest:tt)*) => { - element!($ty_name, $name, $web_sys_ty); - elements!($($rest)*); - }; -} - -macro_rules! element { - ($ty_name:ident, $name:ident, $web_sys_ty:ty) => { - /// A view representing a - #[doc = concat!("`", stringify!($name), "`")] - /// element. - pub struct $ty_name(crate::Element<$web_sys_ty, ViewSeq>); - - /// Builder function for a - #[doc = concat!("`", stringify!($name), "`")] - /// view. - pub fn $name(children: ViewSeq) -> $ty_name { - $ty_name(crate::element(stringify!($name), children)) - } - - impl $ty_name { - /// Set an attribute on this element. - /// - /// # Panics - /// - /// If the name contains characters that are not valid in an attribute name, - /// then the `View::build`/`View::rebuild` functions will panic for this view. - pub fn attr( - mut self, - name: impl Into>, - value: impl Into, - ) -> Self { - self.0.set_attr(name, value); - self - } - - /// Set an attribute on this element. - /// - /// # Panics - /// - /// If the name contains characters that are not valid in an attribute name, - /// then the `View::build`/`View::rebuild` functions will panic for this view. - pub fn set_attr( - &mut self, - name: impl Into>, - value: impl Into, - ) -> &mut Self { - self.0.set_attr(name, value); - self - } - - pub fn after_update(mut self, after_update: impl Fn(&$web_sys_ty) + 'static) -> Self { - self.0 = self.0.after_update(after_update); - self - } - } - - impl crate::view::ViewMarker for $ty_name {} - - impl crate::view::View for $ty_name - where - ViewSeq: crate::view::ViewSequence, - { - type State = crate::ElementState; - type Element = $web_sys_ty; - - fn build( - &self, - cx: &mut crate::context::Cx, - ) -> (xilem_core::Id, Self::State, Self::Element) { - self.0.build(cx) - } - - fn rebuild( - &self, - cx: &mut crate::context::Cx, - prev: &Self, - id: &mut xilem_core::Id, - state: &mut Self::State, - element: &mut Self::Element, - ) -> crate::ChangeFlags { - self.0.rebuild(cx, &prev.0, id, state, element) - } - - fn message( - &self, - id_path: &[xilem_core::Id], - state: &mut Self::State, - message: Box, - app_state: &mut T_, - ) -> xilem_core::MessageResult { - self.0.message(id_path, state, message, app_state) - } - } - }; -} - -// void elements (those without children) are `area`, `base`, `br`, `col`, -// `embed`, `hr`, `img`, `input`, `link`, `meta`, `source`, `track`, `wbr` -elements!( - // the order is copied from - // https://developer.mozilla.org/en-US/docs/Web/HTML/Element - // DOM interfaces copied from https://html.spec.whatwg.org/multipage/grouping-content.html and friends - - // content sectioning - // (Address, address, web_sys::HtmlElement), - // (Article, article, web_sys::HtmlElement), - // (Aside, aside, web_sys::HtmlElement), - // (Footer, footer, web_sys::HtmlElement), - // (Header, header, web_sys::HtmlElement), - // (H1, h1, web_sys::HtmlHeadingElement), - // (H2, h2, web_sys::HtmlHeadingElement), - // (H3, h3, web_sys::HtmlHeadingElement), - // (H4, h4, web_sys::HtmlHeadingElement), - // (H5, h5, web_sys::HtmlHeadingElement), - // (H6, h6, web_sys::HtmlHeadingElement), - // (Hgroup, hgroup, web_sys::HtmlElement), - // (Main, main, web_sys::HtmlElement), - // (Nav, nav, web_sys::HtmlElement), - // (Section, section, web_sys::HtmlElement), - // text content - (Blockquote, blockquote, web_sys::HtmlQuoteElement), - // (Dd, dd, web_sys::HtmlElement), - // (Div, div, web_sys::HtmlDivElement), - (Dl, dl, web_sys::HtmlDListElement), - // (Dt, dt, web_sys::HtmlElement), - // (Figcaption, figcaption, web_sys::HtmlElement), - // (Figure, figure, web_sys::HtmlElement), - (Hr, hr, web_sys::HtmlHrElement), - (Li, li, web_sys::HtmlLiElement), - (Menu, menu, web_sys::HtmlMenuElement), - (Ol, ol, web_sys::HtmlOListElement), - (P, p, web_sys::HtmlParagraphElement), - (Pre, pre, web_sys::HtmlPreElement), - (Ul, ul, web_sys::HtmlUListElement), - // inline text - (A, a, web_sys::HtmlAnchorElement), - // (Abbr, abbr, web_sys::HtmlElement), - // (B, b, web_sys::HtmlElement), - // (Bdi, bdi, web_sys::HtmlElement), - // (Bdo, bdo, web_sys::HtmlElement), - (Br, br, web_sys::HtmlBrElement), - // (Cite, cite, web_sys::HtmlElement), - // (Code, code, web_sys::HtmlElement), - (Data, data, web_sys::HtmlDataElement), - // (Dfn, dfn, web_sys::HtmlElement), - // (Em, em, web_sys::HtmlElement), - // (I, i, web_sys::HtmlElement), - // (Kbd, kbd, web_sys::HtmlElement), - // (Mark, mark, web_sys::HtmlElement), - (Q, q, web_sys::HtmlQuoteElement), - // (Rp, rp, web_sys::HtmlElement), - // (Rt, rt, web_sys::HtmlElement), - // (Ruby, ruby, web_sys::HtmlElement), - // (S, s, web_sys::HtmlElement), - // (Samp, samp, web_sys::HtmlElement), - // (Small, small, web_sys::HtmlElement), - // (Span, span, web_sys::HtmlSpanElement), - // (Strong, strong, web_sys::HtmlElement), - // (Sub, sub, web_sys::HtmlElement), - // (Sup, sup, web_sys::HtmlElement), - (Time, time, web_sys::HtmlTimeElement), - // (U, u, web_sys::HtmlElement), - // (Var, var, web_sys::HtmlElement), - // (Wbr, wbr, web_sys::HtmlElement), - // image and multimedia - (Area, area, web_sys::HtmlAreaElement), - (Audio, audio, web_sys::HtmlAudioElement), - (Img, img, web_sys::HtmlImageElement), - (Map, map, web_sys::HtmlMapElement), - (Track, track, web_sys::HtmlTrackElement), - (Video, video, web_sys::HtmlVideoElement), - // embedded content - (Embed, embed, web_sys::HtmlEmbedElement), - (Iframe, iframe, web_sys::HtmlIFrameElement), - (Object, object, web_sys::HtmlObjectElement), - (Picture, picture, web_sys::HtmlPictureElement), - // (Portal, portal, web_sys::HtmlElement), - (Source, source, web_sys::HtmlSourceElement), - // SVG and MathML (TODO, svg and mathml elements) - // (Svg, svg, web_sys::HtmlElement), - // (Math, math, web_sys::HtmlElement), - // scripting - // (Canvas, canvas, web_sys::HtmlCanvasElement), - // (Noscript, noscript, web_sys::HtmlElement), - (Script, script, web_sys::HtmlScriptElement), - // demarcating edits - (Del, del, web_sys::HtmlModElement), - (Ins, ins, web_sys::HtmlModElement), - // tables - (Caption, caption, web_sys::HtmlTableCaptionElement), - (Col, col, web_sys::HtmlTableColElement), - (Colgroup, colgroup, web_sys::HtmlTableColElement), - (Table, table, web_sys::HtmlTableSectionElement), - (Tbody, tbody, web_sys::HtmlTableSectionElement), - (Td, td, web_sys::HtmlTableCellElement), - (Tfoot, tfoot, web_sys::HtmlTableSectionElement), - (Th, th, web_sys::HtmlTableCellElement), - (Thead, thead, web_sys::HtmlTableSectionElement), - (Tr, tr, web_sys::HtmlTableRowElement), - // forms - (Button, button, web_sys::HtmlButtonElement), - (Datalist, datalist, web_sys::HtmlDataListElement), - (Fieldset, fieldset, web_sys::HtmlFieldSetElement), - (Form, form, web_sys::HtmlFormElement), - (Input, input, web_sys::HtmlInputElement), - (Label, label, web_sys::HtmlLabelElement), - (Legend, legend, web_sys::HtmlLegendElement), - (Meter, meter, web_sys::HtmlMeterElement), - (Optgroup, optgroup, web_sys::HtmlOptGroupElement), - (OptionElement, option, web_sys::HtmlOptionElement), // Avoid cluttering the namespace with `Option` - (Output, output, web_sys::HtmlOutputElement), - (Progress, progress, web_sys::HtmlProgressElement), - (Select, select, web_sys::HtmlSelectElement), - (Textarea, textarea, web_sys::HtmlTextAreaElement), - // interactive elements, - (Details, details, web_sys::HtmlDetailsElement), - (Dialog, dialog, web_sys::HtmlDialogElement), - // (Summary, summary, web_sys::HtmlElement), - // web components, - (Slot, slot, web_sys::HtmlSlotElement), - (Template, template, web_sys::HtmlTemplateElement), -); - -pub struct VecMap(Vec<(K, V)>); - -impl Default for VecMap { - fn default() -> Self { - Self(Vec::new()) - } -} - -/// Basically an ordered Map (similar as BTreeMap) with a Vec as backend for very few elements -impl VecMap { - pub fn get(&self, key: &Q) -> Option<&V> - where - K: Borrow + PartialEq, - Q: PartialEq, - { - self.0 - .iter() - .find_map(|(k, v)| if key.eq(k.borrow()) { Some(v) } else { None }) - } - - pub fn get_mut(&mut self, key: &Q) -> Option<&mut V> - where - K: Borrow + Ord, - Q: Ord, - { - self.0 - .iter_mut() - .find_map(|(k, v)| if key.eq((*k).borrow()) { Some(v) } else { None }) - } - - pub fn keys(&self) -> impl Iterator { - self.0.iter().map(|(name, _)| name) - } - - pub fn iter(&self) -> impl Iterator { - self.0.iter().map(|(k, v)| (k, v)) - } - - pub fn diff<'a>(&'a self, other: &'a Self) -> impl Iterator> + 'a - where - K: Ord, - V: PartialEq, - { - DiffMapIterator { - prev: self.iter().peekable(), - next: other.iter().peekable(), - } - } - - pub fn insert(&mut self, key: K, value: V) -> Option - where - K: Ord, - { - match self.0.binary_search_by_key(&&key, |(n, _)| n) { - Ok(pos) => { - let mut val = (key, value); - std::mem::swap(&mut self.0[pos], &mut val); - Some(val.1) - } - Err(pos) => { - self.0.insert(pos, (key, value)); - None - } - } - } -} - -pub fn diff_tree_maps<'a, K: Ord, V: PartialEq>( - prev: &'a BTreeMap, - next: &'a BTreeMap, -) -> impl Iterator> + 'a { - DiffMapIterator { - prev: prev.iter().peekable(), - next: next.iter().peekable(), - } -} - -struct DiffMapIterator<'a, K: 'a, V: 'a, I: Iterator> { - prev: Peekable, - next: Peekable, -} - -impl<'a, K: Ord + 'a, V: PartialEq, I: Iterator> Iterator - for DiffMapIterator<'a, K, V, I> -{ - type Item = Diff<&'a K, &'a V>; - fn next(&mut self) -> Option { - loop { - match (self.prev.peek(), self.next.peek()) { - (Some(&(prev_k, prev_v)), Some(&(next_k, next_v))) => match prev_k.cmp(next_k) { - Ordering::Less => { - self.prev.next(); - return Some(Diff::Remove(prev_k)); - } - Ordering::Greater => { - self.next.next(); - return Some(Diff::Add(next_k, next_v)); - } - Ordering::Equal => { - self.prev.next(); - self.next.next(); - if prev_v != next_v { - return Some(Diff::Change(next_k, next_v)); - } - } - }, - (Some(&(prev_k, _)), None) => { - self.prev.next(); - return Some(Diff::Remove(prev_k)); - } - (None, Some(&(next_k, next_v))) => { - self.next.next(); - return Some(Diff::Add(next_k, next_v)); - } - (None, None) => return None, - } - } - } -} - -pub enum Diff { - Add(K, V), - Remove(K), - Change(K, V), -} - type CowStr = Cow<'static, str>; -// TODO in the future it's likely there's an element that doesn't implement PartialEq, -// but for now it's simpler for diffing, maybe also use some kind of serialization in that case -#[derive(PartialEq, Debug)] -pub enum AttributeValue { - U32(u32), - I32(i32), - F32(f32), - F64(f64), - String(CowStr), - // for classes mostly - // TODO maybe use Vec as backend (should probably be more performant for few classes, which seems to be the average case) - StringBTreeSet(BTreeSet), -} - -// TODO not sure how useful an extra enum for attribute keys is (comparison is probably a little bit faster...) -// #[derive(PartialEq, Eq)] -// enum AttrKey { -// Width, -// Height, -// Class, -// Untyped(Box>), -// } - -impl AttributeValue { - fn as_cow(&self) -> CowStr { - match self { - AttributeValue::U32(n) => n.to_string().into(), - AttributeValue::I32(n) => n.to_string().into(), - AttributeValue::F32(n) => n.to_string().into(), - AttributeValue::F64(n) => n.to_string().into(), - AttributeValue::String(s) => s.clone(), - // currently just concatenates strings with spaces in between, - // this may change in the future (TODO separate enum tag for classes, and e.g. comma separated lists?) - AttributeValue::StringBTreeSet(bt) => bt - .iter() - .fold(String::new(), |mut acc, s| { - if !acc.is_empty() { - acc += " "; - } - if !s.is_empty() { - acc += s; - } - acc - }) - .into(), - } - } -} - -impl From for AttributeValue { - fn from(value: u32) -> Self { - AttributeValue::U32(value) - } -} - -impl From for AttributeValue { - fn from(value: i32) -> Self { - AttributeValue::I32(value) - } -} - -impl From for AttributeValue { - fn from(value: f32) -> Self { - AttributeValue::F32(value) - } -} - -impl From for AttributeValue { - fn from(value: f64) -> Self { - AttributeValue::F64(value) - } -} - -impl From for AttributeValue { - fn from(value: String) -> Self { - AttributeValue::String(value.into()) - } -} - -impl From for AttributeValue { - fn from(value: CowStr) -> Self { - AttributeValue::String(value) - } -} - -impl From<&'static str> for AttributeValue { - fn from(value: &'static str) -> Self { - AttributeValue::String(value.into()) - } -} - -type Attrs = VecMap; - -impl Attrs { - fn insert_attr(&mut self, name: impl Into, value: impl Into) { - self.insert(name.into(), value.into()); - } -} - -pub trait EventHandler { +pub trait EventHandler +where + OA: crate::OptionalAction, +{ type State; fn build(&self, cx: &mut crate::context::Cx) -> (xilem_core::Id, Self::State); @@ -498,7 +43,9 @@ pub trait EventHandler { ) -> xilem_core::MessageResult; } -impl A> EventHandler for F { +impl, E: 'static, F: Fn(&mut T, E) -> OA> + EventHandler for F +{ type State = (); fn build(&self, _cx: &mut crate::Cx) -> (xilem_core::Id, Self::State) { @@ -528,7 +75,10 @@ impl A> EventHandler for F { } if event.downcast_ref::>().is_some() { let event = *event.downcast::>().unwrap(); - crate::MessageResult::Action(self(app_state, event.event)) + match self(app_state, event.event).action() { + Some(a) => crate::MessageResult::Action(a), + None => crate::MessageResult::Nop, + } } else { debug_warn!("downcasting event in event handler callback failed, discarding"); crate::MessageResult::Stale(event) @@ -536,9 +86,9 @@ impl A> EventHandler for F { } } -struct EventListener { +pub struct EventListener { #[allow(clippy::complexity)] - phantom: PhantomData (T, A, E, El)>, + phantom: PhantomData (T, OA, A, E, El)>, event: &'static str, options: EventListenerOptions, event_handler: EH, @@ -551,11 +101,12 @@ struct EventListenerState { handler_state: EHS, } -impl EventListener +impl EventListener where E: JsCast + 'static, + OA: crate::OptionalAction, El: 'static, - EH: EventHandler>, + EH: EventHandler>, { fn new(event: &'static str, event_handler: EH, options: EventListenerOptions) -> Self { EventListener { @@ -677,18 +228,19 @@ struct DynamicEventListener { } impl DynamicEventListener { - pub fn new(listener: EventListener) -> Self + pub fn new(listener: EventListener) -> Self where T: 'static, A: 'static, E: JsCast + 'static, + OA: crate::OptionalAction + 'static, EL: 'static, - EH: EventHandler> + 'static, + EH: EventHandler> + 'static, { let build: DynamicEventListenerBuildFn = |self_, element, cx| { let (id, state) = self_ .listener - .downcast_ref::>() + .downcast_ref::>() .unwrap() .build(cx, element); (id, Box::new(state)) @@ -698,7 +250,7 @@ impl DynamicEventListener { |self_, event_target, cx, prev, id, state| { let listener = self_ .listener - .downcast_ref::>() + .downcast_ref::>() .unwrap(); if let Some(prev) = prev.listener.downcast_ref() { if let Some(state) = state.downcast_mut() { @@ -719,7 +271,7 @@ impl DynamicEventListener { |self_, id_path, state, message, app_state| { let listener = self_ .listener - .downcast_ref::>() + .downcast_ref::>() .unwrap(); if let Some(state) = state.downcast_mut() { listener.message(id_path, state, message, app_state) @@ -782,7 +334,7 @@ fn impl_build_element( cx: &mut crate::context::Cx, id: xilem_core::Id, node_name: &str, - attrs: &Attrs, + attrs: &Attributes, children: &Vec, event_listeners: &[DynamicEventListener], ) -> (web_sys::HtmlElement, EventListenersState) { @@ -790,7 +342,7 @@ fn impl_build_element( let el = cx.create_html_element(node_name); for (name, value) in attrs.iter() { - el.set_attribute(name, &value.as_cow()).unwrap_throw(); + el.set_attribute(name, &value.serialize()).unwrap_throw(); } for child in children { @@ -815,8 +367,8 @@ fn impl_build_element( #[allow(clippy::too_many_arguments)] fn impl_rebuild_element( cx: &mut crate::context::Cx, - attrs: &Attrs, - prev_attrs: &Attrs, + attrs: &Attributes, + prev_attrs: &Attributes, element: &web_sys::Element, prev_event_listeners: &[DynamicEventListener], event_listeners: &[DynamicEventListener], @@ -831,7 +383,7 @@ fn impl_rebuild_element( for itm in prev_attrs.diff(attrs) { match itm { Diff::Add(name, value) | Diff::Change(name, value) => { - set_attribute(element, name, &value.as_cow()); + set_attribute(element, name, &value.serialize()); changed |= ChangeFlags::OTHER_CHANGE; } Diff::Remove(name) => { @@ -904,121 +456,366 @@ pub trait Node { fn node_name(&self) -> &str; } +macro_rules! def_simple_attr { + ($name:ident, $setter_name: ident, $ty: ty) => { + #[inline(always)] + fn $name(mut self, $name: $ty) -> Self { + self.attrs_mut().insert(stringify!($name), $name); + self + } + + #[inline(always)] + fn $setter_name(&mut self, $name: $ty) { + self.attrs_mut().insert(stringify!($name), $name); + } + }; +} + +// TODO add also something like `set_on_click(&mut self)`? +macro_rules! def_event_attr { + ($name: ident, $name_with_options: ident, $event: ty, $event_name: expr) => { + fn $name(self, handler: EH) -> Self + where + T: 'static, + A: 'static, + Self::Element: 'static, + OA: crate::OptionalAction + 'static, + EH: EventHandler> + 'static, + { + self.$name_with_options(handler, EventListenerOptions::default()) + } + + fn $name_with_options(mut self, handler: EH, options: EventListenerOptions) -> Self + where + T: 'static, + A: 'static, + Self::Element: 'static, + OA: crate::OptionalAction + 'static, + EH: EventHandler> + 'static, + { + self.add_event_listener(EventListener::new($event_name, handler, options)); + self + } + }; +} + /// These traits should mirror the respective DOM interfaces /// In this case https://dom.spec.whatwg.org/#interface-element /// Or rather a curated/opinionated subset that makes sense in xilem for each of these interfaces /// unfortunately with this (builder + generic type parameters in methods) pattern not trait-object-safe -/// -/// I'm generally not sure yet, if it makes sense to do all of this via traits (there are advantages though, but it's (likely) not compilation size...) -/// (thinking about AsRef and AsMut, and implementing all builder methods on the concrete type, similar as with wasm-bindgen) -/// but on the other hand most of these methods should inline to the non-generic Attrs::insert_attr (or similar non-generic impls) -pub trait Element: Node + crate::view::View { - // TODO rename to class (currently conflicts with `ViewExt`) - // TODO should classes be additive? Currently they are. - fn classes(self, class: C) -> Self; - fn add_class(&mut self, class: C); - // TODO should this be in its own trait? (it doesn't have much to do with the DOM Node interface) - fn raw_attrs(&self) -> &Attrs; - // TODO should this be in Node? - fn attr, V: Into>(self, key: K, value: V) -> Self; - fn set_attr, V: Into>(&mut self, key: K, value: V); - - // TODO generate all this event listener boilerplate with macros - fn on_click(self, handler: EH) -> Self - where +pub trait Element: Node + crate::view::View + Sized { + // The following three functions are the only ones that need to be implemented by each element. + // All other functions are implemented either in this trait or in the corresponding dom interface traits + /// Return the raw `&Attributes` of this element + fn attrs(&self) -> &Attributes; + /// Return the raw `&mut Attributes` of this element + fn attrs_mut(&mut self) -> &mut Attributes; + + fn add_event_listener( + &mut self, + listener: EventListener, + ) where T: 'static, A: 'static, - EH: EventHandler> + 'static; + E: JsCast + 'static, + OA: crate::OptionalAction + 'static, + EH: EventHandler> + 'static; - fn on_click_with_options(self, handler: EH, options: EventListenerOptions) -> Self + fn on(mut self, event: &'static str, handler: EH) -> Self where T: 'static, A: 'static, - EH: EventHandler> + 'static; + Self::Element: 'static, + E: JsCast + 'static, + OA: crate::OptionalAction + 'static, + EH: EventHandler> + 'static, + { + self.add_event_listener(EventListener::new(event, handler, Default::default())); + self + } - fn on_scroll(self, handler: EH) -> Self - where - Self: Sized, - T: 'static, - A: 'static, - EH: EventHandler> + 'static; + fn class(mut self, class: C) -> Self { + add_class(self.attrs_mut(), class); + self + } - fn on_scroll_with_options(self, handler: EH, options: EventListenerOptions) -> Self + fn add_class(&mut self, class: C) { + add_class(self.attrs_mut(), class); + } + + /// Set an attribute on this element. + /// + /// # Panics + /// + /// If the name contains characters that are not valid in an attribute name, + /// then the `View::build`/`View::rebuild` functions will panic for this view. + fn attr, V: Into>(mut self, key: K, value: V) -> Self { + self.attrs_mut().insert(key, value); + self + } + + /// Set an attribute on this element. + /// + /// # Panics + /// + /// If the name contains characters that are not valid in an attribute name, + /// then the `View::build`/`View::rebuild` functions will panic for this view. + fn set_attr, V: Into>(&mut self, key: K, value: V) { + self.attrs_mut().insert(key, value); + } + + // Mouse events + def_event_attr!( + on_click, + on_click_with_options, + web_sys::MouseEvent, + "click" + ); + def_event_attr!( + on_dblclick, + on_dblclick_with_options, + web_sys::MouseEvent, + "dblclick" + ); + def_event_attr!( + on_auxclick, + on_auxclick_with_options, + web_sys::MouseEvent, + "auxclick" + ); + def_event_attr!( + on_contextmenu, + on_contextmenu_with_options, + web_sys::MouseEvent, + "contextmenu" + ); + def_event_attr!( + on_mousedown, + on_mousedown_with_options, + web_sys::MouseEvent, + "mousedown" + ); + def_event_attr!( + on_mouseenter, + on_mouseenter_with_options, + web_sys::MouseEvent, + "mouseenter" + ); + def_event_attr!( + on_mouseleave, + on_mouseleave_with_options, + web_sys::MouseEvent, + "mouseleave" + ); + def_event_attr!( + on_mousemove, + on_mousemove_with_options, + web_sys::MouseEvent, + "mousemove" + ); + def_event_attr!( + on_mouseout, + on_mouseout_with_options, + web_sys::MouseEvent, + "mouseout" + ); + def_event_attr!( + on_mouseover, + on_mouseover_with_options, + web_sys::MouseEvent, + "mouseover" + ); + def_event_attr!( + on_mouseup, + on_mouseup_with_options, + web_sys::MouseEvent, + "mouseup" + ); + + // Scroll events + def_event_attr!(on_scroll, on_scroll_with_options, web_sys::Event, "scroll"); + def_event_attr!( + on_scrollend, + on_scrollend_with_options, + web_sys::Event, + "scrollend" + ); + + // Keyboard events + def_event_attr!( + on_keydown, + on_keydown_with_options, + web_sys::KeyboardEvent, + "keydown" + ); + def_event_attr!( + on_keyup, + on_keyup_with_options, + web_sys::KeyboardEvent, + "keyup" + ); + + // Focus events + def_event_attr!( + on_focus, + on_focus_with_options, + web_sys::FocusEvent, + "focus" + ); + def_event_attr!( + on_focusin, + on_focusin_with_options, + web_sys::FocusEvent, + "focusin" + ); + def_event_attr!( + on_focusout, + on_focusout_with_options, + web_sys::FocusEvent, + "focusout" + ); + def_event_attr!(on_blur, on_blur_with_options, web_sys::FocusEvent, "blur"); + + def_event_attr!( + on_input, + on_input_with_options, + web_sys::InputEvent, + "input" + ); + + // With an explicit Fn traitbound for the event-handler/callback, it's possible to workaround explicit necessary typing + // But this obviously restricts the event handler to this exact function type + fn on_touchstart(mut self, callback: F) -> Self where T: 'static, A: 'static, - EH: EventHandler> + 'static; + Self::Element: 'static, + OA: crate::OptionalAction + 'static, + F: for<'a> Fn(&'a mut T, crate::Event) -> OA + 'static, + { + self.add_event_listener(EventListener::new( + "touchstart", + callback, + Default::default(), + )); + self + } // TODO rest of all the methods allowed on an element } -pub trait HtmlElement: Element {} - -pub trait HtmlDivElement: HtmlElement { - // TODO "align" attr -} - -pub trait HtmlSpanElement: HtmlElement {} - -pub trait HtmlHeadingElement: HtmlElement { - // TODO "align" attr +macro_rules! dom_interface_trait_definitions { + ($($dom_interface:ident : $super_dom_interface: ident $body: tt),*) => { + $(pub trait $dom_interface: $super_dom_interface $body)* + }; } -// not sure if an extra trait for this makes sense, but for consistency -pub trait HtmlCanvasElement: HtmlElement { - fn width(self, width: u32) -> Self; - fn set_width(&mut self, width: u32); - - fn height(self, height: u32) -> Self; - fn set_height(&mut self, height: u32); -} +// TODO all the typed attributes +dom_interface_trait_definitions!( + HtmlAnchorElement : HtmlElement {}, + HtmlAreaElement : HtmlElement {}, + HtmlAudioElement : HtmlMediaElement {}, + HtmlBaseElement : HtmlElement {}, + HtmlBodyElement : HtmlElement {}, + HtmlBrElement : HtmlElement {}, + HtmlButtonElement : HtmlElement {}, + HtmlCanvasElement : HtmlElement { + def_simple_attr!(width, set_width, u32); + def_simple_attr!(height, set_height, u32); + }, + HtmlDataElement : HtmlElement {}, + HtmlDataListElement : HtmlElement {}, + HtmlDetailsElement : HtmlElement {}, + HtmlDialogElement : HtmlElement {}, + HtmlDirectoryElement : HtmlElement {}, + HtmlDivElement : HtmlElement {}, + HtmlDListElement : HtmlElement {}, + HtmlElement : Element {}, + HtmlUnknownElement : HtmlElement {}, + HtmlEmbedElement : HtmlElement {}, + HtmlFieldSetElement : HtmlElement {}, + HtmlFontElement : HtmlElement {}, + HtmlFormElement : HtmlElement {}, + HtmlFrameElement : HtmlElement {}, + HtmlFrameSetElement : HtmlElement {}, + HtmlHeadElement : HtmlElement {}, + HtmlHeadingElement : HtmlElement {}, + HtmlHrElement : HtmlElement {}, + HtmlHtmlElement : HtmlElement {}, + HtmlIFrameElement : HtmlElement {}, + HtmlImageElement : HtmlElement {}, + HtmlInputElement : HtmlElement {}, + HtmlLabelElement : HtmlElement {}, + HtmlLegendElement : HtmlElement {}, + HtmlLiElement : HtmlElement {}, + HtmlLinkElement : HtmlElement {}, + HtmlMapElement : HtmlElement {}, + HtmlMediaElement : HtmlElement {}, + HtmlMenuElement : HtmlElement {}, + HtmlMenuItemElement : HtmlElement {}, + HtmlMetaElement : HtmlElement {}, + HtmlMeterElement : HtmlElement {}, + HtmlModElement : HtmlElement {}, + HtmlObjectElement : HtmlElement {}, + HtmlOListElement : HtmlElement {}, + HtmlOptGroupElement : HtmlElement {}, + HtmlOptionElement : HtmlElement {}, + HtmlOutputElement : HtmlElement {}, + HtmlParagraphElement : HtmlElement {}, + HtmlParamElement : HtmlElement {}, + HtmlPictureElement : HtmlElement {}, + HtmlPreElement : HtmlElement {}, + HtmlProgressElement : HtmlElement {}, + HtmlQuoteElement : HtmlElement {}, + HtmlScriptElement : HtmlElement {}, + HtmlSelectElement : HtmlElement {}, + HtmlSlotElement : HtmlElement {}, + HtmlSourceElement : HtmlElement {}, + HtmlSpanElement : HtmlElement {}, + HtmlStyleElement : HtmlElement {}, + HtmlTableCaptionElement : HtmlElement {}, + HtmlTableCellElement : HtmlElement {}, + HtmlTableColElement : HtmlElement {}, + HtmlTableElement : HtmlElement {}, + HtmlTableRowElement : HtmlElement {}, + HtmlTableSectionElement : HtmlElement {}, + HtmlTemplateElement : HtmlElement {}, + HtmlTimeElement : HtmlElement {}, + HtmlTextAreaElement : HtmlElement {}, + HtmlTitleElement : HtmlElement {}, + HtmlTrackElement : HtmlElement {}, + HtmlUListElement : HtmlElement {}, + HtmlVideoElement : HtmlMediaElement {} +); -fn add_class(attrs: &mut Attrs, class: C) { - let mut classes = class.classes().peekable(); +fn add_class(attrs: &mut Attributes, class: C) { + let mut class = class.into_class().peekable(); - if classes.peek().is_none() { + if class.peek().is_none() { return; } match attrs.get_mut("class") { - Some(AttributeValue::StringBTreeSet(attr_value)) => { - attr_value.extend(classes); + Some(AttributeValue::Classes(attr_value)) => { + attr_value.extend(class); } // could be useful, in case untyped values are inserted here Some(untyped_class) if matches!(untyped_class, AttributeValue::String(_)) => { - let mut classes = BTreeSet::from_iter(classes); - classes.insert(if let AttributeValue::String(s) = untyped_class { + let mut class = BTreeSet::from_iter(class); + class.insert(if let AttributeValue::String(s) = untyped_class { s.clone() } else { unreachable!() }); - *untyped_class = AttributeValue::StringBTreeSet(classes); + *untyped_class = AttributeValue::Classes(class); } Some(other) => { // TODO warning // panic!("A static attribute 'class' should always have either the type BTreeSet or String") - *other = AttributeValue::StringBTreeSet(BTreeSet::from_iter(classes)); + *other = AttributeValue::Classes(BTreeSet::from_iter(class)); } None => { - attrs.insert( - "class".into(), - AttributeValue::StringBTreeSet(BTreeSet::from_iter(classes)), - ); - } - }; -} - -macro_rules! impl_simple_attr { - ($name:ident, $setter_name: ident, $ty: ty, $el: ident) => { - #[inline(always)] - fn $name(mut self, $name: $ty) -> $el { - self.attrs.insert_attr(stringify!($name), $name); - self - } - - #[inline(always)] - fn $setter_name(&mut self, $name: $ty) { - self.attrs.insert_attr(stringify!($name), $name); + attrs.insert("class", AttributeValue::Classes(BTreeSet::from_iter(class))); } }; } @@ -1028,100 +825,30 @@ macro_rules! impl_simple_attr { // (see below at `simple_attr_impl` for an example) to avoid big compilation code size macro_rules! impl_element { ($ty_name:ident, $name: ident, $concrete_dom_interface: ident) => { - impl Element for $ty_name + impl Element for $ty_name where - VS: crate::view::ViewSequence, + VS: crate::view::ViewSequence, { - fn classes(mut self, class: C) -> Self { - add_class(&mut self.attrs, class); - self - } - - fn add_class(&mut self, class: C) { - add_class(&mut self.attrs, class); - } - - fn raw_attrs(&self) -> &Attrs { + fn attrs(&self) -> &Attributes { &self.attrs } - fn attr, V: Into>( - mut self, - key: K, - value: V, - ) -> $ty_name { - self.attrs.insert_attr(key, value); - self + fn attrs_mut(&mut self) -> &mut Attributes { + &mut self.attrs } - fn set_attr, V: Into>(&mut self, key: K, value: V) { - self.attrs.insert_attr(key, value); - } - - fn on_click(self, handler: EH) -> $ty_name - where - T: 'static, - A: 'static, - EH: EventHandler< - T, - A, - crate::Event, - > + 'static, // V::Element, but this results in better docs - { - self.on_click_with_options(handler, EventListenerOptions::default()) - } - - fn on_click_with_options( - mut self, - handler: EH, - options: EventListenerOptions, - ) -> $ty_name - where - T: 'static, - A: 'static, - EH: EventHandler< - T, - A, - crate::Event, - > + 'static, // V::Element, but this results in better docs - { - let listener = EventListener::new("click", handler, options); - self.event_listeners - .push(DynamicEventListener::new(listener)); - self - } - - fn on_scroll(self, handler: EH) -> $ty_name - where - T: 'static, - A: 'static, - EH: EventHandler< - T, - A, - crate::Event, - > + 'static, // V::Element, but this results in better docs - { - self.on_scroll_with_options(handler, EventListenerOptions::default()) - } - - fn on_scroll_with_options( - mut self, - handler: EH, - options: EventListenerOptions, - ) -> $ty_name - where - T: 'static, - A: 'static, - EH: EventHandler< - T, - A, - crate::Event, - > + 'static, // V::Element, but this results in better docs + fn add_event_listener( + &mut self, + listener: EventListener, + ) where + T_: 'static, + A_: 'static, + E: JsCast + 'static, + OA: crate::OptionalAction + 'static, + EH: EventHandler> + 'static, { - let listener = EventListener::new("scroll", handler, options); self.event_listeners .push(DynamicEventListener::new(listener)); - self } } }; @@ -1132,16 +859,16 @@ macro_rules! generate_dom_interface_impl { generate_dom_interface_impl!($ty_name, $name, $dom_interface, {}); }; ($ty_name:ident, $name:ident, $dom_interface:ident, $body: tt) => { - impl $dom_interface for $ty_name + impl $dom_interface for $ty_name where - VS: crate::view::ViewSequence, + VS: crate::view::ViewSequence, $body }; } macro_rules! impl_html_dom_interface { ($ty_name: ident, $name: ident, Node) => { - impl Node for $ty_name { + impl Node for $ty_name { fn node_name(&self) -> &str { stringify!($name) } @@ -1155,44 +882,42 @@ macro_rules! impl_html_dom_interface { impl_html_dom_interface!($ty_name, $name, $concrete_dom_interface, Element); generate_dom_interface_impl!($ty_name, $name, HtmlElement); }; - ($ty_name: ident, $name: ident, $concrete_dom_interface: ident, HtmlDivElement) => { - impl_html_dom_interface!($ty_name, $name, $concrete_dom_interface, HtmlElement); - generate_dom_interface_impl!($ty_name, $name, HtmlDivElement); - }; - ($ty_name: ident, $name: ident, $concrete_dom_interface: ident, HtmlSpanElement) => { - impl_html_dom_interface!($ty_name, $name, $concrete_dom_interface, HtmlElement); - generate_dom_interface_impl!($ty_name, $name, HtmlSpanElement); + ($ty_name: ident, $name: ident, $concrete_dom_interface: ident, HtmlAudioElement) => { + impl_html_dom_interface!($ty_name, $name, $concrete_dom_interface, HtmlMediaElement); + generate_dom_interface_impl!($ty_name, $name, HtmlHeadingElement); }; - ($ty_name: ident, $name: ident, $concrete_dom_interface: ident, HtmlHeadingElement) => { - impl_html_dom_interface!($ty_name, $name, $concrete_dom_interface, HtmlElement); + ($ty_name: ident, $name: ident, $concrete_dom_interface: ident, HtmlVideoElement) => { + impl_html_dom_interface!($ty_name, $name, $concrete_dom_interface, HtmlMediaElement); generate_dom_interface_impl!($ty_name, $name, HtmlHeadingElement); }; - ($ty_name: ident, $name: ident, $concrete_dom_interface: ident, HtmlCanvasElement) => { + // TODO resolve parent interface correctly + // All remaining interfaces inherit directly from HtmlElement + ($ty_name: ident, $name: ident, $concrete_dom_interface: ident, $dom_interface: ident) => { impl_html_dom_interface!($ty_name, $name, $concrete_dom_interface, HtmlElement); - generate_dom_interface_impl!($ty_name, $name, HtmlCanvasElement, { - impl_simple_attr!(width, set_width, u32, $ty_name); - impl_simple_attr!(height, set_height, u32, $ty_name); - }); + generate_dom_interface_impl!($ty_name, $name, $dom_interface); }; } +// TODO only use T_ A_ when necessary (e.g. for the `A` element) +// TODO maybe it's possible to reduce even more in the impl function bodies and put into impl_functions +// (should improve compile times and probably wasm binary size) macro_rules! define_html_elements { ($(($ty_name:ident, $name:ident, $dom_interface:ident),)*) => { $( // TODO not sure how much it helps reducing the code size, // but the two attributes could be extracted into its own type, and the actual element type is just a single tuple struct wrapping this type, - pub struct $ty_name { - pub(crate) attrs: Attrs, - event_listeners: Vec>, + pub struct $ty_name { + pub(crate) attrs: Attributes, + event_listeners: Vec>, children: VS, - phantom: std::marker::PhantomData (T, A)>, + phantom: std::marker::PhantomData (T_, A_)>, } - impl crate::view::ViewMarker for $ty_name {} + impl crate::view::ViewMarker for $ty_name {} - impl crate::view::View for $ty_name + impl crate::view::View for $ty_name where - VS: crate::view::ViewSequence, + VS: crate::view::ViewSequence, { type State = ElementState; type Element = web_sys::$dom_interface; @@ -1259,8 +984,8 @@ macro_rules! define_html_elements { id_path: &[xilem_core::Id], state: &mut Self::State, message: Box, - app_state: &mut T, - ) -> xilem_core::MessageResult { + app_state: &mut T_, + ) -> xilem_core::MessageResult { debug_assert!(state.event_listener_state.len() == self.event_listeners.len()); impl_message_element( id_path, @@ -1280,9 +1005,9 @@ macro_rules! define_html_elements { /// Builder function for a #[doc = concat!("`", stringify!($name), "`")] /// element view. - pub fn $name(children: VS) -> $ty_name + pub fn $name(children: VS) -> $ty_name where - VS: crate::view::ViewSequence, + VS: crate::view::ViewSequence, { $ty_name { attrs: Default::default(), @@ -1319,132 +1044,132 @@ define_html_elements!( (Nav, nav, HtmlElement), (Section, section, HtmlElement), // text content - // (Blockquote, blockquote, HtmlQuoteElement), + (Blockquote, blockquote, HtmlQuoteElement), (Dd, dd, HtmlElement), (Div, div, HtmlDivElement), - // (Dl, dl, HtmlDListElement), + (Dl, dl, HtmlDListElement), (Dt, dt, HtmlElement), (Figcaption, figcaption, HtmlElement), (Figure, figure, HtmlElement), - // (Hr, hr, HtmlHrElement), - // (Li, li, HtmlLiElement), - // (Menu, menu, HtmlMenuElement), - // (Ol, ol, HtmlOListElement), - // (P, p, HtmlParagraphElement), - // (Pre, pre, HtmlPreElement), - // (Ul, ul, HtmlUListElement), + (Hr, hr, HtmlHrElement), + (Li, li, HtmlLiElement), + (Menu, menu, HtmlMenuElement), + (Ol, ol, HtmlOListElement), + (P, p, HtmlParagraphElement), + (Pre, pre, HtmlPreElement), + (Ul, ul, HtmlUListElement), // inline text - // (A, a, HtmlAnchorElement), + (A, a, HtmlAnchorElement), (Abbr, abbr, HtmlElement), (B, b, HtmlElement), (Bdi, bdi, HtmlElement), (Bdo, bdo, HtmlElement), - // (Br, br, HtmlBrElement), + (Br, br, HtmlBrElement), (Cite, cite, HtmlElement), (Code, code, HtmlElement), - // (Data, data, HtmlDataElement), + (Data, data, HtmlDataElement), (Dfn, dfn, HtmlElement), (Em, em, HtmlElement), (I, i, HtmlElement), (Kbd, kbd, HtmlElement), (Mark, mark, HtmlElement), - // (Q, q, HtmlQuoteElement), + (Q, q, HtmlQuoteElement), (Rp, rp, HtmlElement), (Rt, rt, HtmlElement), (Ruby, ruby, HtmlElement), (S, s, HtmlElement), (Samp, samp, HtmlElement), (Small, small, HtmlElement), - (Span, span, HtmlSpanElement), // TODO HtmlSpanElement + (Span, span, HtmlSpanElement), (Strong, strong, HtmlElement), (Sub, sub, HtmlElement), (Sup, sup, HtmlElement), - // (Time, time, HtmlTimeElement), + (Time, time, HtmlTimeElement), (U, u, HtmlElement), (Var, var, HtmlElement), (Wbr, wbr, HtmlElement), // image and multimedia - // (Area, area, HtmlAreaElement), - // (Audio, audio, HtmlAudioElement), - // (Img, img, HtmlImageElement), - // (Map, map, HtmlMapElement), - // (Track, track, HtmlTrackElement), - // (Video, video, HtmlVideoElement), + (Area, area, HtmlAreaElement), + (Audio, audio, HtmlAudioElement), + (Img, img, HtmlImageElement), + (Map, map, HtmlMapElement), + (Track, track, HtmlTrackElement), + (Video, video, HtmlVideoElement), // embedded content - // (Embed, embed, HtmlEmbedElement), - // (Iframe, iframe, HtmlIFrameElement), - // (Object, object, HtmlObjectElement), - // (Picture, picture, HtmlPictureElement), + (Embed, embed, HtmlEmbedElement), + (Iframe, iframe, HtmlIFrameElement), + (Object, object, HtmlObjectElement), + (Picture, picture, HtmlPictureElement), (Portal, portal, HtmlElement), - // (Source, source, HtmlSourceElement), + (Source, source, HtmlSourceElement), // SVG and MathML (TODO, svg and mathml elements) (Svg, svg, HtmlElement), (Math, math, HtmlElement), // scripting (Canvas, canvas, HtmlCanvasElement), (Noscript, noscript, HtmlElement), - // (Script, script, HtmlScriptElement), + (Script, script, HtmlScriptElement), // demarcating edits - // (Del, del, HtmlModElement), - // (Ins, ins, HtmlModElement), + (Del, del, HtmlModElement), + (Ins, ins, HtmlModElement), // tables - // (Caption, caption, HtmlTableCaptionElement), - // (Col, col, HtmlTableColElement), - // (Colgroup, colgroup, HtmlTableColElement), - // (Table, table, HtmlTableSectionElement), - // (Tbody, tbody, HtmlTableSectionElement), - // (Td, td, HtmlTableCellElement), - // (Tfoot, tfoot, HtmlTableSectionElement), - // (Th, th, HtmlTableCellElement), - // (Thead, thead, HtmlTableSectionElement), - // (Tr, tr, HtmlTableRowElement), + (Caption, caption, HtmlTableCaptionElement), + (Col, col, HtmlTableColElement), + (Colgroup, colgroup, HtmlTableColElement), + (Table, table, HtmlTableSectionElement), + (Tbody, tbody, HtmlTableSectionElement), + (Td, td, HtmlTableCellElement), + (Tfoot, tfoot, HtmlTableSectionElement), + (Th, th, HtmlTableCellElement), + (Thead, thead, HtmlTableSectionElement), + (Tr, tr, HtmlTableRowElement), // forms - // (Button, button, HtmlButtonElement), - // (Datalist, datalist, HtmlDataListElement), - // (Fieldset, fieldset, HtmlFieldSetElement), - // (Form, form, HtmlFormElement), - // (Input, input, HtmlInputElement), - // (Label, label, HtmlLabelElement), - // (Legend, legend, HtmlLegendElement), - // (Meter, meter, HtmlMeterElement), - // (Optgroup, optgroup, HtmlOptGroupElement), - // (OptionElement, option, web_sys::HtmlOptionElement), // Avoid cluttering the namespace with `Option` - // (Output, output, HtmlOutputElement), - // (Progress, progress, HtmlProgressElement), - // (Select, select, HtmlSelectElement), - // (Textarea, textarea, HtmlTextAreaElement), + (Button, button, HtmlButtonElement), + (Datalist, datalist, HtmlDataListElement), + (Fieldset, fieldset, HtmlFieldSetElement), + (Form, form, HtmlFormElement), + (Input, input, HtmlInputElement), + (Label, label, HtmlLabelElement), + (Legend, legend, HtmlLegendElement), + (Meter, meter, HtmlMeterElement), + (Optgroup, optgroup, HtmlOptGroupElement), + (OptionElement, option, HtmlOptionElement), // Avoid cluttering the namespace with `Option` + (Output, output, HtmlOutputElement), + (Progress, progress, HtmlProgressElement), + (Select, select, HtmlSelectElement), + (Textarea, textarea, HtmlTextAreaElement), // interactive elements, - // (Details, details, HtmlDetailsElement), - // (Dialog, dialog, HtmlDialogElement), + (Details, details, HtmlDetailsElement), + (Dialog, dialog, HtmlDialogElement), (Summary, summary, HtmlElement), // web components, - // (Slot, slot, HtmlSlotElement), - // (Template, template, HtmlTemplateElement), + (Slot, slot, HtmlSlotElement), + (Template, template, HtmlTemplateElement), ); // A few experiments for more flexible attributes (el.class(class: C)) pub trait IntoClass { type ClassIter: Iterator; - fn classes(self) -> Self::ClassIter; + fn into_class(self) -> Self::ClassIter; } impl IntoClass for &'static str { type ClassIter = std::option::IntoIter; - fn classes(self) -> Self::ClassIter { + fn into_class(self) -> Self::ClassIter { Some(self.into()).into_iter() } } impl IntoClass for String { type ClassIter = std::option::IntoIter; - fn classes(self) -> Self::ClassIter { + fn into_class(self) -> Self::ClassIter { Some(self.into()).into_iter() } } impl IntoClass for CowStr { type ClassIter = std::option::IntoIter; - fn classes(self) -> Self::ClassIter { + fn into_class(self) -> Self::ClassIter { Some(self).into_iter() } } @@ -1453,15 +1178,35 @@ impl IntoClass for [T; N] { // we really need impl type ClassIter = std::iter::FlatMap, T::ClassIter, fn(T) -> T::ClassIter>; - fn classes(self) -> Self::ClassIter { - self.into_iter().flat_map(IntoClass::classes) + fn into_class(self) -> Self::ClassIter { + self.into_iter().flat_map(IntoClass::into_class) + } +} + +impl<'a> IntoClass for &'a [&'static str] { + // we really need impl + type ClassIter = std::iter::Map< + std::iter::Copied>, + fn(&'static str) -> CowStr, + >; + fn into_class(self) -> Self::ClassIter { + self.iter().copied().map(Cow::from) + } +} + +impl IntoClass for Option { + // we really need impl + type ClassIter = + std::iter::FlatMap, T::ClassIter, fn(T) -> T::ClassIter>; + fn into_class(self) -> Self::ClassIter { + self.into_iter().flat_map(IntoClass::into_class) } } impl IntoClass for Vec { type ClassIter = std::iter::FlatMap, T::ClassIter, fn(T) -> T::ClassIter>; - fn classes(self) -> Self::ClassIter { - self.into_iter().flat_map(IntoClass::classes) + fn into_class(self) -> Self::ClassIter { + self.into_iter().flat_map(IntoClass::into_class) } } @@ -1478,7 +1223,7 @@ impl IntoClass for Vec { // but an alternative would be multiple class invocations with different types impl IntoClass for (A, B) { type ClassIter = std::iter::Chain; - fn classes(self) -> Self::ClassIter { - self.0.classes().chain(self.1.classes()) + fn into_class(self) -> Self::ClassIter { + self.0.into_class().chain(self.1.into_class()) } } diff --git a/crates/xilem_html/src/event/events.rs b/crates/xilem_html/src/event/events.rs index 933e25da4..6b5f34868 100644 --- a/crates/xilem_html/src/event/events.rs +++ b/crates/xilem_html/src/event/events.rs @@ -1,273 +1,273 @@ -//! Macros to generate all the different html events -//! -macro_rules! events { - () => {}; - (($ty_name:ident, $builder_name:ident, $name:literal, $web_sys_ty:ty), $($rest:tt)*) => { - event!($ty_name, $builder_name, $name, $web_sys_ty); - events!($($rest)*); - }; -} +// //! Macros to generate all the different html events +// //! +// macro_rules! events { +// () => {}; +// (($ty_name:ident, $builder_name:ident, $name:literal, $web_sys_ty:ty), $($rest:tt)*) => { +// event!($ty_name, $builder_name, $name, $web_sys_ty); +// events!($($rest)*); +// }; +// } -macro_rules! event { - ($ty_name:ident, $builder_name:ident, $name:literal, $web_sys_ty:ty) => { - /// A view that listens for the - #[doc = concat!("`", $name, "`")] - /// event. - pub struct $ty_name - where - V: crate::view::View, - F: Fn(&mut T, &$crate::Event<$web_sys_ty, V::Element>) -> OA, - V::Element: 'static, - OA: $crate::event::OptionalAction, - { - inner: crate::OnEvent<$web_sys_ty, V, F>, - data: std::marker::PhantomData, - action: std::marker::PhantomData, - optional_action: std::marker::PhantomData, - } +// macro_rules! event { +// ($ty_name:ident, $builder_name:ident, $name:literal, $web_sys_ty:ty) => { +// /// A view that listens for the +// #[doc = concat!("`", $name, "`")] +// /// event. +// pub struct $ty_name +// where +// V: crate::view::View, +// F: Fn(&mut T, &$crate::Event<$web_sys_ty, V::Element>) -> OA, +// V::Element: 'static, +// OA: $crate::event::OptionalAction, +// { +// inner: crate::OnEvent<$web_sys_ty, V, F>, +// data: std::marker::PhantomData, +// action: std::marker::PhantomData, +// optional_action: std::marker::PhantomData, +// } - impl $ty_name - where - V: crate::view::View, - F: Fn(&mut T, &$crate::Event<$web_sys_ty, V::Element>) -> OA, - V::Element: 'static, - OA: $crate::event::OptionalAction, - { - /// Whether the event handler should be passive. (default = `true`) - /// - /// Passive event handlers can't prevent the browser's default action from - /// running (otherwise possible with `event.prevent_default()`), which - /// restricts what they can be used for, but reduces overhead. - pub fn passive(mut self, value: bool) -> Self { - self.inner.passive = value; - self - } - } +// impl $ty_name +// where +// V: crate::view::View, +// F: Fn(&mut T, &$crate::Event<$web_sys_ty, V::Element>) -> OA, +// V::Element: 'static, +// OA: $crate::event::OptionalAction, +// { +// /// Whether the event handler should be passive. (default = `true`) +// /// +// /// Passive event handlers can't prevent the browser's default action from +// /// running (otherwise possible with `event.prevent_default()`), which +// /// restricts what they can be used for, but reduces overhead. +// pub fn passive(mut self, value: bool) -> Self { +// self.inner.passive = value; +// self +// } +// } - /// Builder for the - #[doc = concat!("`", $name, "`")] - /// event listener. - pub fn $builder_name(child: V, callback: F) -> $ty_name - where - V: crate::view::View, - F: Fn(&mut T, &$crate::Event<$web_sys_ty, V::Element>) -> OA, - V::Element: 'static, - OA: $crate::event::OptionalAction, - { - $ty_name { - inner: crate::on_event($name, child, callback), - data: std::marker::PhantomData, - action: std::marker::PhantomData, - optional_action: std::marker::PhantomData, - } - } +// /// Builder for the +// #[doc = concat!("`", $name, "`")] +// /// event listener. +// pub fn $builder_name(child: V, callback: F) -> $ty_name +// where +// V: crate::view::View, +// F: Fn(&mut T, &$crate::Event<$web_sys_ty, V::Element>) -> OA, +// V::Element: 'static, +// OA: $crate::event::OptionalAction, +// { +// $ty_name { +// inner: crate::on_event($name, child, callback), +// data: std::marker::PhantomData, +// action: std::marker::PhantomData, +// optional_action: std::marker::PhantomData, +// } +// } - impl crate::view::ViewMarker for $ty_name - where - V: crate::view::View, - F: Fn(&mut T, &$crate::Event<$web_sys_ty, V::Element>) -> OA, - V::Element: 'static, - OA: $crate::event::OptionalAction, - { - } +// impl crate::view::ViewMarker for $ty_name +// where +// V: crate::view::View, +// F: Fn(&mut T, &$crate::Event<$web_sys_ty, V::Element>) -> OA, +// V::Element: 'static, +// OA: $crate::event::OptionalAction, +// { +// } - impl crate::view::View for $ty_name - where - V: crate::view::View, - F: Fn(&mut T, &$crate::Event<$web_sys_ty, V::Element>) -> OA, - V::Element: 'static, - OA: $crate::event::OptionalAction, - { - type State = crate::event::OnEventState; - type Element = V::Element; +// impl crate::view::View for $ty_name +// where +// V: crate::view::View, +// F: Fn(&mut T, &$crate::Event<$web_sys_ty, V::Element>) -> OA, +// V::Element: 'static, +// OA: $crate::event::OptionalAction, +// { +// type State = crate::event::OnEventState; +// type Element = V::Element; - fn build( - &self, - cx: &mut crate::context::Cx, - ) -> (xilem_core::Id, Self::State, Self::Element) { - self.inner.build(cx) - } +// fn build( +// &self, +// cx: &mut crate::context::Cx, +// ) -> (xilem_core::Id, Self::State, Self::Element) { +// self.inner.build(cx) +// } - fn rebuild( - &self, - cx: &mut crate::context::Cx, - prev: &Self, - id: &mut xilem_core::Id, - state: &mut Self::State, - element: &mut Self::Element, - ) -> crate::ChangeFlags { - self.inner.rebuild(cx, &prev.inner, id, state, element) - } +// fn rebuild( +// &self, +// cx: &mut crate::context::Cx, +// prev: &Self, +// id: &mut xilem_core::Id, +// state: &mut Self::State, +// element: &mut Self::Element, +// ) -> crate::ChangeFlags { +// self.inner.rebuild(cx, &prev.inner, id, state, element) +// } - fn message( - &self, - id_path: &[xilem_core::Id], - state: &mut Self::State, - message: Box, - app_state: &mut T, - ) -> xilem_core::MessageResult { - self.inner.message(id_path, state, message, app_state) - } - } - }; -} +// fn message( +// &self, +// id_path: &[xilem_core::Id], +// state: &mut Self::State, +// message: Box, +// app_state: &mut T, +// ) -> xilem_core::MessageResult { +// self.inner.message(id_path, state, message, app_state) +// } +// } +// }; +// } -// event list from -// https://html.spec.whatwg.org/multipage/webappapis.html#idl-definitions -// -// I didn't include the events on the window, since we aren't attaching -// any events to the window in xilem_html +// // event list from +// // https://html.spec.whatwg.org/multipage/webappapis.html#idl-definitions +// // +// // I didn't include the events on the window, since we aren't attaching +// // any events to the window in xilem_html -events!( - (OnAbort, on_abort, "abort", web_sys::Event), - (OnAuxClick, on_auxclick, "auxclick", web_sys::PointerEvent), - ( - OnBeforeInput, - on_beforeinput, - "beforeinput", - web_sys::InputEvent - ), - (OnBeforeMatch, on_beforematch, "beforematch", web_sys::Event), - ( - OnBeforeToggle, - on_beforetoggle, - "beforetoggle", - web_sys::Event - ), - (OnBlur, on_blur, "blur", web_sys::FocusEvent), - (OnCancel, on_cancel, "cancel", web_sys::Event), - (OnCanPlay, on_canplay, "canplay", web_sys::Event), - ( - OnCanPlayThrough, - on_canplaythrough, - "canplaythrough", - web_sys::Event - ), - (OnChange, on_change, "change", web_sys::Event), - (OnClick, on_click, "click", web_sys::MouseEvent), - (OnClose, on_close, "close", web_sys::Event), - (OnContextLost, on_contextlost, "contextlost", web_sys::Event), - ( - OnContextMenu, - on_contextmenu, - "contextmenu", - web_sys::PointerEvent - ), - ( - OnContextRestored, - on_contextrestored, - "contextrestored", - web_sys::Event - ), - (OnCopy, on_copy, "copy", web_sys::Event), - (OnCueChange, on_cuechange, "cuechange", web_sys::Event), - (OnCut, on_cut, "cut", web_sys::Event), - (OnDblClick, on_dblclick, "dblclick", web_sys::MouseEvent), - (OnDrag, on_drag, "drag", web_sys::Event), - (OnDragEnd, on_dragend, "dragend", web_sys::Event), - (OnDragEnter, on_dragenter, "dragenter", web_sys::Event), - (OnDragLeave, on_dragleave, "dragleave", web_sys::Event), - (OnDragOver, on_dragover, "dragover", web_sys::Event), - (OnDragStart, on_dragstart, "dragstart", web_sys::Event), - (OnDrop, on_drop, "drop", web_sys::Event), - ( - OnDurationChange, - on_durationchange, - "durationchange", - web_sys::Event - ), - (OnEmptied, on_emptied, "emptied", web_sys::Event), - (OnEnded, on_ended, "ended", web_sys::Event), - (OnError, on_error, "error", web_sys::Event), - (OnFocus, on_focus, "focus", web_sys::FocusEvent), - (OnFocusIn, on_focusin, "focusin", web_sys::FocusEvent), - (OnFocusOut, on_focusout, "focusout", web_sys::FocusEvent), - (OnFormData, on_formdata, "formdata", web_sys::Event), - (OnInput, on_input, "input", web_sys::InputEvent), - (OnInvalid, on_invalid, "invalid", web_sys::Event), - (OnKeyDown, on_keydown, "keydown", web_sys::KeyboardEvent), - (OnKeyUp, on_keyup, "keyup", web_sys::KeyboardEvent), - (OnLoad, on_load, "load", web_sys::Event), - (OnLoadedData, on_loadeddata, "loadeddata", web_sys::Event), - ( - OnLoadedMetadata, - on_loadedmetadata, - "loadedmetadata", - web_sys::Event - ), - (OnLoadStart, on_loadstart, "loadstart", web_sys::Event), - (OnMouseDown, on_mousedown, "mousedown", web_sys::MouseEvent), - ( - OnMouseEnter, - on_mouseenter, - "mouseenter", - web_sys::MouseEvent - ), - ( - OnMouseLeave, - on_mouseleave, - "mouseleave", - web_sys::MouseEvent - ), - (OnMouseMove, on_mousemove, "mousemove", web_sys::MouseEvent), - (OnMouseOut, on_mouseout, "mouseout", web_sys::MouseEvent), - (OnMouseOver, on_mouseover, "mouseover", web_sys::MouseEvent), - (OnMouseUp, on_mouseup, "mouseup", web_sys::MouseEvent), - (OnPaste, on_paste, "paste", web_sys::Event), - (OnPause, on_pause, "pause", web_sys::Event), - (OnPlay, on_play, "play", web_sys::Event), - (OnPlaying, on_playing, "playing", web_sys::Event), - (OnProgress, on_progress, "progress", web_sys::Event), - (OnRateChange, on_ratechange, "ratechange", web_sys::Event), - (OnReset, on_reset, "reset", web_sys::Event), - (OnResize, on_resize, "resize", web_sys::Event), - (OnScroll, on_scroll, "scroll", web_sys::Event), - (OnScrollEnd, on_scrollend, "scrollend", web_sys::Event), - ( - OnSecurityPolicyViolation, - on_securitypolicyviolation, - "securitypolicyviolation", - web_sys::Event - ), - (OnSeeked, on_seeked, "seeked", web_sys::Event), - (OnSeeking, on_seeking, "seeking", web_sys::Event), - (OnSelect, on_select, "select", web_sys::Event), - (OnSlotChange, on_slotchange, "slotchange", web_sys::Event), - (OnStalled, on_stalled, "stalled", web_sys::Event), - (OnSubmit, on_submit, "submit", web_sys::Event), - (OnSuspend, on_suspend, "suspend", web_sys::Event), - (OnTimeUpdate, on_timeupdate, "timeupdate", web_sys::Event), - (OnToggle, on_toggle, "toggle", web_sys::Event), - ( - OnVolumeChange, - on_volumechange, - "volumechange", - web_sys::Event - ), - (OnWaiting, on_waiting, "waiting", web_sys::Event), - ( - OnWebkitAnimationEnd, - on_webkitanimationend, - "webkitanimationend", - web_sys::Event - ), - ( - OnWebkitAnimationIteration, - on_webkitanimationiteration, - "webkitanimationiteration", - web_sys::Event - ), - ( - OnWebkitAnimationStart, - on_webkitanimationstart, - "webkitanimationstart", - web_sys::Event - ), - ( - OnWebkitTransitionEnd, - on_webkittransitionend, - "webkittransitionend", - web_sys::Event - ), - (OnWheel, on_wheel, "wheel", web_sys::WheelEvent), -); +// events!( +// (OnAbort, on_abort, "abort", web_sys::Event), +// (OnAuxClick, on_auxclick, "auxclick", web_sys::PointerEvent), +// ( +// OnBeforeInput, +// on_beforeinput, +// "beforeinput", +// web_sys::InputEvent +// ), +// (OnBeforeMatch, on_beforematch, "beforematch", web_sys::Event), +// ( +// OnBeforeToggle, +// on_beforetoggle, +// "beforetoggle", +// web_sys::Event +// ), +// (OnBlur, on_blur, "blur", web_sys::FocusEvent), +// (OnCancel, on_cancel, "cancel", web_sys::Event), +// (OnCanPlay, on_canplay, "canplay", web_sys::Event), +// ( +// OnCanPlayThrough, +// on_canplaythrough, +// "canplaythrough", +// web_sys::Event +// ), +// (OnChange, on_change, "change", web_sys::Event), +// (OnClick, on_click, "click", web_sys::MouseEvent), +// (OnClose, on_close, "close", web_sys::Event), +// (OnContextLost, on_contextlost, "contextlost", web_sys::Event), +// ( +// OnContextMenu, +// on_contextmenu, +// "contextmenu", +// web_sys::PointerEvent +// ), +// ( +// OnContextRestored, +// on_contextrestored, +// "contextrestored", +// web_sys::Event +// ), +// (OnCopy, on_copy, "copy", web_sys::Event), +// (OnCueChange, on_cuechange, "cuechange", web_sys::Event), +// (OnCut, on_cut, "cut", web_sys::Event), +// (OnDblClick, on_dblclick, "dblclick", web_sys::MouseEvent), +// (OnDrag, on_drag, "drag", web_sys::Event), +// (OnDragEnd, on_dragend, "dragend", web_sys::Event), +// (OnDragEnter, on_dragenter, "dragenter", web_sys::Event), +// (OnDragLeave, on_dragleave, "dragleave", web_sys::Event), +// (OnDragOver, on_dragover, "dragover", web_sys::Event), +// (OnDragStart, on_dragstart, "dragstart", web_sys::Event), +// (OnDrop, on_drop, "drop", web_sys::Event), +// ( +// OnDurationChange, +// on_durationchange, +// "durationchange", +// web_sys::Event +// ), +// (OnEmptied, on_emptied, "emptied", web_sys::Event), +// (OnEnded, on_ended, "ended", web_sys::Event), +// (OnError, on_error, "error", web_sys::Event), +// (OnFocus, on_focus, "focus", web_sys::FocusEvent), +// (OnFocusIn, on_focusin, "focusin", web_sys::FocusEvent), +// (OnFocusOut, on_focusout, "focusout", web_sys::FocusEvent), +// (OnFormData, on_formdata, "formdata", web_sys::Event), +// (OnInput, on_input, "input", web_sys::InputEvent), +// (OnInvalid, on_invalid, "invalid", web_sys::Event), +// (OnKeyDown, on_keydown, "keydown", web_sys::KeyboardEvent), +// (OnKeyUp, on_keyup, "keyup", web_sys::KeyboardEvent), +// (OnLoad, on_load, "load", web_sys::Event), +// (OnLoadedData, on_loadeddata, "loadeddata", web_sys::Event), +// ( +// OnLoadedMetadata, +// on_loadedmetadata, +// "loadedmetadata", +// web_sys::Event +// ), +// (OnLoadStart, on_loadstart, "loadstart", web_sys::Event), +// (OnMouseDown, on_mousedown, "mousedown", web_sys::MouseEvent), +// ( +// OnMouseEnter, +// on_mouseenter, +// "mouseenter", +// web_sys::MouseEvent +// ), +// ( +// OnMouseLeave, +// on_mouseleave, +// "mouseleave", +// web_sys::MouseEvent +// ), +// (OnMouseMove, on_mousemove, "mousemove", web_sys::MouseEvent), +// (OnMouseOut, on_mouseout, "mouseout", web_sys::MouseEvent), +// (OnMouseOver, on_mouseover, "mouseover", web_sys::MouseEvent), +// (OnMouseUp, on_mouseup, "mouseup", web_sys::MouseEvent), +// (OnPaste, on_paste, "paste", web_sys::Event), +// (OnPause, on_pause, "pause", web_sys::Event), +// (OnPlay, on_play, "play", web_sys::Event), +// (OnPlaying, on_playing, "playing", web_sys::Event), +// (OnProgress, on_progress, "progress", web_sys::Event), +// (OnRateChange, on_ratechange, "ratechange", web_sys::Event), +// (OnReset, on_reset, "reset", web_sys::Event), +// (OnResize, on_resize, "resize", web_sys::Event), +// (OnScroll, on_scroll, "scroll", web_sys::Event), +// (OnScrollEnd, on_scrollend, "scrollend", web_sys::Event), +// ( +// OnSecurityPolicyViolation, +// on_securitypolicyviolation, +// "securitypolicyviolation", +// web_sys::Event +// ), +// (OnSeeked, on_seeked, "seeked", web_sys::Event), +// (OnSeeking, on_seeking, "seeking", web_sys::Event), +// (OnSelect, on_select, "select", web_sys::Event), +// (OnSlotChange, on_slotchange, "slotchange", web_sys::Event), +// (OnStalled, on_stalled, "stalled", web_sys::Event), +// (OnSubmit, on_submit, "submit", web_sys::Event), +// (OnSuspend, on_suspend, "suspend", web_sys::Event), +// (OnTimeUpdate, on_timeupdate, "timeupdate", web_sys::Event), +// (OnToggle, on_toggle, "toggle", web_sys::Event), +// ( +// OnVolumeChange, +// on_volumechange, +// "volumechange", +// web_sys::Event +// ), +// (OnWaiting, on_waiting, "waiting", web_sys::Event), +// ( +// OnWebkitAnimationEnd, +// on_webkitanimationend, +// "webkitanimationend", +// web_sys::Event +// ), +// ( +// OnWebkitAnimationIteration, +// on_webkitanimationiteration, +// "webkitanimationiteration", +// web_sys::Event +// ), +// ( +// OnWebkitAnimationStart, +// on_webkitanimationstart, +// "webkitanimationstart", +// web_sys::Event +// ), +// ( +// OnWebkitTransitionEnd, +// on_webkittransitionend, +// "webkittransitionend", +// web_sys::Event +// ), +// (OnWheel, on_wheel, "wheel", web_sys::WheelEvent), +// ); diff --git a/crates/xilem_html/src/event/mod.rs b/crates/xilem_html/src/event/mod.rs index 26144449c..071ca892c 100644 --- a/crates/xilem_html/src/event/mod.rs +++ b/crates/xilem_html/src/event/mod.rs @@ -6,7 +6,8 @@ pub mod events; use std::{any::Any, marker::PhantomData, ops::Deref}; -use gloo::events::{EventListener, EventListenerOptions}; +use gloo::events::EventListener; +pub use gloo::events::EventListenerOptions; use wasm_bindgen::{JsCast, UnwrapThrowExt}; use xilem_core::{Id, MessageResult}; diff --git a/crates/xilem_html/src/lib.rs b/crates/xilem_html/src/lib.rs index b935c8747..3e843097a 100644 --- a/crates/xilem_html/src/lib.rs +++ b/crates/xilem_html/src/lib.rs @@ -29,7 +29,9 @@ pub use element::elements; pub use element::{element, AttributeValue, Element, ElementState}; #[cfg(feature = "typed")] pub use event::events; -pub use event::{on_event, Action, Event, OnEvent, OnEventState, OptionalAction}; +pub use event::{ + on_event, Action, Event, EventListenerOptions, OnEvent, OnEventState, OptionalAction, +}; pub use one_of::{OneOf2, OneOf3, OneOf4, OneOf5, OneOf6, OneOf7, OneOf8}; pub use view::{ memoize, s, Adapt, AdaptState, AdaptThunk, AnyView, Memoize, Pod, View, ViewMarker, diff --git a/crates/xilem_html/src/view_ext.rs b/crates/xilem_html/src/view_ext.rs index f62a46a11..21c6a650a 100644 --- a/crates/xilem_html/src/view_ext.rs +++ b/crates/xilem_html/src/view_ext.rs @@ -1,68 +1,64 @@ // Copyright 2023 the Druid Authors. // SPDX-License-Identifier: Apache-2.0 -use std::borrow::Cow; - -use crate::{ - class::Class, event::OptionalAction, events, view::View, Adapt, AdaptState, AdaptThunk, Event, -}; +use crate::{view::View, Adapt, AdaptState, AdaptThunk}; /// A trait that makes it possible to attach event listeners and more to views /// in the continuation style. pub trait ViewExt: View + Sized { - /// Add an `onclick` event listener. - fn on_click< - OA: OptionalAction, - F: Fn(&mut T, &Event) -> OA, - >( - self, - f: F, - ) -> events::OnClick { - events::on_click(self, f) - } + // /// Add an `onclick` event listener. + // fn on_click< + // OA: OptionalAction, + // F: Fn(&mut T, &Event) -> OA, + // >( + // self, + // f: F, + // ) -> events::OnClick { + // events::on_click(self, f) + // } - /// Add an `ondblclick` event listener. - fn on_dblclick< - OA: OptionalAction, - F: Fn(&mut T, &Event) -> OA, - >( - self, - f: F, - ) -> events::OnDblClick { - events::on_dblclick(self, f) - } + // /// Add an `ondblclick` event listener. + // fn on_dblclick< + // OA: OptionalAction, + // F: Fn(&mut T, &Event) -> OA, + // >( + // self, + // f: F, + // ) -> events::OnDblClick { + // events::on_dblclick(self, f) + // } - /// Add an `oninput` event listener. - fn on_input< - OA: OptionalAction, - F: Fn(&mut T, &Event) -> OA, - >( - self, - f: F, - ) -> events::OnInput { - events::on_input(self, f) - } + // /// Add an `oninput` event listener. + // fn on_input< + // OA: OptionalAction, + // F: Fn(&mut T, &Event) -> OA, + // >( + // self, + // f: F, + // ) -> events::OnInput { + // events::on_input(self, f) + // } - /// Add an `onkeydown` event listener. - fn on_keydown< - OA: OptionalAction, - F: Fn(&mut T, &Event) -> OA, - >( - self, - f: F, - ) -> events::OnKeyDown { - events::on_keydown(self, f) - } + // /// Add an `onkeydown` event listener. + // fn on_keydown< + // OA: OptionalAction, + // F: Fn(&mut T, &Event) -> OA, + // >( + // self, + // f: F, + // ) -> events::OnKeyDown { + // events::on_keydown(self, f) + // } - fn on_blur< - OA: OptionalAction, - F: Fn(&mut T, &Event) -> OA, - >( - self, - f: F, - ) -> events::OnBlur { - events::on_blur(self, f) - } + // fn on_blur< + // OA: OptionalAction, + // F: Fn(&mut T, &Event) -> OA, + // >( + // self, + // f: F, + // ) -> events::OnBlur { + // events::on_blur(self, f) + // } fn adapt(self, f: F) -> Adapt where @@ -78,10 +74,10 @@ pub trait ViewExt: View + Sized { AdaptState::new(f, self) } - /// Apply a CSS class to the child view. - fn class(self, class: impl Into>) -> Class { - crate::class::class(self, class) - } + // /// Apply a CSS class to the child view. + // fn class(self, class: impl Into>) -> Class { + // crate::class::class(self, class) + // } } impl> ViewExt for V {} diff --git a/crates/xilem_html/web_examples/counter/src/main.rs b/crates/xilem_html/web_examples/counter/src/main.rs index d2ee75a45..5fb340302 100644 --- a/crates/xilem_html/web_examples/counter/src/main.rs +++ b/crates/xilem_html/web_examples/counter/src/main.rs @@ -1,9 +1,4 @@ -use xilem_html::{ - document_body, elements as el, - elements::Element, - events::{self as evt}, - App, Event, View, ViewExt, ViewMarker, -}; +use xilem_html::{document_body, elements as el, elements::Element, App, Event, View, ViewMarker}; #[derive(Default)] struct AppState { @@ -40,23 +35,16 @@ impl AppState { } /// You can create functions that generate views. -fn btn( - label: &'static str, - click_fn: F, -) -> evt::OnClick, F, ()> +fn btn(label: &'static str, click_fn: F) -> el::Button where - F: Fn(&mut AppState, &Event), + A: 'static, + F: Fn(&mut AppState, Event) + 'static, { el::button(label).on_click(click_fn) } -#[allow(clippy::needless_pass_by_ref_mut)] fn app_logic(state: &mut AppState) -> impl View + ViewMarker { - // TODO remove - let classes: Vec<_> = (0..state.clicks).map(|i| format!("click-{i}")).collect(); - let d = el::div(( - el::span(format!("clicked {} times", state.clicks)).classes(state.class), - el::br(()), + el::div(( btn("+1 click", |state, _| state.increment()), btn("-1 click", |state, _| state.decrement()), btn("reset clicks", |state, _| state.reset()), @@ -65,13 +53,6 @@ fn app_logic(state: &mut AppState) -> impl View + ViewMarker { el::br(()), state.text.clone(), )) - .classes(classes); - - if state.clicks > 10 { - d.attr("data-custom", format!("click-{}", state.clicks)) - } else { - d - } } pub fn main() { diff --git a/crates/xilem_html/web_examples/todomvc/src/main.rs b/crates/xilem_html/web_examples/todomvc/src/main.rs index 27ffb3c49..be2d5b5c2 100644 --- a/crates/xilem_html/web_examples/todomvc/src/main.rs +++ b/crates/xilem_html/web_examples/todomvc/src/main.rs @@ -4,8 +4,8 @@ use state::{AppState, Filter, Todo}; use xilem_html::{ elements::{self as el, Element}, - events::on_click, - get_element_by_id, Action, Adapt, App, MessageResult, View, ViewExt, ViewMarker, + get_element_by_id, Action, Adapt, App, Event, EventListenerOptions, MessageResult, View, + ViewMarker, }; // All of these actions arise from within a `Todo`, but we need access to the full state to reduce @@ -20,12 +20,12 @@ enum TodoAction { impl Action for TodoAction {} fn todo_item(todo: &mut Todo, editing: bool) -> impl View + ViewMarker { - let mut class = String::new(); + let mut classes = Vec::new(); if todo.completed { - class.push_str(" completed"); + classes.push("completed"); } if editing { - class.push_str(" editing"); + classes.push("editing"); } let input = el::input(()) .attr("class", "toggle") @@ -43,12 +43,11 @@ fn todo_item(todo: &mut Todo, editing: bool) -> impl View + Vi .attr("class", "destroy") .on_click(|state: &mut Todo, _| TodoAction::Destroy(state.id)), )) - .classes("view"), - // .attr("class", "view"), + .class("view"), el::input(()) .attr("value", todo.title_editing.clone()) .attr("class", "edit") - .on_keydown(|state: &mut Todo, evt| { + .on_keydown(|state: &mut Todo, evt: Event| { let key = evt.key(); if key == "Enter" { state.save_editing(); @@ -59,15 +58,16 @@ fn todo_item(todo: &mut Todo, editing: bool) -> impl View + Vi None } }) - .on_input(|state: &mut Todo, evt| { - state.title_editing.clear(); - state.title_editing.push_str(&evt.target().value()); - evt.prevent_default(); - }) - .passive(false) - .on_blur(|_, _| TodoAction::CancelEditing), + // TODO is it possible with the EventHandler trait to eliminate the explicit typings for the event attribute (which is IMO quite an ergonomic regression) + .on_input( + |state: &mut Todo, evt: Event| { + state.title_editing.clear(); + state.title_editing.push_str(&evt.target().value()); + evt.prevent_default(); + }, + ), )) - .attr("class", class) + .class(classes) } fn footer_view(state: &mut AppState, should_display: bool) -> impl View + ViewMarker { @@ -78,7 +78,7 @@ fn footer_view(state: &mut AppState, should_display: bool) -> impl View 0).then(|| { - on_click( + Element::on_click( el::button("Clear completed").attr("class", "clear-completed"), |state: &mut AppState, _| { state.todos.retain(|todo| !todo.completed); @@ -95,28 +95,28 @@ fn footer_view(state: &mut AppState, should_display: bool) -> impl View impl View { el::header(( el::h1("TODOs"), input - .on_keydown(|state: &mut AppState, evt| { + .on_keydown(|state: &mut AppState, evt: Event| { if evt.key() == "Enter" { state.create_todo(); } }) - .on_input(|state: &mut AppState, evt| { + .on_input_with_options(|state: &mut AppState, evt: Event| { state.update_new_todo(&evt.target().value()); evt.prevent_default(); - }) - .passive(false), + }, EventListenerOptions::enable_prevent_default()) )) .attr("class", "header"), main,