feat: add aria

This commit is contained in:
Jonathan Kelley 2021-07-13 00:56:39 -04:00
parent c79d9ae674
commit 4091846934
11 changed files with 322 additions and 97 deletions

View file

@ -170,10 +170,10 @@ Dioxus is heavily inspired by React, but we want your transition to feel like an
| Suspense | 🛠 | ✅ | schedule future render from future/promise |
| Cooperative Scheduling | 🛠 | ✅ | Prioritize important events over non-important events |
| Fine-grained reactivity | 🛠 | ❓ | Skip diffing for fine-grain updates |
| Compile-time correct | ✅ | ❓ | Throw errors on invalid template layouts |
| Runs natively | ✅ | ❓ | runs as a portable binary w/o a runtime (Node) |
| 1st class global state | ✅ | ❓ | redux/recoil/mobx on top of context |
| Subtree Memoization | ✅ | ❓ | skip diffing static element subtrees |
| Compile-time correct | ✅ | ❓ | Throw errors on invalid template layouts |
| Heuristic Engine | 🛠 | ❓ | track component memory usage to minimize future allocations |
| NodeRef | 🛠 | ✅ | gain direct access to nodes [1] |

View file

@ -89,7 +89,7 @@ static Example: FC<()> = |cx| {
// Expressions can be used in element position too:
{rsx!(p { "More templating!" })}
{html!(<p>"Even HTML templating!!"</p>)}
// {html!(<p>"Even HTML templating!!"</p>)}
// Iterators
{(0..10).map(|i| rsx!(li { "{i}" }))}

View file

@ -2,7 +2,7 @@ use std::cell::Cell;
use dioxus::prelude::*;
use dioxus_core::{
nodes::{NodeKey, VElement, VText},
nodes::{VElement, VText},
RealDomNode,
};
@ -23,6 +23,9 @@ const Example: FC<()> = |cx| {
Fragment {
Fragment {
"h1"
div {
}
}
"h2"
}

View file

@ -188,7 +188,7 @@ impl ToTokens for Component {
#name,
#builder,
#key_token,
#children
__cx.bump().alloc(#children)
)
})
}

View file

@ -23,37 +23,18 @@ pub struct Element {
impl ToTokens for Element {
fn to_tokens(&self, tokens: &mut TokenStream2) {
let name = &self.name;
let attr = &self.attributes;
let childs = &self.children;
let listeners = &self.listeners;
tokens.append_all(quote! {
__cx.element(dioxus_elements::#name)
});
// By gating these methods, we can keep the output of `cargo expand` readable.
// We also prevent the issue where zero-sized arrays fail to have propery type inference.
if self.attributes.len() > 0 {
let attr = &self.attributes;
tokens.append_all(quote! {
.attributes([ #(#attr),* ])
})
}
if self.children.len() > 0 {
let childs = &self.children;
tokens.append_all(quote! {
.children([ #(#childs),* ])
});
}
if self.listeners.len() > 0 {
let listeners = &self.listeners;
tokens.append_all(quote! {
.listeners([ #(#listeners),* ])
});
}
tokens.append_all(quote! {
.finish()
__cx.element(
dioxus_elements::#name,
__cx.bump().alloc([ #(#listeners),* ]),
__cx.bump().alloc([ #(#attr),* ]),
__cx.bump().alloc([ #(#childs),* ]),
None,
)
});
}
}
@ -83,7 +64,13 @@ impl Parse for Element {
}
if content.peek(Ident) && content.peek2(Token![:]) && !content.peek3(Token![:]) {
parse_element_body(&content, &mut attributes, &mut listeners, &mut key)?;
parse_element_body(
&content,
&mut attributes,
&mut listeners,
&mut key,
name.clone(),
)?;
} else {
children.push(content.parse::<Node>()?);
}
@ -110,6 +97,7 @@ impl Parse for Element {
/// Parse a VElement's Attributes
/// =======================================
struct ElementAttr {
element_name: Ident,
name: Ident,
value: AttrType,
namespace: Option<String>,
@ -130,6 +118,7 @@ fn parse_element_body(
attrs: &mut Vec<ElementAttr>,
listeners: &mut Vec<ElementAttr>,
key: &mut Option<AttrType>,
element_name: Ident,
) -> Result<()> {
let mut name = Ident::parse_any(stream)?;
let name_str = name.to_string();
@ -159,6 +148,7 @@ fn parse_element_body(
name,
value: ty,
namespace: None,
element_name: element_name.clone(),
});
return Ok(());
}
@ -186,6 +176,7 @@ fn parse_element_body(
name,
value: ty,
namespace: Some("style".to_string()),
element_name: element_name.clone(),
});
}
@ -227,13 +218,15 @@ fn parse_element_body(
name,
value: ty,
namespace: None,
element_name,
});
Ok(())
}
impl ToTokens for ElementAttr {
fn to_tokens(&self, tokens: &mut TokenStream2) {
let name = self.name.to_string();
let el_name = &self.element_name;
let name_str = self.name.to_string();
let nameident = &self.name;
let namespace = match &self.namespace {
@ -243,12 +236,22 @@ impl ToTokens for ElementAttr {
match &self.value {
AttrType::BumpText(value) => tokens.append_all(quote! {
__cx.attr(#name, format_args_f!(#value), #namespace)
dioxus_elements::#el_name.#nameident(__cx, format_args_f!(#value))
}),
// __cx.attr(#name, format_args_f!(#value), #namespace, false)
//
// AttrType::BumpText(value) => tokens.append_all(quote! {
// __cx.attr(#name, format_args_f!(#value), #namespace, false)
// }),
AttrType::FieldTokens(exp) => tokens.append_all(quote! {
__cx.attr(#name, #exp, #namespace)
dioxus_elements::#el_name.#nameident(__cx, #exp)
}),
// __cx.attr(#name_str, #exp, #namespace, false)
// AttrType::FieldTokens(exp) => tokens.append_all(quote! {
// dioxus_elements::#el_name.#nameident(__cx, format_args_f!(#value))
// __cx.attr(#name_str, #exp, #namespace, false)
// }),
// todo: move event handlers on to the elements or onto the nodefactory
AttrType::Event(event) => tokens.append_all(quote! {

View file

@ -97,12 +97,16 @@ impl ToTokens for RsxRender {
// The `in cx` pattern allows directly rendering
Some(ident) => out_tokens.append_all(quote! {
#ident.render(dioxus::prelude::LazyNodes::new(move |__cx: NodeFactory|{
use dioxus_elements::GlobalAttributes;
#inner
}))
}),
// Otherwise we just build the LazyNode wrapper
None => out_tokens.append_all(quote! {
dioxus::prelude::LazyNodes::new(move |__cx: NodeFactory|{
use dioxus_elements::GlobalAttributes;
#inner
})
}),

View file

@ -46,9 +46,14 @@ pub struct VFragment<'src> {
pub trait DioxusElement {
const TAG_NAME: &'static str;
const NAME_SPACE: Option<&'static str>;
#[inline]
fn tag_name(&self) -> &'static str {
Self::TAG_NAME
}
#[inline]
fn namespace(&self) -> Option<&'static str> {
Self::NAME_SPACE
}
}
pub struct VElement<'a> {
// tag is always static
@ -172,10 +177,33 @@ impl<'a> NodeFactory<'a> {
listeners: &[Listener],
attributes: &[Attribute],
children: &'a [VNode<'a>],
) {
) -> VNode<'a> {
todo!()
}
pub fn element() {}
pub fn element(
&self,
el: impl DioxusElement,
listeners: &'a [Listener<'a>],
attributes: &'a [Attribute<'a>],
children: &'a [VNode<'a>],
key: Option<&'a str>,
) -> VNode<'a> {
VNode {
dom_id: RealDomNode::empty_cell(),
key,
kind: VNodeKind::Element(self.bump().alloc(VElement {
tag_name: el.tag_name(),
namespace: el.namespace(),
static_listeners: false,
listeners,
static_attrs: false,
attributes,
static_children: false,
children,
})),
}
}
pub fn suspended() -> VNode<'static> {
VNode {
@ -202,7 +230,7 @@ impl<'a> NodeFactory<'a> {
}
}
pub fn virtual_child<P, C>(
pub fn virtual_child<P>(
&self,
component: FC<P>,
props: P,

View file

@ -19,48 +19,213 @@ use std::fmt::Arguments;
use dioxus_core::{nodes::Attribute, DioxusElement, NodeFactory};
trait GlobalAttributes {
fn accesskey<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> {
cx.attr("accesskey", val, None, false)
macro_rules! no_namespace_trait_methods {
(
$(
$(#[$attr:meta])*
$name:ident;
)*
) => {
$(
$(#[$attr])*
fn $name<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> {
cx.attr(stringify!(name), val, None, false)
}
)*
};
}
macro_rules! style_trait_methods {
(
$(
$(#[$attr:meta])*
$name:ident: $lit:literal,
)*
) => {
$(
$(#[$attr])*
fn $name<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> {
cx.attr($lit, val, Some("style"), false)
}
)*
};
}
macro_rules! aria_trait_methods {
(
$(
$(#[$attr:meta])*
$name:ident: $lit:literal,
)*
) => {
$(
$(#[$attr])*
fn $name<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> {
cx.attr($lit, val, None, false)
}
)*
};
}
pub trait GlobalAttributes {
no_namespace_trait_methods! {
accesskey;
class;
contenteditable;
data;
dir;
draggable;
hidden;
id;
lang;
spellcheck;
style;
tabindex;
title;
translate;
}
fn class<'a>(&self, cx: NodeFactory<'a>, val: Arguments<'a>) -> Attribute<'a> {
cx.attr("class", val, None, false)
style_trait_methods! {
background: "background",
background_attachment: "background-attachment",
/// ## Definition and Usage
///
/// The background-color property sets the background color of an element.
///
/// The background of an element is the total size of the element, including padding and border (but not the margin).
///
/// Tip: Use a background color and a text color that makes the text easy to read.
///
/// ## Example
///
/// ```
/// body {
/// style: {
/// background_color: "coral"
/// }
/// }
/// ```
background_color: "background-color",
background_image: "background-image",
background_position: "background-position",
background_repeat: "background-repeat",
border: "border",
border_bottom: "border-bottom",
border_bottom_color: "border-bottom-color",
border_bottom_style: "border-bottom-style",
border_bottom_width: "border-bottom-width",
border_color: "border-color",
border_left: "border-left",
border_left_color: "border-left-color",
border_left_style: "border-left-style",
border_left_width: "border-left-width",
border_right: "border-right",
border_right_color: "border-right-color",
border_right_style: "border-right-style",
border_right_width: "border-right-width",
border_style: "border-style",
border_top: "border-top",
border_top_color: "border-top-color",
border_top_style: "border-top-style",
border_top_width: "border-top-width",
border_width: "border-width",
clear: "clear",
clip: "clip",
color: "color",
cursor: "cursor",
display: "display",
filter: "filter",
css_float: "css-float",
font: "font",
font_family: "font-family",
font_size: "font-size",
font_variant: "font-variant",
font_weight: "font-weight",
height: "height",
left: "left",
letter_spacing: "letter-spacing",
line_height: "line-height",
list_style: "list-style",
list_style_image: "list-style-image",
list_style_position: "list-style-position",
list_style_type: "list-style-type",
margin: "margin",
margin_bottom: "margin-bottom",
margin_left: "margin-left",
margin_right: "margin-right",
margin_top: "margin-top",
overflow: "overflow",
padding: "padding",
padding_bottom: "padding-bottom",
padding_left: "padding-left",
padding_right: "padding-right",
padding_top: "padding-top",
page_break_after: "page-break-after",
page_break_before: "page-break-before",
position: "position",
stroke_dasharray: "stroke-dasharray",
stroke_dashoffset: "stroke-dashoffset",
text_align: "text-align",
text_decoration: "text-decoration",
text_indent: "text-indent",
text_transform: "text-transform",
top: "top",
vertical_align: "vertical-align",
visibility: "visibility",
width: "width",
z_index: "z-index",
}
fn contenteditable<'a>(&self, cx: NodeFactory<'a>, val: Arguments<'a>) -> Attribute<'a> {
cx.attr("contenteditable", val, None, false)
}
fn data<'a>(&self, cx: NodeFactory<'a>, val: Arguments<'a>) -> Attribute<'a> {
cx.attr("data", val, None, false)
}
fn dir<'a>(&self, cx: NodeFactory<'a>, val: Arguments<'a>) -> Attribute<'a> {
cx.attr("dir", val, None, false)
}
fn draggable<'a>(&self, cx: NodeFactory<'a>, val: Arguments<'a>) -> Attribute<'a> {
cx.attr("draggable", val, None, false)
}
fn hidden<'a>(&self, cx: NodeFactory<'a>, val: Arguments<'a>) -> Attribute<'a> {
cx.attr("hidden", val, None, false)
}
fn id<'a>(&self, cx: NodeFactory<'a>, val: Arguments<'a>) -> Attribute<'a> {
cx.attr("id", val, None, false)
}
fn lang<'a>(&self, cx: NodeFactory<'a>, val: Arguments<'a>) -> Attribute<'a> {
cx.attr("lang", val, None, false)
}
fn spellcheck<'a>(&self, cx: NodeFactory<'a>, val: Arguments<'a>) -> Attribute<'a> {
cx.attr("spellcheck", val, None, false)
}
fn style<'a>(&self, cx: NodeFactory<'a>, val: Arguments<'a>) -> Attribute<'a> {
cx.attr("style", val, Some("style"), false)
}
fn tabindex<'a>(&self, cx: NodeFactory<'a>, val: Arguments<'a>) -> Attribute<'a> {
cx.attr("tabindex", val, None, false)
}
fn title<'a>(&self, cx: NodeFactory<'a>, val: Arguments<'a>) -> Attribute<'a> {
cx.attr("title", val, None, false)
}
fn translate<'a>(&self, cx: NodeFactory<'a>, val: Arguments<'a>) -> Attribute<'a> {
cx.attr("translate", val, None, false)
aria_trait_methods! {
aria_current: "aria-current",
aria_details: "aria-details",
aria_disabled: "aria-disabled",
aria_hidden: "aria-hidden",
aria_invalid: "aria-invalid",
aria_keyshortcuts: "aria-keyshortcuts",
aria_label: "aria-label",
aria_roledescription: "aria-roledescription",
// Widget Attributes
aria_autocomplete: "aria-autocomplete",
aria_checked: "aria-checked",
aria_expanded: "aria-expanded",
aria_haspopup: "aria-haspopup",
aria_level: "aria-level",
aria_modal: "aria-modal",
aria_multiline: "aria-multiline",
aria_multiselectable: "aria-multiselectable",
aria_orientation: "aria-orientation",
aria_placeholder: "aria-placeholder",
aria_pressed: "aria-pressed",
aria_readonly: "aria-readonly",
aria_required: "aria-required",
aria_selected: "aria-selected",
aria_sort: "aria-sort",
aria_valuemax: "aria-valuemax",
aria_valuemin: "aria-valuemin",
aria_valuenow: "aria-valuenow",
aria_valuetext: "aria-valuetext",
// Live Region Attributes
aria_atomic: "aria-atomic",
aria_busy: "aria-busy",
aria_live: "aria-live",
aria_relevant: "aria-relevant",
aria_dropeffect: "aria-dropeffect",
aria_grabbed: "aria-grabbed",
// Relationship Attributes
aria_activedescendant: "aria-activedescendant",
aria_colcount: "aria-colcount",
aria_colindex: "aria-colindex",
aria_colspan: "aria-colspan",
aria_controls: "aria-controls",
aria_describedby: "aria-describedby",
aria_errormessage: "aria-errormessage",
aria_flowto: "aria-flowto",
aria_labelledby: "aria-labelledby",
aria_owns: "aria-owns",
aria_posinset: "aria-posinset",
aria_rowcount: "aria-rowcount",
aria_rowindex: "aria-rowindex",
aria_rowspan: "aria-rowspan",
aria_setsize: "aria-setsize",
}
}
@ -87,7 +252,7 @@ macro_rules! builder_constructors {
impl $name {
$(
pub fn $fil<'a>(&self, cx: NodeFactory<'a>, val: Arguments<'a>) -> Attribute<'a> {
pub fn $fil<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> {
cx.attr(stringify!($fil), val, None, false)
}
)*
@ -149,7 +314,7 @@ builder_constructors! {
rel: LinkType,
sizes: String, // FIXME
title: String, // FIXME
// type: Mime,
r#type: Mime,
};
/// Build a
@ -166,7 +331,7 @@ builder_constructors! {
/// [`<style>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/style)
/// element.
style {
// type: Mime,
r#type: Mime,
media: String, // FIXME media query
nonce: Nonce,
title: String, // FIXME
@ -307,6 +472,25 @@ builder_constructors! {
/// Build a
/// [`<div>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/div)
/// element.
///
/// ## Definition and Usage
/// - The <div> tag defines a division or a section in an HTML document.
/// - The <div> tag is used as a container for HTML elements - which is then styled with CSS or manipulated with JavaScript.
/// - The <div> tag is easily styled by using the class or id attribute.
/// - Any sort of content can be put inside the <div> tag!
///
/// Note: By default, browsers always place a line break before and after the <div> element.
///
/// ## Usage
/// ```
/// html!(<div> A header element </div>)
/// rsx!(div { "A header element" })
/// LazyNodes::new(|f| f.element(div, &[], &[], &[], None))
/// ```
///
/// ## References:
/// - https://developer.mozilla.org/en-US/docs/Web/HTML/Element/div
/// - https://www.w3schools.com/tags/tag_div.asp
div {};
/// Build a
@ -347,7 +531,7 @@ builder_constructors! {
ol {
reversed: Bool,
start: isize,
// type: OrderedListType,
r#type: OrderedListType,
};
/// Build a
@ -376,7 +560,7 @@ builder_constructors! {
href: Uri,
hreflang: LanguageTag,
target: Target,
// type: Mime,
r#type: Mime,
// ping: SpacedList<Uri>,
// rel: SpacedList<LinkType>,
};
@ -553,10 +737,10 @@ builder_constructors! {
autoplay: Bool,
controls: Bool,
crossorigin: CrossOrigin,
// loop: Bool,
muted: Bool,
preload: Preload,
src: Uri,
r#loop: Bool,
};
/// Build a
@ -601,7 +785,7 @@ builder_constructors! {
controls: Bool,
crossorigin: CrossOrigin,
height: usize,
// loop: Bool,
r#loop: Bool,
muted: Bool,
preload: Preload,
playsinline: Bool,
@ -619,7 +803,7 @@ builder_constructors! {
embed {
height: usize,
src: Uri,
// type: Mime,
r#type: Mime,
width: usize,
};
@ -647,7 +831,7 @@ builder_constructors! {
form: Id,
height: usize,
name: Id,
// type: Mime,
r#type: Mime,
typemustmatch: Bool,
usemap: String, // TODO should be a fragment starting with '#'
width: usize,
@ -671,7 +855,7 @@ builder_constructors! {
/// element.
source {
src: Uri,
// type: Mime,
r#type: Mime,
};
@ -701,8 +885,8 @@ builder_constructors! {
nonce: Nonce,
src: Uri,
text: String,
// async: Bool,
// type: String, // TODO could be an enum
r#async: Bool,
r#type: String, // TODO could be an enum
};
@ -807,7 +991,7 @@ builder_constructors! {
formnovalidate: Bool,
formtarget: Target,
name: Id,
// type: ButtonType,
r#type: ButtonType,
value: String,
};
@ -869,7 +1053,7 @@ builder_constructors! {
src: Uri,
step: String,
tabindex: usize,
// type: InputType,
r#type: InputType,
value: String,
width: isize,
};
@ -878,7 +1062,7 @@ builder_constructors! {
/// [`<label>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/label)
/// element.
label {
// for: Id,
r#for: Id,
form: Id,
};
@ -924,7 +1108,7 @@ builder_constructors! {
output {
form: Id,
name: Id,
// for: SpacedSet<Id>,
// r#for: SpacedSet<Id>,
};
/// Build a

View file

@ -151,6 +151,7 @@ mod tests {
use dioxus_core as dioxus;
use dioxus_core::prelude::*;
use dioxus_html as dioxus_elements;
use dioxus_html::GlobalAttributes;
const SIMPLE_APP: FC<()> = |cx| {
cx.render(rsx!(div {

View file

@ -21,4 +21,5 @@ mod dioxus_elements {
const TAG_NAME: &'static str = "div";
const NAME_SPACE: Option<&'static str> = None;
}
pub trait GlobalAttributes {}
}

View file

@ -188,6 +188,7 @@ pub use dioxus_webview as desktop;
pub mod prelude {
//! A glob import that includes helper types like FC, rsx!, html!, and required traits
pub use dioxus_core::prelude::*;
pub use dioxus_elements::GlobalAttributes;
pub use dioxus_hooks::*;
pub use dioxus_html as dioxus_elements;
}