Make the HTML elements more typed

Don't store the name in the element view, but use a zero-sized type.

Note, this breaks the untyped case. That will probably need a separate view, or alternatively the tag doesn't have to be zero-sized (so there is a variant that stores a String), the name func could take a &self parameter, and it could return the internal string.

Note, also uses the stringify function to reduce the duplication in the macros. That's a separate, orthogonal change.
This commit is contained in:
Raph Levien 2023-07-03 16:36:01 -07:00
parent 3501382f26
commit 6f27b4da94
3 changed files with 135 additions and 202 deletions

View File

@ -2,94 +2,31 @@
//! //!
macro_rules! elements { macro_rules! elements {
() => {}; () => {};
(($ty_name:ident, $builder_name:ident, $name:literal, $web_sys_ty:ty), $($rest:tt)*) => { (($ty_name:ident, $builder_name:ident, $web_sys_ty:ty), $($rest:tt)*) => {
element!($ty_name, $builder_name, $name, $web_sys_ty); element!($ty_name, $builder_name, $web_sys_ty);
elements!($($rest)*); elements!($($rest)*);
}; };
} }
macro_rules! element { macro_rules! element {
($ty_name:ident, $builder_name:ident, $name:literal, $web_sys_ty:ty) => { ($ty_name:ident, $builder_name:ident, $web_sys_ty:ty) => {
/// A view representing a /// Tag for a
#[doc = concat!("`", $name, "`")] #[doc = concat!("`", stringify!($builder_name), "`")]
/// element. /// view.
pub struct $ty_name<ViewSeq>(crate::Element<$web_sys_ty, ViewSeq>); pub struct $ty_name;
impl $crate::element::ElementTag for $ty_name {
type WebSysElement = $web_sys_ty;
fn name() -> &'static str {
stringify!($builder_name)
}
}
/// Builder function for a /// Builder function for a
#[doc = concat!("`", $name, "`")] #[doc = concat!("`", stringify!($builder_name), "`")]
/// view. /// view.
pub fn $builder_name<ViewSeq>(children: ViewSeq) -> $ty_name<ViewSeq> { pub fn $builder_name<ViewSeq>(children: ViewSeq) -> $crate::Element<$ty_name, ViewSeq> {
$ty_name(crate::element($name, children)) crate::element(stringify!($builder_name), children)
}
impl<ViewSeq> $ty_name<ViewSeq> {
/// 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<std::borrow::Cow<'static, str>>,
value: impl Into<std::borrow::Cow<'static, str>>,
) -> 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<std::borrow::Cow<'static, str>>,
value: impl Into<std::borrow::Cow<'static, str>>,
) -> &mut Self {
self.0.set_attr(name, value);
self
}
}
impl<ViewSeq> crate::view::ViewMarker for $ty_name<ViewSeq> {}
impl<T_, A_, ViewSeq> crate::view::View<T_, A_> for $ty_name<ViewSeq>
where
ViewSeq: crate::view::ViewSequence<T_, A_>,
{
type State = crate::ElementState<ViewSeq::State>;
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<dyn std::any::Any>,
app_state: &mut T_,
) -> xilem_core::MessageResult<A_> {
self.0.message(id_path, state, message, app_state)
}
} }
}; };
} }
@ -102,131 +39,121 @@ elements!(
// DOM interfaces copied from https://html.spec.whatwg.org/multipage/grouping-content.html and friends // DOM interfaces copied from https://html.spec.whatwg.org/multipage/grouping-content.html and friends
// content sectioning // content sectioning
(Address, address, "address", web_sys::HtmlElement), (Address, address, web_sys::HtmlElement),
(Article, article, "article", web_sys::HtmlElement), (Article, article, web_sys::HtmlElement),
(Aside, aside, "aside", web_sys::HtmlElement), (Aside, aside, web_sys::HtmlElement),
(Footer, footer, "footer", web_sys::HtmlElement), (Footer, footer, web_sys::HtmlElement),
(Header, header, "header", web_sys::HtmlElement), (Header, header, web_sys::HtmlElement),
(H1, h1, "h1", web_sys::HtmlHeadingElement), (H1, h1, web_sys::HtmlHeadingElement),
(H2, h2, "h2", web_sys::HtmlHeadingElement), (H2, h2, web_sys::HtmlHeadingElement),
(H3, h3, "h3", web_sys::HtmlHeadingElement), (H3, h3, web_sys::HtmlHeadingElement),
(H4, h4, "h4", web_sys::HtmlHeadingElement), (H4, h4, web_sys::HtmlHeadingElement),
(H5, h5, "h5", web_sys::HtmlHeadingElement), (H5, h5, web_sys::HtmlHeadingElement),
(H6, h6, "h6", web_sys::HtmlHeadingElement), (H6, h6, web_sys::HtmlHeadingElement),
(Hgroup, hgroup, "hgroup", web_sys::HtmlElement), (Hgroup, hgroup, web_sys::HtmlElement),
(Main, main, "main", web_sys::HtmlElement), (Main, main, web_sys::HtmlElement),
(Nav, nav, "nav", web_sys::HtmlElement), (Nav, nav, web_sys::HtmlElement),
(Section, section, "section", web_sys::HtmlElement), (Section, section, web_sys::HtmlElement),
// text content // text content
( (Blockquote, blockquote, web_sys::HtmlQuoteElement),
Blockquote, (Dd, dd, web_sys::HtmlElement),
blockquote, (Div, div, web_sys::HtmlDivElement),
"blockquote", (Dl, dl, web_sys::HtmlDListElement),
web_sys::HtmlQuoteElement (Dt, dt, web_sys::HtmlElement),
), (Figcaption, figcaption, web_sys::HtmlElement),
(Dd, dd, "dd", web_sys::HtmlElement), (Figure, figure, web_sys::HtmlElement),
(Div, div, "div", web_sys::HtmlDivElement), (Hr, hr, web_sys::HtmlHrElement),
(Dl, dl, "dl", web_sys::HtmlDListElement), (Li, li, web_sys::HtmlLiElement),
(Dt, dt, "dt", web_sys::HtmlElement), (Menu, menu, web_sys::HtmlMenuElement),
(Figcaption, figcaption, "figcaption", web_sys::HtmlElement), (Ol, ol, web_sys::HtmlOListElement),
(Figure, figure, "figure", web_sys::HtmlElement), (P, p, web_sys::HtmlParagraphElement),
(Hr, hr, "hr", web_sys::HtmlHrElement), (Pre, pre, web_sys::HtmlPreElement),
(Li, li, "li", web_sys::HtmlLiElement), (Ul, ul, web_sys::HtmlUListElement),
(Menu, menu, "menu", web_sys::HtmlMenuElement),
(Ol, ol, "ol", web_sys::HtmlOListElement),
(P, p, "p", web_sys::HtmlParagraphElement),
(Pre, pre, "pre", web_sys::HtmlPreElement),
(Ul, ul, "ul", web_sys::HtmlUListElement),
// inline text // inline text
(A, a, "a", web_sys::HtmlAnchorElement), (A, a, web_sys::HtmlAnchorElement),
(Abbr, abbr, "abbr", web_sys::HtmlElement), (Abbr, abbr, web_sys::HtmlElement),
(B, b, "b", web_sys::HtmlElement), (B, b, web_sys::HtmlElement),
(Bdi, bdi, "bdi", web_sys::HtmlElement), (Bdi, bdi, web_sys::HtmlElement),
(Bdo, bdo, "bdo", web_sys::HtmlElement), (Bdo, bdo, web_sys::HtmlElement),
(Br, br, "br", web_sys::HtmlBrElement), (Br, br, web_sys::HtmlBrElement),
(Cite, cite, "cite", web_sys::HtmlElement), (Cite, cite, web_sys::HtmlElement),
(Code, code, "code", web_sys::HtmlElement), (Code, code, web_sys::HtmlElement),
(Data, data, "data", web_sys::HtmlDataElement), (Data, data, web_sys::HtmlDataElement),
(Dfn, dfn, "dfn", web_sys::HtmlElement), (Dfn, dfn, web_sys::HtmlElement),
(Em, em, "em", web_sys::HtmlElement), (Em, em, web_sys::HtmlElement),
(I, i, "i", web_sys::HtmlElement), (I, i, web_sys::HtmlElement),
(Kbd, kbd, "kbd", web_sys::HtmlElement), (Kbd, kbd, web_sys::HtmlElement),
(Mark, mark, "mark", web_sys::HtmlElement), (Mark, mark, web_sys::HtmlElement),
(Q, q, "q", web_sys::HtmlQuoteElement), (Q, q, web_sys::HtmlQuoteElement),
(Rp, rp, "rp", web_sys::HtmlElement), (Rp, rp, web_sys::HtmlElement),
(Rt, rt, "rt", web_sys::HtmlElement), (Rt, rt, web_sys::HtmlElement),
(Ruby, ruby, "ruby", web_sys::HtmlElement), (Ruby, ruby, web_sys::HtmlElement),
(S, s, "s", web_sys::HtmlElement), (S, s, web_sys::HtmlElement),
(Samp, samp, "samp", web_sys::HtmlElement), (Samp, samp, web_sys::HtmlElement),
(Small, small, "small", web_sys::HtmlElement), (Small, small, web_sys::HtmlElement),
(Span, span, "span", web_sys::HtmlSpanElement), (Span, span, web_sys::HtmlSpanElement),
(Strong, strong, "strong", web_sys::HtmlElement), (Strong, strong, web_sys::HtmlElement),
(Sub, sub, "sub", web_sys::HtmlElement), (Sub, sub, web_sys::HtmlElement),
(Sup, sup, "sup", web_sys::HtmlElement), (Sup, sup, web_sys::HtmlElement),
(Time, time, "time", web_sys::HtmlTimeElement), (Time, time, web_sys::HtmlTimeElement),
(U, u, "u", web_sys::HtmlElement), (U, u, web_sys::HtmlElement),
(Var, var, "var", web_sys::HtmlElement), (Var, var, web_sys::HtmlElement),
(Wbr, wbr, "wbr", web_sys::HtmlElement), (Wbr, wbr, web_sys::HtmlElement),
// image and multimedia // image and multimedia
(Area, area, "area", web_sys::HtmlAreaElement), (Area, area, web_sys::HtmlAreaElement),
(Audio, audio, "audio", web_sys::HtmlAudioElement), (Audio, audio, web_sys::HtmlAudioElement),
(Img, img, "img", web_sys::HtmlImageElement), (Img, img, web_sys::HtmlImageElement),
(Map, map, "map", web_sys::HtmlMapElement), (Map, map, web_sys::HtmlMapElement),
(Track, track, "track", web_sys::HtmlTrackElement), (Track, track, web_sys::HtmlTrackElement),
(Video, video, "video", web_sys::HtmlVideoElement), (Video, video, web_sys::HtmlVideoElement),
// embedded content // embedded content
(Embed, embed, "embed", web_sys::HtmlEmbedElement), (Embed, embed, web_sys::HtmlEmbedElement),
(Iframe, iframe, "iframe", web_sys::HtmlIFrameElement), (Iframe, iframe, web_sys::HtmlIFrameElement),
(Object, object, "object", web_sys::HtmlObjectElement), (Object, object, web_sys::HtmlObjectElement),
(Picture, picture, "picture", web_sys::HtmlPictureElement), (Picture, picture, web_sys::HtmlPictureElement),
(Portal, portal, "portal", web_sys::HtmlElement), (Portal, portal, web_sys::HtmlElement),
(Source, source, "source", web_sys::HtmlSourceElement), (Source, source, web_sys::HtmlSourceElement),
// SVG and MathML (TODO, svg and mathml elements) // SVG and MathML (TODO, svg and mathml elements)
(Svg, svg, "svg", web_sys::HtmlElement), (Svg, svg, web_sys::HtmlElement),
(Math, math, "math", web_sys::HtmlElement), (Math, math, web_sys::HtmlElement),
// scripting // scripting
(Canvas, canvas, "canvas", web_sys::HtmlCanvasElement), (Canvas, canvas, web_sys::HtmlCanvasElement),
(Noscript, noscript, "noscript", web_sys::HtmlElement), (Noscript, noscript, web_sys::HtmlElement),
(Script, script, "script", web_sys::HtmlScriptElement), (Script, script, web_sys::HtmlScriptElement),
// demarcating edits // demarcating edits
(Del, del, "del", web_sys::HtmlModElement), (Del, del, web_sys::HtmlModElement),
(Ins, ins, "ins", web_sys::HtmlModElement), (Ins, ins, web_sys::HtmlModElement),
// tables // tables
( (Caption, caption, web_sys::HtmlTableCaptionElement),
Caption, (Col, col, web_sys::HtmlTableColElement),
caption, (Colgroup, colgroup, web_sys::HtmlTableColElement),
"caption", (Table, table, web_sys::HtmlTableSectionElement),
web_sys::HtmlTableCaptionElement (Tbody, tbody, web_sys::HtmlTableSectionElement),
), (Td, td, web_sys::HtmlTableCellElement),
(Col, col, "col", web_sys::HtmlTableColElement), (Tfoot, tfoot, web_sys::HtmlTableSectionElement),
(Colgroup, colgroup, "colgroup", web_sys::HtmlTableColElement), (Th, th, web_sys::HtmlTableCellElement),
(Table, table, "table", web_sys::HtmlTableSectionElement), (Thead, thead, web_sys::HtmlTableSectionElement),
(Tbody, tbody, "tbody", web_sys::HtmlTableSectionElement), (Tr, tr, web_sys::HtmlTableRowElement),
(Td, td, "td", web_sys::HtmlTableCellElement),
(Tfoot, tfoot, "tfoot", web_sys::HtmlTableSectionElement),
(Th, th, "th", web_sys::HtmlTableCellElement),
(Thead, thead, "thead", web_sys::HtmlTableSectionElement),
(Tr, tr, "tr", web_sys::HtmlTableRowElement),
// forms // forms
(Button, button, "button", web_sys::HtmlButtonElement), (Button, button, web_sys::HtmlButtonElement),
(Datalist, datalist, "datalist", web_sys::HtmlDataListElement), (Datalist, datalist, web_sys::HtmlDataListElement),
(Fieldset, fieldset, "fieldset", web_sys::HtmlFieldSetElement), (Fieldset, fieldset, web_sys::HtmlFieldSetElement),
(Form, form, "form", web_sys::HtmlFormElement), (Form, form, web_sys::HtmlFormElement),
(Input, input, "input", web_sys::HtmlInputElement), (Input, input, web_sys::HtmlInputElement),
(Label, label, "label", web_sys::HtmlLabelElement), (Label, label, web_sys::HtmlLabelElement),
(Legend, legend, "legend", web_sys::HtmlLegendElement), (Legend, legend, web_sys::HtmlLegendElement),
(Meter, meter, "meter", web_sys::HtmlMeterElement), (Meter, meter, web_sys::HtmlMeterElement),
(Optgroup, optgroup, "optgroup", web_sys::HtmlOptGroupElement), (Optgroup, optgroup, web_sys::HtmlOptGroupElement),
(Option, option, "option", web_sys::HtmlOptionElement), (Option, option, web_sys::HtmlOptionElement),
(Output, output, "output", web_sys::HtmlOutputElement), (Output, output, web_sys::HtmlOutputElement),
(Progress, progress, "progress", web_sys::HtmlProgressElement), (Progress, progress, web_sys::HtmlProgressElement),
(Select, select, "select", web_sys::HtmlSelectElement), (Select, select, web_sys::HtmlSelectElement),
(Textarea, textarea, "textarea", web_sys::HtmlTextAreaElement), (Textarea, textarea, web_sys::HtmlTextAreaElement),
// interactive elements, // interactive elements,
(Details, details, "details", web_sys::HtmlDetailsElement), (Details, details, web_sys::HtmlDetailsElement),
(Dialog, dialog, "dialog", web_sys::HtmlDialogElement), (Dialog, dialog, web_sys::HtmlDialogElement),
(Summary, summary, "summary", web_sys::HtmlElement), (Summary, summary, web_sys::HtmlElement),
// web components, // web components,
(Slot, slot, "slot", web_sys::HtmlSlotElement), (Slot, slot, web_sys::HtmlSlotElement),
(Template, template, "template", web_sys::HtmlTemplateElement), (Template, template, web_sys::HtmlTemplateElement),
); );

View File

@ -4,7 +4,7 @@
//! `use xilem_html::elements as el` or similar to the top of your file. //! `use xilem_html::elements as el` or similar to the top of your file.
use crate::{ use crate::{
context::{ChangeFlags, Cx}, context::{ChangeFlags, Cx},
view::{DomElement, Pod, View, ViewMarker, ViewSequence}, view::{DomElement, DomNode, Pod, View, ViewMarker, ViewSequence},
}; };
use std::{borrow::Cow, cmp::Ordering, collections::BTreeMap, fmt, marker::PhantomData}; use std::{borrow::Cow, cmp::Ordering, collections::BTreeMap, fmt, marker::PhantomData};
@ -24,6 +24,12 @@ pub struct Element<El, Children = ()> {
ty: PhantomData<El>, ty: PhantomData<El>,
} }
pub trait ElementTag {
type WebSysElement: JsCast + DomElement;
fn name() -> &'static str;
}
impl<E, ViewSeq> Element<E, ViewSeq> { impl<E, ViewSeq> Element<E, ViewSeq> {
pub fn debug_as_el(&self) -> impl fmt::Debug + '_ { pub fn debug_as_el(&self) -> impl fmt::Debug + '_ {
struct DebugFmt<'a, E, VS>(&'a Element<E, VS>); struct DebugFmt<'a, E, VS>(&'a Element<E, VS>);
@ -100,12 +106,12 @@ impl<T, A, El, Children> View<T, A> for Element<El, Children>
where where
Children: ViewSequence<T, A>, Children: ViewSequence<T, A>,
// In addition, the `E` parameter is expected to be a child of `web_sys::Node` // In addition, the `E` parameter is expected to be a child of `web_sys::Node`
El: JsCast + DomElement, El: ElementTag,
{ {
type State = ElementState<Children::State>; type State = ElementState<Children::State>;
type Element = El; type Element = El::WebSysElement;
fn build(&self, cx: &mut Cx) -> (Id, Self::State, El) { fn build(&self, cx: &mut Cx) -> (Id, Self::State, Self::Element) {
let el = cx.create_html_element(&self.name); let el = cx.create_html_element(&self.name);
for (name, value) in &self.attributes { for (name, value) in &self.attributes {
el.set_attribute(name, value).unwrap(); el.set_attribute(name, value).unwrap();
@ -128,7 +134,7 @@ where
prev: &Self, prev: &Self,
id: &mut Id, id: &mut Id,
state: &mut Self::State, state: &mut Self::State,
element: &mut El, element: &mut Self::Element,
) -> ChangeFlags { ) -> ChangeFlags {
let mut changed = ChangeFlags::empty(); let mut changed = ChangeFlags::empty();
// update tag name // update tag name

View File

@ -43,7 +43,7 @@ impl AppState {
fn btn<A, F>( fn btn<A, F>(
label: &'static str, label: &'static str,
click_fn: F, click_fn: F,
) -> evt::OnClick<AppState, A, el::Button<&'static str>, F, ()> ) -> evt::OnClick<AppState, A, xilem_html::Element<el::Button, &'static str>, F, ()>
where where
F: Fn(&mut AppState, &Event<web_sys::MouseEvent, web_sys::HtmlButtonElement>), F: Fn(&mut AppState, &Event<web_sys::MouseEvent, web_sys::HtmlButtonElement>),
{ {