From 9889977c9bd3a49cc2be7d4e5be84678527f0e14 Mon Sep 17 00:00:00 2001 From: Philipp Mildenberger Date: Tue, 8 Aug 2023 01:28:49 +0200 Subject: [PATCH] Implemented all HTML DOM interface traits (most without explicit attribute methods yet) * Macro refactor * Disabled/Deleted previous generated elements * Disabled/Commented out event views, (implemented some of them on the Element DOM interface trait * Lots of smaller fixes --- crates/xilem_html/src/element/elements.rs | 868 +++++++++--------- crates/xilem_html/src/event/events.rs | 526 +++++------ crates/xilem_html/src/event/mod.rs | 3 +- crates/xilem_html/src/lib.rs | 4 +- crates/xilem_html/src/view_ext.rs | 112 ++- .../web_examples/counter/src/main.rs | 23 +- .../web_examples/todomvc/src/main.rs | 96 +- 7 files changed, 800 insertions(+), 832 deletions(-) diff --git a/crates/xilem_html/src/element/elements.rs b/crates/xilem_html/src/element/elements.rs index 51622c93d..065bfdb10 100644 --- a/crates/xilem_html/src/element/elements.rs +++ b/crates/xilem_html/src/element/elements.rs @@ -13,6 +13,7 @@ use std::{ use gloo::events::{EventListenerOptions, EventListenerPhase}; use wasm_bindgen::JsCast; +use super::{remove_attribute, set_attribute}; use crate::{event::EventMsg, Event, Pod}; use wasm_bindgen::UnwrapThrowExt; @@ -23,233 +24,6 @@ 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 { @@ -477,7 +251,10 @@ impl Attrs { } } -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 +275,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 +307,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 +318,9 @@ impl A> EventHandler for F { } } -struct EventListener { +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 +333,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 +460,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 +482,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 +503,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) @@ -904,6 +688,29 @@ pub trait Node { fn node_name(&self) -> &str; } +// TODO add also something like `set_on_click(&mut self)`? +macro_rules! def_event_attr { + ($name: ident, $name_with_options: ident, $event: ty) => { + fn $name(self, handler: EH) -> Self + where + T: 'static, + A: 'static, + OA: crate::OptionalAction + 'static, + EH: EventHandler> + 'static; + + fn $name_with_options(self, handler: EH, options: EventListenerOptions) -> Self + where + T: 'static, + A: 'static, + OA: crate::OptionalAction + 'static, + EH: EventHandler> + 'static; + }; +} + +// TODO implementing all these methods directly on the elements themselves results in a quite big macro expanded codegen I think (need to test/check compile (parse?) times), +// An idea to reduce this generated code, is implementing all the methods as default impl directly in the traits +// and expose the raw attrs as e.g. Element::raw_attrs_mut(&mut self) -> &mut Attrs + /// 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 @@ -915,94 +722,160 @@ pub trait Node { 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 class(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? + /// 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>(self, key: K, value: V) -> 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); - // TODO generate all this event listener boilerplate with macros - fn on_click(self, handler: EH) -> Self - where - T: 'static, - A: 'static, - EH: EventHandler> + 'static; - - fn on_click_with_options(self, handler: EH, options: EventListenerOptions) -> Self - where - T: 'static, - A: 'static, - EH: EventHandler> + 'static; - - fn on_scroll(self, handler: EH) -> Self - where - Self: Sized, - T: 'static, - A: 'static, - EH: EventHandler> + 'static; - - fn on_scroll_with_options(self, handler: EH, options: EventListenerOptions) -> Self - where - T: 'static, - A: 'static, - EH: EventHandler> + 'static; + def_event_attr!(on_click, on_click_with_options, web_sys::MouseEvent); + def_event_attr!(on_dblclick, on_dblclick_with_options, web_sys::MouseEvent); + def_event_attr!(on_scroll, on_scroll_with_options, web_sys::Event); + def_event_attr!(on_scrollend, on_scrollend_with_options, web_sys::Event); + def_event_attr!(on_keydown, on_keydown_with_options, web_sys::KeyboardEvent); + def_event_attr!(on_keyup, on_keyup_with_options, web_sys::KeyboardEvent); + def_event_attr!(on_focus, on_focus_with_options, web_sys::FocusEvent); + def_event_attr!(on_focusin, on_focusin_with_options, web_sys::FocusEvent); + def_event_attr!(on_focusout, on_focusout_with_options, web_sys::FocusEvent); + def_event_attr!(on_blur, on_blur_with_options, web_sys::FocusEvent); + // not per se an attribute defined on every element, + // but for all elements that have the attribute contenteditable enabled this is allowed and of course for the input element + def_event_attr!(on_input, on_input_with_options, web_sys::InputEvent); // 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 { + 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); + }, + 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(); + 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); + 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::StringBTreeSet(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::StringBTreeSet(BTreeSet::from_iter(class)); } None => { attrs.insert( "class".into(), - AttributeValue::StringBTreeSet(BTreeSet::from_iter(classes)), + AttributeValue::StringBTreeSet(BTreeSet::from_iter(class)), ); } }; @@ -1011,7 +884,7 @@ fn add_class(attrs: &mut Attrs, class: C) { macro_rules! impl_simple_attr { ($name:ident, $setter_name: ident, $ty: ty, $el: ident) => { #[inline(always)] - fn $name(mut self, $name: $ty) -> $el { + fn $name(mut self, $name: $ty) -> $el { self.attrs.insert_attr(stringify!($name), $name); self } @@ -1023,16 +896,57 @@ macro_rules! impl_simple_attr { }; } +macro_rules! impl_event_attr { + ($name: ident, $name_with_options: ident, $event: ty, $event_name: expr, $concrete_dom_interface: ident, $ty_name: ident) => { + fn $name(self, handler: EH) -> $ty_name + where + T_: 'static, + A_: 'static, + OA: crate::OptionalAction + 'static, + EH: EventHandler< + T_, + OA, + A_, + crate::Event<$event, web_sys::$concrete_dom_interface>, // V::Element, but this results in better docs + > + 'static, + { + self.$name_with_options(handler, EventListenerOptions::default()) + } + + fn $name_with_options( + mut self, + handler: EH, + options: EventListenerOptions, + ) -> $ty_name + where + T_: 'static, + A_: 'static, + OA: crate::OptionalAction + 'static, + EH: EventHandler< + T_, + OA, + A_, + crate::Event<$event, web_sys::$concrete_dom_interface>, // V::Element, but this results in better docs + > + 'static, + { + let listener = EventListener::new($event_name, handler, options); + self.event_listeners + .push(DynamicEventListener::new(listener)); + self + } + }; +} + // Since these methods are used for all HTML elements, // it might make sense to add an extra inner impl function if possible // (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 { + fn class(mut self, class: C) -> Self { add_class(&mut self.attrs, class); self } @@ -1049,7 +963,7 @@ macro_rules! impl_element { mut self, key: K, value: V, - ) -> $ty_name { + ) -> $ty_name { self.attrs.insert_attr(key, value); self } @@ -1058,71 +972,108 @@ macro_rules! impl_element { 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()) - } + impl_event_attr!( + on_click, + on_click_with_options, + web_sys::MouseEvent, + "click", + $concrete_dom_interface, + $ty_name + ); - 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 - } + impl_event_attr!( + on_dblclick, + on_dblclick_with_options, + web_sys::MouseEvent, + "dblclick", + $concrete_dom_interface, + $ty_name + ); - 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()) - } + // TODO rest of the mouse events - 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 - { - let listener = EventListener::new("scroll", handler, options); - self.event_listeners - .push(DynamicEventListener::new(listener)); - self - } + // Keyboard Events + + impl_event_attr!( + on_keydown, + on_keydown_with_options, + web_sys::KeyboardEvent, + "keydown", + $concrete_dom_interface, + $ty_name + ); + + impl_event_attr!( + on_keyup, + on_keyup_with_options, + web_sys::KeyboardEvent, + "keyup", + $concrete_dom_interface, + $ty_name + ); + + // Scroll Events + + impl_event_attr!( + on_scroll, + on_scroll_with_options, + web_sys::Event, + "scroll", + $concrete_dom_interface, + $ty_name + ); + + impl_event_attr!( + on_scrollend, + on_scrollend_with_options, + web_sys::Event, + "scrollend", + $concrete_dom_interface, + $ty_name + ); + + // Focus events + impl_event_attr!( + on_focus, + on_focus_with_options, + web_sys::FocusEvent, + "focus", + $concrete_dom_interface, + $ty_name + ); + impl_event_attr!( + on_focusin, + on_focusin_with_options, + web_sys::FocusEvent, + "focusin", + $concrete_dom_interface, + $ty_name + ); + impl_event_attr!( + on_focusout, + on_focusout_with_options, + web_sys::FocusEvent, + "focusout", + $concrete_dom_interface, + $ty_name + ); + impl_event_attr!( + on_blur, + on_blur_with_options, + web_sys::FocusEvent, + "blur", + $concrete_dom_interface, + $ty_name + ); + + impl_event_attr!( + on_input, + on_input_with_options, + web_sys::InputEvent, + "input", + $concrete_dom_interface, + $ty_name + ); } }; } @@ -1132,16 +1083,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,16 +1106,12 @@ 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) => { @@ -1174,25 +1121,32 @@ macro_rules! impl_html_dom_interface { impl_simple_attr!(height, set_height, u32, $ty_name); }); }; + // 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, $dom_interface); + }; } +// TODO only use T_ A_ when necessary (e.g. for the `A` element) 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 struct $ty_name { pub(crate) attrs: Attrs, - event_listeners: Vec>, + 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 +1213,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 +1234,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 +1273,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 +1407,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 +1452,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 654fcf7d5..effa4ebba 100644 --- a/crates/xilem_html/src/lib.rs +++ b/crates/xilem_html/src/lib.rs @@ -27,7 +27,9 @@ pub use element::elements; pub use element::{element, 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..ec328acc2 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(()), btn("+1 click", |state, _| state.increment()), btn("-1 click", |state, _| state.decrement()), btn("reset clicks", |state, _| state.reset()), @@ -64,8 +52,7 @@ fn app_logic(state: &mut AppState) -> impl View + ViewMarker { btn("change text", |state, _| state.change_text()), el::br(()), state.text.clone(), - )) - .classes(classes); + )); if state.clicks > 10 { d.attr("data-custom", format!("click-{}", state.clicks)) diff --git a/crates/xilem_html/web_examples/todomvc/src/main.rs b/crates/xilem_html/web_examples/todomvc/src/main.rs index 4449fcbcc..b5b4e737d 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 mut input = el::input(()) .attr("class", "toggle") @@ -45,12 +45,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(); @@ -61,15 +60,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 { @@ -80,7 +80,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); @@ -90,9 +90,9 @@ fn footer_view(state: &mut AppState, should_display: bool) -> impl View impl View impl View { .attr("placeholder", "What needs to be done?") .attr("value", state.new_todo.clone()) .attr("autofocus", "true"); - el::div(( - el::header(( - el::h1("TODOs"), - input - .on_keydown(|state: &mut AppState, evt| { - if evt.key() == "Enter" { - state.create_todo(); - } - }) - .on_input(|state: &mut AppState, evt| { - state.update_new_todo(&evt.target().value()); - evt.prevent_default(); - }) - .passive(false), - )) - .attr("class", "header"), - main, - footer, - )) + // formatter has capitulated.. + el::div( + ( + el::header( + ( + el::h1("TODOs"), + input + .on_keydown(|state: &mut AppState, evt: Event| { + if evt.key() == "Enter" { + state.create_todo(); + } + }) + .on_input_with_options( + |state: &mut AppState, evt: Event| + { + state.update_new_todo(&evt.target().value()); + evt.prevent_default(); + }, + EventListenerOptions::enable_prevent_default() + ), + ), + ) + .attr("class", "header"), + main, + footer, + ), + ) } pub fn main() {