Skip to content

Commit

Permalink
xilem_html: Introduce DOM interface traits to make per DOM element ty…
Browse files Browse the repository at this point in the history
…ping possible and flexible
  • Loading branch information
Philipp-M committed Nov 3, 2023
1 parent 8a5825d commit 193685a
Show file tree
Hide file tree
Showing 25 changed files with 1,496 additions and 1,254 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ members = [
"crates/xilem_core",
"crates/xilem_html",
"crates/xilem_html/web_examples/counter",
"crates/xilem_html/web_examples/counter_untyped",
"crates/xilem_html/web_examples/counter_custom_element",
"crates/xilem_html/web_examples/todomvc",
"crates/xilem_svg",
]
Expand Down
102 changes: 77 additions & 25 deletions crates/xilem_html/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,31 +17,6 @@ default-target = "x86_64-pc-windows-msvc"
# rustdoc-scrape-examples tracking issue https://github.com/rust-lang/rust/issues/88791
cargo-args = ["-Zunstable-options", "-Zrustdoc-scrape-examples"]

[features]
default = ["typed"]
typed = [
"web-sys/FocusEvent", "web-sys/HtmlAnchorElement", "web-sys/HtmlAreaElement",
"web-sys/HtmlAudioElement", "web-sys/HtmlBrElement", "web-sys/HtmlButtonElement",
"web-sys/HtmlCanvasElement", "web-sys/HtmlDataElement", "web-sys/HtmlDataListElement",
"web-sys/HtmlDetailsElement", "web-sys/HtmlDialogElement", "web-sys/HtmlDivElement",
"web-sys/HtmlDListElement", "web-sys/HtmlEmbedElement", "web-sys/HtmlFieldSetElement",
"web-sys/HtmlFormElement", "web-sys/HtmlHeadingElement", "web-sys/HtmlHrElement",
"web-sys/HtmlIFrameElement", "web-sys/HtmlImageElement", "web-sys/HtmlInputElement",
"web-sys/HtmlLabelElement", "web-sys/HtmlLegendElement", "web-sys/HtmlLiElement",
"web-sys/HtmlMapElement", "web-sys/HtmlMenuElement", "web-sys/HtmlMeterElement",
"web-sys/HtmlModElement", "web-sys/HtmlObjectElement", "web-sys/HtmlOListElement",
"web-sys/HtmlOptGroupElement", "web-sys/HtmlOptionElement", "web-sys/HtmlOutputElement",
"web-sys/HtmlParagraphElement", "web-sys/HtmlPictureElement", "web-sys/HtmlPreElement",
"web-sys/HtmlProgressElement", "web-sys/HtmlQuoteElement", "web-sys/HtmlScriptElement",
"web-sys/HtmlSelectElement", "web-sys/HtmlSlotElement", "web-sys/HtmlSourceElement",
"web-sys/HtmlSpanElement", "web-sys/HtmlTableElement", "web-sys/HtmlTableCellElement",
"web-sys/HtmlTableColElement", "web-sys/HtmlTableCaptionElement", "web-sys/HtmlTableRowElement",
"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",
]

[dependencies]
xilem_core.workspace = true
kurbo.workspace = true
Expand All @@ -63,4 +38,81 @@ features = [
"SvgElement",
"Text",
"Window",
"FocusEvent",
"HtmlInputElement",
"InputEvent",
"KeyboardEvent",
"MouseEvent",
"PointerEvent",
"WheelEvent",
"HtmlAnchorElement",
"HtmlAreaElement",
"HtmlAudioElement",
"HtmlBaseElement",
"HtmlBodyElement",
"HtmlBrElement",
"HtmlButtonElement",
"HtmlCanvasElement",
"HtmlDataElement",
"HtmlDataListElement",
"HtmlDetailsElement",
"HtmlDialogElement",
"HtmlDirectoryElement",
"HtmlDivElement",
"HtmlDListElement",
"HtmlUnknownElement",
"HtmlEmbedElement",
"HtmlFieldSetElement",
"HtmlFontElement",
"HtmlFormElement",
"HtmlFrameElement",
"HtmlFrameSetElement",
"HtmlHeadElement",
"HtmlHeadingElement",
"HtmlHrElement",
"HtmlHtmlElement",
"HtmlIFrameElement",
"HtmlImageElement",
"HtmlInputElement",
"HtmlLabelElement",
"HtmlLegendElement",
"HtmlLiElement",
"HtmlLinkElement",
"HtmlMapElement",
"HtmlMediaElement",
"HtmlMenuElement",
"HtmlMenuItemElement",
"HtmlMetaElement",
"HtmlMeterElement",
"HtmlModElement",
"HtmlObjectElement",
"HtmlOListElement",
"HtmlOptGroupElement",
"HtmlOptionElement",
"HtmlOutputElement",
"HtmlParagraphElement",
"HtmlParamElement",
"HtmlPictureElement",
"HtmlPreElement",
"HtmlProgressElement",
"HtmlQuoteElement",
"HtmlScriptElement",
"HtmlSelectElement",
"HtmlSlotElement",
"HtmlSourceElement",
"HtmlSpanElement",
"HtmlStyleElement",
"HtmlTableCaptionElement",
"HtmlTableCellElement",
"HtmlTableColElement",
"HtmlTableElement",
"HtmlTableRowElement",
"HtmlTableSectionElement",
"HtmlTemplateElement",
"HtmlTimeElement",
"HtmlTextAreaElement",
"HtmlTitleElement",
"HtmlTrackElement",
"HtmlUListElement",
"HtmlVideoElement",
]
59 changes: 59 additions & 0 deletions crates/xilem_html/src/attribute.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
use std::borrow::Cow;

use xilem_core::{Id, MessageResult};

use crate::{sealed::Sealed, AttributeValue, ChangeFlags, Cx, View, ViewMarker};

use super::interfaces::{for_all_dom_interfaces, Element};

pub struct Attr<E> {
pub(crate) element: E,
pub(crate) name: Cow<'static, str>,
pub(crate) value: Option<AttributeValue>,
}

impl<E> ViewMarker for Attr<E> {}
impl<E> Sealed for Attr<E> {}

impl<T, A, E: Element<T, A>> View<T, A> for Attr<E> {
type State = E::State;
type Element = E::Element;

fn build(&self, cx: &mut Cx) -> (Id, Self::State, Self::Element) {
cx.add_new_attribute_to_current_element(&self.name, &self.value);
self.element.build(cx)
}

fn rebuild(
&self,
cx: &mut Cx,
prev: &Self,
id: &mut Id,
state: &mut Self::State,
element: &mut Self::Element,
) -> ChangeFlags {
cx.add_new_attribute_to_current_element(&self.name, &self.value);
self.element.rebuild(cx, &prev.element, id, state, element)
}

fn message(
&self,
id_path: &[Id],
state: &mut Self::State,
message: Box<dyn std::any::Any>,
app_state: &mut T,
) -> MessageResult<A> {
self.element.message(id_path, state, message, app_state)
}
}

macro_rules! impl_dom_interface_for_attr {
($dom_interface:ident) => {
impl<T, A, E: $crate::interfaces::$dom_interface<T, A>>
$crate::interfaces::$dom_interface<T, A> for Attr<E>
{
}
};
}

for_all_dom_interfaces!(impl_dom_interface_for_attr);
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
type CowStr = std::borrow::Cow<'static, str>;

#[derive(PartialEq, Debug)]
#[derive(PartialEq, Clone, Debug, PartialOrd)]
pub enum AttributeValue {
True, // for the boolean true, this serializes to an empty string (e.g. for <input checked>)
I32(i32),
Expand Down
75 changes: 0 additions & 75 deletions crates/xilem_html/src/class.rs

This file was deleted.

91 changes: 89 additions & 2 deletions crates/xilem_html/src/context.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,51 @@
use std::any::Any;

use bitflags::bitflags;
use wasm_bindgen::JsCast;
use wasm_bindgen::{JsCast, UnwrapThrowExt};
use web_sys::Document;

use xilem_core::{Id, IdPath};

use crate::{app::AppRunner, Message, HTML_NS, SVG_NS};
use crate::{
app::AppRunner,
diff::{diff_kv_iterables, Diff},
vecmap::VecMap,
AttributeValue, Message, HTML_NS, SVG_NS,
};

type CowStr = std::borrow::Cow<'static, str>;

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 `value` 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();
}
}

// 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_attributes: VecMap<CowStr, AttributeValue>,
app_ref: Option<Box<dyn AppRunner>>,
}

Expand All @@ -34,6 +68,7 @@ impl Cx {
id_path: Vec::new(),
document: crate::document(),
app_ref: None,
current_element_attributes: Default::default(),
}
}

Expand Down Expand Up @@ -89,6 +124,58 @@ impl Cx {
self.create_element(SVG_NS, name).unchecked_into()
}

// 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_new_attribute_to_current_element(
&mut self,
name: &CowStr,
value: &Option<AttributeValue>,
) {
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_attributes.contains_key(name) {
self.current_element_attributes
.insert(name.clone(), value.clone());
}
}
}

pub(crate) fn apply_attributes(
&mut self,
element: &web_sys::Element,
) -> VecMap<CowStr, AttributeValue> {
let mut attributes = VecMap::default();
std::mem::swap(&mut attributes, &mut self.current_element_attributes);
for (name, value) in attributes.iter() {
set_attribute(element, name, &value.serialize());
}
attributes
}

pub(crate) fn apply_attribute_changes(
&mut self,
element: &web_sys::Element,
attributes: &mut VecMap<CowStr, AttributeValue>,
) -> ChangeFlags {
let mut changed = ChangeFlags::empty();
// update attributes
for itm in diff_kv_iterables(&*attributes, &self.current_element_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.current_element_attributes);
self.current_element_attributes.clear();
changed
}

pub fn message_thunk(&self) -> MessageThunk {
MessageThunk {
id_path: self.id_path.clone(),
Expand Down
Loading

0 comments on commit 193685a

Please sign in to comment.