use signals::signal::SignalExt;
use signals::signal_vec::{SignalVecExt, VecDiff};
use wasm_bindgen::UnwrapThrowExt;
pub mod traits;
use traits::*;
fn create_element(tag: &str) -> web_sys::Element {
crate::document()
.create_element(tag)
.expect_throw("create_element(tag)")
}
fn create_text_node(text: &str) -> web_sys::Text {
crate::document().create_text_node(text)
}
fn spawn_for_each<S, F>(signal: S, f: F)
where
S: signals::signal::Signal + 'static,
F: Fn(S::Item) + 'static,
{
let f = signal.for_each(move |value| {
f(value);
futures_util::future::ready(())
});
wasm_bindgen_futures::futures_0_3::spawn_local(f);
}
fn spawn_for_each_vec<S, F>(signal: S, f: F)
where
S: signals::signal_vec::SignalVec + 'static,
F: Fn(VecDiff<S::Item>) + 'static,
{
let f = signal.for_each(move |value| {
f(value);
futures_util::future::ready(())
});
wasm_bindgen_futures::futures_0_3::spawn_local(f);
}
impl<T: Element> GlobalAttributes for T {}
pub enum InputType {
Button,
CheckBox,
Color,
Date,
DateTimeLocal,
Email,
File,
Hidden,
Image,
Month,
Number,
Password,
Radio,
Range,
Reset,
Search,
Submit,
Tel,
Text,
Time,
Url,
Week,
}
impl InputType {
fn as_str(&self) -> &str {
match self {
InputType::Button => "button",
InputType::CheckBox => "checkbox",
InputType::Color => "color",
InputType::Date => "date",
InputType::DateTimeLocal => "datetime-local",
InputType::Email => "email",
InputType::File => "file",
InputType::Hidden => "hidden",
InputType::Image => "image",
InputType::Month => "month",
InputType::Number => "number",
InputType::Password => "password",
InputType::Radio => "radio",
InputType::Range => "range",
InputType::Reset => "reset",
InputType::Search => "search",
InputType::Submit => "submit",
InputType::Tel => "tel",
InputType::Text => "text",
InputType::Time => "time",
InputType::Url => "url",
InputType::Week => "week",
}
}
}
macro_rules! define_html_elements {
($(
$([mdn=$doc_link:expr])? $html_element_tag:ident $HtmlElementType:ident $([$($tt:tt)+])?
),+) => {
pub enum Node {
$(
$HtmlElementType($HtmlElementType),
)+
NodeList(NodeList),
}
impl Node {
pub(crate) fn append_to(&self, parent: &web_sys::Node) {
match self {
$(
Node::$HtmlElementType(element) => element.append_to(parent),
)+
Node::NodeList(list) => list.append_to(parent),
}
}
pub(crate) fn insert_at(&self, index: usize, parent: &web_sys::Node) {
match self {
$(
Node::$HtmlElementType(element) => element.insert_at(index, parent),
)+
Node::NodeList(_) => {
log::info!("Node::NodeList::remove_from do nothing");
}
}
}
pub(crate) fn remove_from(&self, parent: &web_sys::Node) {
match self {
$(
Node::$HtmlElementType(element) => element.remove_from(parent),
)+
Node::NodeList(_) => {
log::info!("Node::NodeList::remove_from do nothing");
}
}
}
}
$(
define_html_elements!{ @doc $($doc_link)?, $html_element_tag $HtmlElementType $([$($tt)+])? }
)+
};
( @doc $doc_link:expr, $html_element_tag:ident $HtmlElementType:ident $([$($tt:tt)+])? ) => {
define_html_elements!{ @one_element $doc_link, $html_element_tag $HtmlElementType $([$($tt)+])? }
};
( @doc , $html_element_tag:ident $HtmlElementType:ident $([$($tt:tt)+])? ) => {
define_html_elements!{ @one_element
concat!("https://developer.mozilla.org/en-US/docs/Web/HTML/Element/", stringify!($html_element_tag)),
$html_element_tag $HtmlElementType $([$($tt)+])?
}
};
(@one_element $doc_link:expr, $html_element_tag:ident $HtmlElementType:ident) => {
#[doc = $doc_link]
pub struct $HtmlElementType {
websys_element: web_sys::Element,
listeners: Vec<Box<crate::events::Listener>>,
}
impl $HtmlElementType {
pub fn new() -> Self {
Self {
websys_element: create_element(stringify!($html_element_tag)),
listeners: Vec::new(),
}
}
}
impl Default for $HtmlElementType {
fn default() -> Self {
Self::new()
}
}
impl From<$HtmlElementType> for Node {
fn from(item: $HtmlElementType) -> Self {
Node::$HtmlElementType(item)
}
}
impl Element for $HtmlElementType {
fn websys_element(&self) -> &web_sys::Element {
&self.websys_element
}
fn websys_node(&self) -> &web_sys::Node {
self.websys_element.as_ref()
}
fn store_future(&mut self) {
}
fn listeners_mut(&mut self) -> &mut Vec<Box<crate::events::Listener>> {
&mut self.listeners
}
}
};
(@one_element $doc_link:expr, $html_element_tag:ident $HtmlElementType:ident [$($tt:tt)+]) => {
#[doc = $doc_link]
pub struct $HtmlElementType {
websys_element: web_sys::Element,
listeners: Vec<Box<crate::events::Listener>>,
node_list: std::sync::Arc<std::sync::Mutex<NodeList>>,
}
impl $HtmlElementType {
pub fn new() -> Self {
Self {
websys_element: create_element(stringify!($html_element_tag)),
listeners: Vec::new(),
node_list: std::sync::Arc::new(std::sync::Mutex::new(NodeList::new())),
}
}
pub fn child<T: Attachable + Into<Node> + $($tt)+>(self, child: T) -> Self {
self.push_child(child)
}
}
impl Default for $HtmlElementType {
fn default() -> Self {
Self::new()
}
}
impl From<$HtmlElementType> for Node {
fn from(item: $HtmlElementType) -> Self {
Node::$HtmlElementType(item)
}
}
impl Element for $HtmlElementType {
fn websys_element(&self) -> &web_sys::Element {
&self.websys_element
}
fn websys_node(&self) -> &web_sys::Node {
self.websys_element.as_ref()
}
fn store_future(&mut self) {
}
fn listeners_mut(&mut self) -> &mut Vec<Box<crate::events::Listener>> {
&mut self.listeners
}
}
impl HasChildren for $HtmlElementType {
fn node_list(&self) -> std::sync::Arc<std::sync::Mutex<NodeList>> {
std::sync::Arc::clone(&self.node_list)
}
}
};
}
macro_rules! implement_marker_trait_for {
($($TraitName:ident {$($ElementType:ident)+})+) => {
$(
$(
impl $TraitName for $ ElementType {}
)+
)+
}
}
define_html_elements! {
a A [ChildOfA],
abbr Abbr [PhrasingContent],
address Address [FlowContent],
area Area,
article Article [FlowContent],
aside Aside [FlowContent],
audio Audio [ChildOfAudioVideo],
b B [PhrasingContent],
bdi Bdi [PhrasingContent],
bdo Bdo [PhrasingContent],
blockquote BlockQuote [FlowContent],
br Br,
button Button [PhrasingNonInteractiveContent],
canvas Canvas [Element],
caption Caption [FlowContent],
cite Cite [PhrasingContent],
code Code [PhrasingContent],
col Col,
colgroup ColGroup [TraitCol],
data Data [PhrasingContent],
datalist DataList [ChildOfDataList],
dd Dd [FlowContent],
del Del [Element],
details Details [ChildOfDetails],
dfn Dfn [PhrasingContent],
dialog Dialog [FlowContent],
div Div [FlowContent],
dl Dl [ChildOfDl],
dt Dt [FlowContent],
em Em [PhrasingContent],
embed Embed,
fieldset FieldSet [ChildOfFieldSet],
figcaption FigCaption [FlowContent],
figure Figure [ChildOfFigure],
footer Footer [FlowContent],
form Form [FlowContent],
h1 H1 [PhrasingContent],
h2 H2 [PhrasingContent],
h3 H3 [PhrasingContent],
h4 H4 [PhrasingContent],
h5 H5 [PhrasingContent],
h6 H6 [PhrasingContent],
header Header [FlowContent],
hgroup Hgroup [Headings],
hr Hr,
i I [PhrasingContent],
iframe Iframe [Element],
img Img,
input Input,
ins Ins [Element],
kbd Kbd [PhrasingContent],
label Label [PhrasingContent],
legend Legend [PhrasingContent],
li Li [FlowContent],
main Main [FlowContent],
map Map [Element],
mark Mark [PhrasingContent],
[mdn = "https://developer.mozilla.org/en-US/docs/Web/MathML/Element/math"]
math Math,
meter Meter [PhrasingContent],
nav Nav [FlowContent],
object Object [Element],
ol Ol [TraitLi],
optgroup OptGroup [TraitOption],
option Option,
output Output [PhrasingContent],
p P [PhrasingContent],
param Param,
picture Picture [ChildOfPicture],
pre Pre [PhrasingContent],
progress Progress [PhrasingContent],
q Q [PhrasingContent],
rp Rp,
rt Rt [PhrasingContent],
rtc Rtc [ChildOfRtc],
ruby Ruby [PhrasingContent],
s S [PhrasingContent],
samp Samp [PhrasingContent],
section Section [FlowContent],
select Select [ChildOfSelect],
slot Slot [Element],
small Small [PhrasingContent],
source Source,
span Span [PhrasingContent],
strong Strong [PhrasingContent],
sub Sub [PhrasingContent],
summary Summary [ChildOfSummary],
sup Sup [PhrasingContent],
[mdn = "https://developer.mozilla.org/en-US/docs/Web/SVG/Element/svg"]
svg Svg,
table Table [ChildOfTable],
tbody Tbody [TraitTr],
td Td [FlowContent],
template Template [Element],
textarea TextArea,
tfoot Tfoot [TraitTr],
th Th [FlowContent],
thead Thead [TraitTr],
time Time [PhrasingContent],
tr Tr [ChildOfTr],
track Track,
u U [PhrasingContent],
ul Ul [TraitLi],
var Var [PhrasingContent],
video Video [ChildOfAudioVideo],
wbr Wbr
}
implement_marker_trait_for! {
EmbeddedContent { Audio Canvas Embed Iframe Img Math Object Picture Svg Video }
FlowContent { A Abbr Address Article Aside Audio B Bdi Bdo BlockQuote Br Button
Canvas Cite Code Data DataList Del Details Dfn Div Dl
Em Embed FieldSet Figure Footer Form
H1 H2 H3 H4 H5 H6 Header Hgroup Hr
I Iframe Img Input Ins Kbd Label
Main Map Mark Math Meter Nav
Object Ol Output P Picture Pre Progress Q Ruby
S Samp Section Select Small Span Strong Sub Sup Svg
Table Template TextArea Time
U Ul Var Video Wbr
}
FlowNonInteractiveContent { Abbr Address Article Aside Audio
B Bdi Bdo BlockQuote Br Canvas Cite Code
Data DataList Del Dfn Div Dl Em FieldSet Figure Footer Form
H1 H2 H3 H4 H5 H6 Header Hgroup Hr I Img Input Ins Kbd
Main Map Mark Math Meter Nav
Object Ol Output P Picture Pre Progress Q Ruby
S Samp Section Small Span Strong Sub Sup Svg
Table Template Time U Ul Var Video Wbr
}
FormContent { Button FieldSet Input Label Meter Object Output Progress Select TextArea }
FormListedContent { Button FieldSet Input Object Output Select TextArea }
FormLabelableContent { Button Input Meter Output Progress Select TextArea }
FormSubmittableContent { Button Input Object Select TextArea }
FormResettableContent { Input Output Select TextArea }
HeadingContent { H1 H2 H3 H4 H5 H6 Hgroup }
InteractiveContent { A Button Details Embed Iframe Label Select TextArea }
PhrasingContent {
A Area Del Ins Map
Abbr Audio B Bdo Br Button Canvas Cite Code
Data DataList Dfn Em Embed I Iframe Img Input
Kbd Label Mark Math Meter Object Output Picture Progress Q Ruby
Samp Select Small Span Strong Sub Svg TextArea Time U Var Video
}
PhrasingNonInteractiveContent {
Abbr Audio B Bdo Br Canvas Cite Code
Data DataList Dfn Em I Img Input
Kbd Mark Math Meter Object Output Picture Progress Q Ruby
Samp Small Span Strong Sub Svg Time U Var Video
}
SectioningContent { Article Aside Nav Section }
TextContent { Option Rp}
Headings { H1 H2 H3 H4 H5 H6 }
TraitA { A }
TraitCol { Col }
TraitInput { Input }
TraitLabel { Label }
TraitLi { Li }
TraitOption { Option }
TraitTextArea { TextArea }
ChildOfAudioVideo { Source Track }
ChildOfDataList { Option }
ChildOfDetails { Summary }
ChildOfDl { Div Dd Dt }
ChildOfFieldSet { Legend }
ChildOfFigure { FigCaption }
ChildOfPicture { Img Source }
ChildOfRtc { Rt }
ChildOfSelect { Option OptGroup }
ChildOfSummary { H1 H2 H3 H4 H5 H6 Hgroup }
ChildOfTable { Caption ColGroup Thead Tbody Tr Tfoot }
ChildOfTr { Td Th }
}
pub struct NodeList {
nodes: Vec<Node>,
}
impl Attachable for NodeList {
fn append_to(&self, parent: &web_sys::Node) {
self.nodes.iter().for_each(|n| {
n.append_to(parent);
})
}
fn insert_at(&self, _: usize, _: &web_sys::Node) {
log::info!("Attachable::insert_at(self: &NodeList) do nothing!");
}
fn replace_at(&self, _: usize, _: &web_sys::Node) {
log::info!("Attachable::replace_at(self: &NodeList) do nothing!");
}
fn remove_from(&self, _: &web_sys::Node) {
log::info!("Attachable::remove_from(self: &NodeList) do nothing!");
}
}
impl NodeList {
pub fn new() -> Self {
Self { nodes: Vec::new() }
}
pub fn child<T>(mut self, child: T) -> Self
where
T: Into<Node>,
{
self.nodes.push(child.into());
self
}
}
impl Default for NodeList {
fn default() -> Self {
Self::new()
}
}
impl From<NodeList> for Node {
fn from(item: NodeList) -> Self {
Node::NodeList(item)
}
}
pub trait HasChildren: Element + Sized {
fn node_list(&self) -> std::sync::Arc<std::sync::Mutex<NodeList>>;
fn push_child<T>(self, child: T) -> Self
where
T: Attachable + Into<Node>,
{
child.append_to(self.websys_node());
self.node_list()
.lock()
.expect_throw("self.node_list().lock()")
.nodes
.push(child.into());
self
}
fn list_signal<S, T, F, N>(self, signal: S, render_item: F) -> Self
where
T: Clone + 'static,
S: signals::signal_vec::SignalVec<Item = T> + 'static,
F: Fn(T) -> N + 'static,
N: crate::dom::Element + Into<crate::dom::Node>,
{
let parent_node = self.websys_node().clone();
let child_list = self.node_list();
debug_assert_eq!(
0,
child_list
.lock()
.expect_throw("child_list.lock()")
.nodes
.len()
);
spawn_for_each_vec(signal, move |change| {
let mut child_list = child_list.lock().expect_throw("child_list.lock()");
match change {
VecDiff::Replace { values } => {
parent_node.set_text_content(None);
child_list.nodes.clear();
values.into_iter().for_each(|item| {
let child = render_item(item);
child.append_to(&parent_node);
child_list.nodes.push(child.into());
});
}
VecDiff::Push { value } => {
let child = render_item(value);
child.append_to(&parent_node);
child_list.nodes.push(child.into());
}
VecDiff::Pop {} => {
debug_assert!(!child_list.nodes.is_empty());
child_list
.nodes
.pop()
.expect_throw("child_list.nodes.pop()")
.remove_from(&parent_node);
}
VecDiff::InsertAt { index, value } => {
let child = render_item(value);
child.insert_at(index, &parent_node);
child_list.nodes.insert(index, child.into());
}
VecDiff::RemoveAt { index } => {
child_list.nodes.remove(index).remove_from(&parent_node);
}
VecDiff::UpdateAt { index, value } => {
let child = render_item(value);
child.replace_at(index, &parent_node);
}
VecDiff::Move {
old_index,
new_index,
} => {
let moved = child_list.nodes.remove(old_index);
moved.remove_from(&parent_node);
moved.insert_at(new_index, &parent_node);
child_list.nodes.insert(new_index, moved);
}
VecDiff::Clear {} => {
parent_node.set_text_content(None);
child_list.nodes.clear();
}
}
});
self
}
}