diff --git a/crates/xilem_web/src/attribute.rs b/crates/xilem_web/src/attribute.rs index 1fb455605b..06935c345d 100644 --- a/crates/xilem_web/src/attribute.rs +++ b/crates/xilem_web/src/attribute.rs @@ -3,9 +3,9 @@ use std::marker::PhantomData; use xilem_core::{Id, MessageResult}; -use crate::{interfaces::sealed::Sealed, AttributeValue, ChangeFlags, Cx, View, ViewMarker}; +use crate::{AttributeValue, ChangeFlags, Cx, View, ViewMarker}; -use super::interfaces::Element; +use super::interfaces::{Element, ElementProps as _}; pub struct Attr { pub(crate) element: E, @@ -15,15 +15,15 @@ pub struct Attr { } impl ViewMarker for Attr {} -impl Sealed for Attr {} impl, T, A> View for Attr { type State = E::State; type Element = E::Element; fn build(&self, cx: &mut Cx) -> (Id, Self::State, Self::Element) { - cx.add_attr_to_element(&self.name, &self.value); - self.element.build(cx) + let (id, mut state, element) = self.element.build(cx); + state.set_attribute(Some(element.as_ref()), &self.name, &self.value); + (id, state, element) } fn rebuild( @@ -34,7 +34,7 @@ impl, T, A> View for Attr { state: &mut Self::State, element: &mut Self::Element, ) -> ChangeFlags { - cx.add_attr_to_element(&self.name, &self.value); + state.set_attribute(None, &self.name, &self.value); self.element.rebuild(cx, &prev.element, id, state, element) } diff --git a/crates/xilem_web/src/class.rs b/crates/xilem_web/src/class.rs index da2f587690..db4df2bb87 100644 --- a/crates/xilem_web/src/class.rs +++ b/crates/xilem_web/src/class.rs @@ -3,7 +3,7 @@ use std::{borrow::Cow, marker::PhantomData}; use xilem_core::{Id, MessageResult}; use crate::{ - interfaces::{sealed::Sealed, Element}, + interfaces::{Element, ElementProps as _}, ChangeFlags, Cx, View, ViewMarker, }; @@ -83,17 +83,17 @@ pub struct Class { } impl ViewMarker for Class {} -impl Sealed for Class {} impl, T, A> View for Class { type State = E::State; type Element = E::Element; fn build(&self, cx: &mut Cx) -> (Id, Self::State, Self::Element) { + let (id, mut state, el) = self.element.build(cx); for class_name in &self.class_names { - cx.add_class_to_element(class_name); + state.set_class(Some(el.as_ref()), class_name.clone()); } - self.element.build(cx) + (id, state, el) } fn rebuild( @@ -105,7 +105,7 @@ impl, T, A> View for Class { element: &mut Self::Element, ) -> ChangeFlags { for class_name in &self.class_names { - cx.add_class_to_element(class_name); + state.set_class(None, class_name.clone()); } self.element.rebuild(cx, &prev.element, id, state, element) } diff --git a/crates/xilem_web/src/context.rs b/crates/xilem_web/src/context.rs index ce793851d5..43b670c50e 100644 --- a/crates/xilem_web/src/context.rs +++ b/crates/xilem_web/src/context.rs @@ -1,217 +1,14 @@ use std::any::Any; +use crate::{app::AppRunner, view::DomNode, Message, Pod}; use bitflags::bitflags; -use wasm_bindgen::{JsCast, UnwrapThrowExt}; use web_sys::Document; - use xilem_core::{Id, IdPath}; -use crate::{ - app::AppRunner, - diff::{diff_kv_iterables, Diff}, - vecmap::VecMap, - view::DomNode, - AttributeValue, Message, Pod, -}; - -type CowStr = std::borrow::Cow<'static, str>; - -#[derive(Debug, Default)] -pub struct HtmlProps { - pub(crate) attributes: VecMap, - pub(crate) classes: VecMap, - pub(crate) styles: VecMap, -} - -impl HtmlProps { - fn apply(&mut self, el: &web_sys::Element) -> Self { - let attributes = self.apply_attributes(el); - let classes = self.apply_classes(el); - let styles = self.apply_styles(el); - Self { - attributes, - classes, - styles, - } - } - - fn apply_attributes(&mut self, element: &web_sys::Element) -> VecMap { - let mut attributes = VecMap::default(); - std::mem::swap(&mut attributes, &mut self.attributes); - for (name, value) in attributes.iter() { - set_attribute(element, name, &value.serialize()); - } - attributes - } - - fn apply_classes(&mut self, element: &web_sys::Element) -> VecMap { - let mut classes = VecMap::default(); - std::mem::swap(&mut classes, &mut self.classes); - for (class_name, ()) in classes.iter() { - set_class(element, class_name); - } - classes - } - - fn apply_styles(&mut self, element: &web_sys::Element) -> VecMap { - let mut styles = VecMap::default(); - std::mem::swap(&mut styles, &mut self.styles); - for (name, value) in styles.iter() { - set_style(element, name, value); - } - styles - } - - fn apply_changes(&mut self, element: &web_sys::Element, props: &mut HtmlProps) -> ChangeFlags { - self.apply_attribute_changes(element, &mut props.attributes) - | self.apply_class_changes(element, &mut props.classes) - | self.apply_style_changes(element, &mut props.styles) - } - - pub(crate) fn apply_attribute_changes( - &mut self, - element: &web_sys::Element, - attributes: &mut VecMap, - ) -> ChangeFlags { - let mut changed = ChangeFlags::empty(); - // update attributes - for itm in diff_kv_iterables(&*attributes, &self.attributes) { - match itm { - Diff::Add(name, value) | Diff::Change(name, value) => { - set_attribute(element, name, &value.serialize()); - changed |= ChangeFlags::OTHER_CHANGE; - } - Diff::Remove(name) => { - remove_attribute(element, name); - changed |= ChangeFlags::OTHER_CHANGE; - } - } - } - std::mem::swap(attributes, &mut self.attributes); - self.attributes.clear(); - changed - } - - pub(crate) fn apply_class_changes( - &mut self, - element: &web_sys::Element, - classes: &mut VecMap, - ) -> ChangeFlags { - let mut changed = ChangeFlags::empty(); - // update attributes - for itm in diff_kv_iterables(&*classes, &self.classes) { - match itm { - Diff::Add(class_name, ()) | Diff::Change(class_name, ()) => { - set_class(element, class_name); - changed |= ChangeFlags::OTHER_CHANGE; - } - Diff::Remove(class_name) => { - remove_class(element, class_name); - changed |= ChangeFlags::OTHER_CHANGE; - } - } - } - std::mem::swap(classes, &mut self.classes); - self.classes.clear(); - changed - } - - pub(crate) fn apply_style_changes( - &mut self, - element: &web_sys::Element, - styles: &mut VecMap, - ) -> ChangeFlags { - let mut changed = ChangeFlags::empty(); - // update attributes - for itm in diff_kv_iterables(&*styles, &self.styles) { - match itm { - Diff::Add(name, value) | Diff::Change(name, value) => { - set_style(element, name, value); - changed |= ChangeFlags::OTHER_CHANGE; - } - Diff::Remove(name) => { - remove_style(element, name); - changed |= ChangeFlags::OTHER_CHANGE; - } - } - } - std::mem::swap(styles, &mut self.styles); - self.styles.clear(); - changed - } -} - -fn set_attribute(element: &web_sys::Element, name: &str, value: &str) { - // we have to special-case `value` because setting the value using `set_attribute` - // doesn't work after the value has been changed. - if name == "value" { - let element: &web_sys::HtmlInputElement = element.dyn_ref().unwrap_throw(); - element.set_value(value); - } else if name == "checked" { - let element: &web_sys::HtmlInputElement = element.dyn_ref().unwrap_throw(); - element.set_checked(true); - } else { - element.set_attribute(name, value).unwrap_throw(); - } -} - -fn remove_attribute(element: &web_sys::Element, name: &str) { - // we have to special-case `checked` because setting the value using `set_attribute` - // doesn't work after the value has been changed. - if name == "checked" { - let element: &web_sys::HtmlInputElement = element.dyn_ref().unwrap_throw(); - element.set_checked(false); - } else { - element.remove_attribute(name).unwrap_throw(); - } -} - -fn set_class(element: &web_sys::Element, class_name: &str) { - debug_assert!( - !class_name.is_empty(), - "class names cannot be the empty string" - ); - debug_assert!( - !class_name.contains(' '), - "class names cannot contain the ascii space character" - ); - element.class_list().add_1(class_name).unwrap_throw(); -} - -fn remove_class(element: &web_sys::Element, class_name: &str) { - debug_assert!( - !class_name.is_empty(), - "class names cannot be the empty string" - ); - debug_assert!( - !class_name.contains(' '), - "class names cannot contain the ascii space character" - ); - element.class_list().remove_1(class_name).unwrap_throw(); -} - -fn set_style(element: &web_sys::Element, name: &str, value: &str) { - if let Some(el) = element.dyn_ref::() { - el.style().set_property(name, value).unwrap_throw(); - } else if let Some(el) = element.dyn_ref::() { - el.style().set_property(name, value).unwrap_throw(); - } -} - -fn remove_style(element: &web_sys::Element, name: &str) { - if let Some(el) = element.dyn_ref::() { - el.style().remove_property(name).unwrap_throw(); - } else if let Some(el) = element.dyn_ref::() { - el.style().remove_property(name).unwrap_throw(); - } -} - // Note: xilem has derive Clone here. Not sure. pub struct Cx { id_path: IdPath, document: Document, - // TODO There's likely a cleaner more robust way to propagate the attributes to an element - pub(crate) current_element_props: HtmlProps, app_ref: Option>, } @@ -234,7 +31,6 @@ impl Cx { id_path: Vec::new(), document: crate::document(), app_ref: None, - current_element_props: Default::default(), } } @@ -306,64 +102,6 @@ impl Cx { &self.document } - pub(crate) fn build_element(&mut self, ns: &str, name: &str) -> (web_sys::Element, HtmlProps) { - let el = self - .document - .create_element_ns(Some(ns), name) - .expect("could not create element"); - let props = self.current_element_props.apply(&el); - (el, props) - } - - pub(crate) fn rebuild_element( - &mut self, - element: &web_sys::Element, - props: &mut HtmlProps, - ) -> ChangeFlags { - self.current_element_props.apply_changes(element, props) - } - - // TODO Not sure how multiple attribute definitions with the same name should be handled (e.g. `e.attr("class", "a").attr("class", "b")`) - // Currently the outer most (in the example above "b") defines the attribute (when it isn't `None`, in that case the inner attr defines the value) - pub(crate) fn add_attr_to_element(&mut self, name: &CowStr, value: &Option) { - // Panic in dev if "class" is used as an attribute. In production the result is undefined. - debug_assert!( - name != "class", - "classes should be set using the `class` method" - ); - // Panic in dev if "style" is used as an attribute. In production the result is undefined. - debug_assert!( - name != "style", - "styles should be set using the `style` method" - ); - - if let Some(value) = value { - // could be slightly optimized via something like this: `new_attrs.entry(name).or_insert_with(|| value)` - if !self.current_element_props.attributes.contains_key(name) { - self.current_element_props - .attributes - .insert(name.clone(), value.clone()); - } - } - } - - pub(crate) fn add_class_to_element(&mut self, class_name: &CowStr) { - // Don't strictly need this check but I assume its better for perf (might not be though) - if !self.current_element_props.classes.contains_key(class_name) { - self.current_element_props - .classes - .insert(class_name.clone(), ()); - } - } - - pub(crate) fn add_style_to_element(&mut self, name: &CowStr, value: &CowStr) { - if !self.current_element_props.styles.contains_key(name) { - self.current_element_props - .styles - .insert(name.clone(), value.clone()); - } - } - pub fn message_thunk(&self) -> MessageThunk { MessageThunk { id_path: self.id_path.clone(), diff --git a/crates/xilem_web/src/elements.rs b/crates/xilem_web/src/elements.rs index ee4b31e67d..cdebab4c93 100644 --- a/crates/xilem_web/src/elements.rs +++ b/crates/xilem_web/src/elements.rs @@ -4,20 +4,140 @@ use wasm_bindgen::{JsCast, UnwrapThrowExt}; use xilem_core::{Id, MessageResult, VecSplice}; use crate::{ - context::HtmlProps, interfaces::sealed::Sealed, view::DomNode, ChangeFlags, Cx, ElementsSplice, - Pod, View, ViewMarker, ViewSequence, HTML_NS, + diff::{diff_kv_iterables, Diff}, + vecmap::VecMap, + view::DomNode, + AttributeValue, ChangeFlags, Cx, ElementsSplice, Pod, View, ViewMarker, ViewSequence, HTML_NS, }; use super::interfaces::Element; type CowStr = std::borrow::Cow<'static, str>; +#[derive(Debug, Default)] +pub struct ElementProps { + pub(crate) attributes: VecMap, + pub(crate) next_attributes: VecMap, + pub(crate) classes: VecMap, + pub(crate) next_classes: VecMap, + pub(crate) styles: VecMap, + pub(crate) next_styles: VecMap, +} + +impl ElementProps { + pub(crate) fn apply_changes(&mut self, element: &web_sys::Element) -> ChangeFlags { + self.apply_attribute_changes(element) + | self.apply_class_changes(element) + | self.apply_style_changes(element) + } + + pub(crate) fn apply_attribute_changes(&mut self, element: &web_sys::Element) -> ChangeFlags { + let mut changed = ChangeFlags::empty(); + // update attributes + for itm in diff_kv_iterables(&self.attributes, &self.next_attributes) { + match itm { + Diff::Add(name, value) | Diff::Change(name, value) => { + set_attribute(element, name, &value.serialize()); + changed |= ChangeFlags::OTHER_CHANGE; + } + Diff::Remove(name) => { + remove_attribute(element, name); + changed |= ChangeFlags::OTHER_CHANGE; + } + } + } + std::mem::swap(&mut self.next_attributes, &mut self.attributes); + self.next_attributes.clear(); + changed + } + + pub(crate) fn apply_class_changes(&mut self, element: &web_sys::Element) -> ChangeFlags { + let mut changed = ChangeFlags::empty(); + // update attributes + for itm in diff_kv_iterables(&self.classes, &self.next_classes) { + match itm { + Diff::Add(class_name, ()) | Diff::Change(class_name, ()) => { + set_class(element, class_name); + changed |= ChangeFlags::OTHER_CHANGE; + } + Diff::Remove(class_name) => { + remove_class(element, class_name); + changed |= ChangeFlags::OTHER_CHANGE; + } + } + } + std::mem::swap(&mut self.next_classes, &mut self.classes); + self.next_classes.clear(); + changed + } + + pub(crate) fn apply_style_changes(&mut self, element: &web_sys::Element) -> ChangeFlags { + let mut changed = ChangeFlags::empty(); + // update attributes + for itm in diff_kv_iterables(&self.styles, &self.next_styles) { + match itm { + Diff::Add(name, value) | Diff::Change(name, value) => { + set_style(element, name, value); + changed |= ChangeFlags::OTHER_CHANGE; + } + Diff::Remove(name) => { + remove_style(element, name); + changed |= ChangeFlags::OTHER_CHANGE; + } + } + } + std::mem::swap(&mut self.next_styles, &mut self.styles); + self.next_styles.clear(); + changed + } +} + +impl crate::interfaces::ElementProps for ElementProps { + fn set_attribute( + &mut self, + element: Option<&web_sys::Element>, + name: &CowStr, + value: &Option, + ) { + if let Some(value) = value { + if let Some(element) = element { + set_attribute(element, name, &value.serialize()); + self.attributes.insert(name.clone(), value.clone()); + } else { + // Could be slightly optimized via something like this: `self.new_attributes.entry(name).or_insert_with(|| value)` + // (when it is implemented in `VecMap`) + if !self.next_attributes.contains_key(name) { + self.next_attributes.insert(name.clone(), value.clone()); + } + } + } + } + + fn set_class(&mut self, element: Option<&web_sys::Element>, class: CowStr) { + if let Some(element) = element { + set_class(element, &class); + self.classes.insert(class, ()); + } else { + self.next_classes.insert(class, ()); + } + } + + fn set_style(&mut self, element: Option<&web_sys::Element>, key: CowStr, value: CowStr) { + if let Some(element) = element { + set_style(element, &key, &value); + self.styles.insert(key, value); + } else { + self.next_styles.insert(key, value); + } + } +} + /// The state associated with a HTML element `View`. /// /// Stores handles to the child elements and any child state, as well as attributes and event listeners pub struct ElementState { pub(crate) children_states: ViewSeqState, - pub(crate) props: HtmlProps, + pub(crate) props: ElementProps, pub(crate) child_elements: Vec, /// This is temporary cache for elements while updating/diffing, /// after usage it shouldn't contain any elements, @@ -25,6 +145,90 @@ pub struct ElementState { pub(crate) scratch: Vec, } +fn set_attribute(element: &web_sys::Element, name: &str, value: &str) { + // we have to special-case `value` because setting the value using `set_attribute` + // doesn't work after the value has been changed. + if name == "value" { + let element: &web_sys::HtmlInputElement = element.dyn_ref().unwrap_throw(); + element.set_value(value); + } else if name == "checked" { + let element: &web_sys::HtmlInputElement = element.dyn_ref().unwrap_throw(); + element.set_checked(true); + } else { + element.set_attribute(name, value).unwrap_throw(); + } +} + +fn remove_attribute(element: &web_sys::Element, name: &str) { + // we have to special-case `checked` because setting the value using `set_attribute` + // doesn't work after the value has been changed. + if name == "checked" { + let element: &web_sys::HtmlInputElement = element.dyn_ref().unwrap_throw(); + element.set_checked(false); + } else { + element.remove_attribute(name).unwrap_throw(); + } +} + +fn set_class(element: &web_sys::Element, class_name: &str) { + debug_assert!( + !class_name.is_empty(), + "class names cannot be the empty string" + ); + debug_assert!( + !class_name.contains(' '), + "class names cannot contain the ascii space character" + ); + element.class_list().add_1(class_name).unwrap_throw(); +} + +fn remove_class(element: &web_sys::Element, class_name: &str) { + debug_assert!( + !class_name.is_empty(), + "class names cannot be the empty string" + ); + debug_assert!( + !class_name.contains(' '), + "class names cannot contain the ascii space character" + ); + element.class_list().remove_1(class_name).unwrap_throw(); +} + +fn set_style(element: &web_sys::Element, name: &str, value: &str) { + if let Some(el) = element.dyn_ref::() { + el.style().set_property(name, value).unwrap_throw(); + } else if let Some(el) = element.dyn_ref::() { + el.style().set_property(name, value).unwrap_throw(); + } +} + +fn remove_style(element: &web_sys::Element, name: &str) { + if let Some(el) = element.dyn_ref::() { + el.style().remove_property(name).unwrap_throw(); + } else if let Some(el) = element.dyn_ref::() { + el.style().remove_property(name).unwrap_throw(); + } +} + +impl crate::interfaces::ElementProps for ElementState { + fn set_attribute( + &mut self, + element: Option<&web_sys::Element>, + name: &CowStr, + value: &Option, + ) { + self.props.set_attribute(element, name, value); + } + + fn set_class(&mut self, element: Option<&web_sys::Element>, class: CowStr) { + self.props.set_class(element, class); + } + + fn set_style(&mut self, element: Option<&web_sys::Element>, key: CowStr, value: CowStr) { + self.props.set_style(element, key, value); + } +} + // TODO something like the `after_update` of the former `Element` view (likely as a wrapper view instead) pub struct CustomElement { @@ -46,12 +250,6 @@ pub fn custom_element>( } } -impl CustomElement { - fn node_name(&self) -> &str { - &self.name - } -} - /// An `ElementsSplice` that does DOM updates in place struct ChildrenSplice<'a, 'b, 'c> { children: VecSplice<'a, 'b, Pod>, @@ -137,7 +335,6 @@ impl<'a, 'b, 'c> ElementsSplice for ChildrenSplice<'a, 'b, 'c> { } impl ViewMarker for CustomElement {} -impl Sealed for CustomElement {} impl View for CustomElement where @@ -150,7 +347,10 @@ where type Element = web_sys::HtmlElement; fn build(&self, cx: &mut Cx) -> (Id, Self::State, Self::Element) { - let (el, props) = cx.build_element(HTML_NS, &self.name); + let el = cx + .document() + .create_element_ns(Some(HTML_NS), &self.name) + .expect("Could not create element"); let mut child_elements = vec![]; let mut scratch = vec![]; @@ -167,11 +367,12 @@ where .unwrap_throw(); let el = el.dyn_into().unwrap_throw(); + let state = ElementState { children_states, child_elements, scratch, - props, + props: Default::default(), }; (id, state, el) } @@ -193,8 +394,13 @@ where .parent_element() .expect_throw("this element was mounted and so should have a parent"); parent.remove_child(element).unwrap_throw(); - let (new_element, props) = cx.build_element(HTML_NS, self.node_name()); - state.props = props; + let new_element = cx + .document() + .create_element_ns(Some(HTML_NS), &self.name) + .expect("Could not create element"); + state.props.attributes = VecMap::default(); + state.props.classes = VecMap::default(); + state.props.styles = VecMap::default(); // TODO could this be combined with child updates? while let Some(child) = element.child_nodes().get(0) { new_element.append_child(&child).unwrap_throw(); @@ -203,7 +409,7 @@ where changed |= ChangeFlags::STRUCTURE; } - changed |= cx.rebuild_element(element, &mut state.props); + changed |= state.props.apply_changes(element); // update children let mut splice = @@ -273,14 +479,16 @@ macro_rules! define_element { pub struct $ty_name<$t, $a = (), $vs = ()>($vs, PhantomData ($t, $a)>); impl<$t, $a, $vs> ViewMarker for $ty_name<$t, $a, $vs> {} - impl<$t, $a, $vs> Sealed for $ty_name<$t, $a, $vs> {} impl<$t, $a, $vs: ViewSequence<$t, $a>> View<$t, $a> for $ty_name<$t, $a, $vs> { type State = ElementState<$vs::State>; type Element = web_sys::$dom_interface; fn build(&self, cx: &mut Cx) -> (Id, Self::State, Self::Element) { - let (el, props) = cx.build_element($ns, $tag_name); + let el = cx + .document() + .create_element_ns(Some($ns), $tag_name) + .expect("Could not create element"); let mut child_elements = vec![]; let mut scratch = vec![]; @@ -300,7 +508,7 @@ macro_rules! define_element { children_states, child_elements, scratch, - props, + props: Default::default(), }; (id, state, el) } @@ -315,7 +523,7 @@ macro_rules! define_element { ) -> ChangeFlags { let mut changed = ChangeFlags::empty(); - changed |= cx.rebuild_element(element, &mut state.props); + changed |= state.props.apply_changes(element); // update children let mut splice = ChildrenSplice::new(&mut state.child_elements, &mut state.scratch, element); @@ -361,10 +569,7 @@ macro_rules! define_elements { use xilem_core::{Id, MessageResult}; use super::{ElementState, ChildrenSplice}; - use crate::{ - interfaces::sealed::Sealed, - ChangeFlags, Cx, View, ViewMarker, ViewSequence, - }; + use crate::{ChangeFlags, Cx, View, ViewMarker, ViewSequence}; $(define_element!(crate::$ns, $element_def);)* }; diff --git a/crates/xilem_web/src/events.rs b/crates/xilem_web/src/events.rs index e417216cc3..93c75e96c9 100644 --- a/crates/xilem_web/src/events.rs +++ b/crates/xilem_web/src/events.rs @@ -1,5 +1,5 @@ use crate::{ - interfaces::{sealed::Sealed, Element}, + interfaces::{Element, ElementProps}, view::DomNode, ChangeFlags, Cx, OptionalAction, View, ViewMarker, }; @@ -87,8 +87,31 @@ pub struct OnEventState { child_state: S, } +impl ElementProps for OnEventState { + fn set_attribute( + &mut self, + element: Option<&web_sys::Element>, + name: &Cow<'static, str>, + value: &Option, + ) { + self.child_state.set_attribute(element, name, value); + } + + fn set_class(&mut self, element: Option<&web_sys::Element>, class: Cow<'static, str>) { + self.child_state.set_class(element, class); + } + + fn set_style( + &mut self, + element: Option<&web_sys::Element>, + key: Cow<'static, str>, + value: Cow<'static, str>, + ) { + self.child_state.set_style(element, key, value); + } +} + impl ViewMarker for OnEvent {} -impl Sealed for OnEvent {} impl View for OnEvent where @@ -233,7 +256,6 @@ macro_rules! event_definitions { } impl ViewMarker for $ty_name {} - impl Sealed for $ty_name {} impl View for $ty_name where diff --git a/crates/xilem_web/src/interfaces.rs b/crates/xilem_web/src/interfaces.rs index e7ec6a816a..b02b6f8ff3 100644 --- a/crates/xilem_web/src/interfaces.rs +++ b/crates/xilem_web/src/interfaces.rs @@ -1,20 +1,50 @@ use crate::{ class::{Class, IntoClasses}, + events::{self, OnEvent}, style::{IntoStyles, Style}, - Pointer, PointerMsg, View, ViewMarker, + Attr, AttributeValue, IntoAttributeValue, OptionalAction, Pointer, PointerMsg, View, + ViewMarker, }; use std::{borrow::Cow, marker::PhantomData}; use gloo::events::EventListenerOptions; use wasm_bindgen::JsCast; -use crate::{ - events::{self, OnEvent}, - Attr, IntoAttributeValue, OptionalAction, -}; +/// This trait is used in `View::State` to allow setting properties of a DOM element for various modifier types, such as `Class` or `Attr` +/// For every view reconciliation the methods have to be called again. +/// In `View::build` the properties are usually set while traversing up from the concrete element (see `Attr`), here they are set directly on the given element. +/// In `View::rebuild` the properties set via the methods are accumulated until reaching the concrete element type, +/// such as `crate::elements::html::Div` where they're diffed with the previous state (and resulting changes are propagated to the underlying DOM element) +pub trait ElementProps { + /// Set an HTML attribute + /// This method is additive, i.e. if `value` is `None` the possibly previous set value is not removed, and it is a noop + /// + /// When `element` is `None` the `name`/`value` pair is scheduled for the next update/diffing process in the concrete element (in `::rebuild`). + /// When `element` is Some(_) it is set directly on the element this usually happens in `View::build` on the up-traversal (see `::build`) + #[allow(clippy::ptr_arg)] + fn set_attribute( + &mut self, + element: Option<&web_sys::Element>, + name: &Cow<'static, str>, + value: &Option, + ); -pub(crate) mod sealed { - pub trait Sealed {} + /// Add a class + /// + /// When `element` is `None` the `class` is scheduled for the next update/diffing process in the concrete element (in `::rebuild`). + /// When `element` is Some(_) it is set directly on the element this usually happens in `View::build` on the up-traversal (see e.g. `::build`) + fn set_class(&mut self, element: Option<&web_sys::Element>, class: Cow<'static, str>); + + /// Set an HTML/SVG style pair + /// + /// When `element` is `None` the `key`/`value` pair is scheduled for the next update/diffing process in the concrete element (in `::rebuild`). + /// When `element` is Some(_) it is set directly on the element this usually happens in `View::build` on the up-traversal (see `