use crate::vdom::Mailbox;
use core::convert::AsRef;
use pulldown_cmark;
use std::{collections::HashMap, fmt};
use wasm_bindgen::{closure::Closure, JsCast};
use web_sys;
#[derive(Debug, Clone, PartialEq)]
pub enum Namespace {
Html,
Svg,
MathMl,
Xul,
Xbl,
Custom(String),
}
impl Namespace {
pub fn as_str(&self) -> &str {
use self::Namespace::*;
match self {
Html => "http://www.w3.org/1999/xhtml",
Svg => "http://www.w3.org/2000/svg",
MathMl => "http://www.w3.org/1998/mathml",
Xul => "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
Xbl => "http://www.mozilla.org/xbl",
Custom(s) => s,
}
}
}
impl From<String> for Namespace {
fn from(ns: String) -> Self {
match ns.as_ref() {
"http://www.w3.org/1999/xhtml" => Namespace::Html,
"http://www.w3.org/2000/svg" => Namespace::Svg,
"http://www.w3.org/1998/mathml" => Namespace::MathMl,
"http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" => Namespace::Xul,
"http://www.mozilla.org/xbl" => Namespace::Xbl,
_ => Namespace::Custom(ns),
}
}
}
pub fn simple_ev<Ms, T>(trigger: T, message: Ms) -> Listener<Ms>
where
Ms: Clone + 'static,
T: ToString,
{
let handler = || message;
let closure = move |_| handler.clone()();
Listener::new(&trigger.to_string(), Some(Box::new(closure)))
}
pub fn input_ev<Ms, T: ToString>(
trigger: T,
mut handler: impl FnMut(String) -> Ms + 'static,
) -> Listener<Ms> {
let closure = move |event: web_sys::Event| {
if let Some(target) = event.target() {
return handler(util::input_value(&target));
}
handler(String::new())
};
Listener::new(&trigger.to_string(), Some(Box::new(closure)))
}
pub fn raw_ev<Ms, T: ToString>(
trigger: T,
mut handler: impl FnMut(web_sys::Event) -> Ms + 'static,
) -> Listener<Ms> {
let closure = move |event: web_sys::Event| handler(event);
Listener::new(&trigger.to_string(), Some(Box::new(closure)))
}
pub fn keyboard_ev<Ms, T: ToString>(
trigger: T,
mut handler: impl FnMut(web_sys::KeyboardEvent) -> Ms + 'static,
) -> Listener<Ms> {
let closure = move |event: web_sys::Event| {
handler(event.dyn_ref::<web_sys::KeyboardEvent>().unwrap().clone())
};
Listener::new(&trigger.to_string(), Some(Box::new(closure)))
}
pub fn mouse_ev<Ms, T: ToString>(
trigger: T,
mut handler: impl FnMut(web_sys::MouseEvent) -> Ms + 'static,
) -> Listener<Ms> {
let closure = move |event: web_sys::Event| {
handler(event.dyn_ref::<web_sys::MouseEvent>().unwrap().clone())
};
Listener::new(&trigger.to_string(), Some(Box::new(closure)))
}
type EventHandler<Ms> = Box<FnMut(web_sys::Event) -> Ms>;
pub struct Listener<Ms> {
pub trigger: Ev,
pub handler: Option<EventHandler<Ms>>,
pub closure: Option<Closure<FnMut(web_sys::Event)>>,
pub control: bool,
pub id: Option<u32>,
}
impl<Ms> fmt::Debug for Listener<Ms> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"Listener {{ trigger:{:?}, handler:{:?}, closure:{:?}, control:{:?}, id:{:?} }}",
self.trigger,
fmt_hook_fn(&self.handler),
fmt_hook_fn(&self.closure),
self.control,
self.id
)
}
}
impl<Ms> Listener<Ms> {
pub fn new(trigger: &str, handler: Option<EventHandler<Ms>>) -> Self {
Self {
trigger: trigger.into(),
handler,
closure: None,
control: false,
id: None,
}
}
pub fn new_control() -> Self {
Self {
trigger: dom_types::Ev::Input,
handler: None,
closure: None,
control: true,
id: None,
}
}
pub fn attach<T>(&mut self, el_ws: &T, mailbox: Mailbox<Ms>)
where
T: AsRef<web_sys::EventTarget>,
{
let mut handler = self.handler.take().expect("Can't find old handler");
let closure =
Closure::wrap(
Box::new(move |event: web_sys::Event| mailbox.send(handler(event)))
as Box<FnMut(web_sys::Event) + 'static>,
);
(el_ws.as_ref() as &web_sys::EventTarget)
.add_event_listener_with_callback(
self.trigger.as_str(),
closure.as_ref().unchecked_ref(),
)
.expect("problem adding listener to element");
self.closure = Some(closure);
}
pub fn detach<T>(&self, el_ws: &T)
where
T: AsRef<web_sys::EventTarget>,
{
let closure = self.closure.as_ref().expect("Can't find closure to detach");
(el_ws.as_ref() as &web_sys::EventTarget)
.remove_event_listener_with_callback(
&self.trigger.as_str(),
closure.as_ref().unchecked_ref(),
)
.expect("problem removing listener from element");
}
}
impl<Ms> PartialEq for Listener<Ms> {
fn eq(&self, other: &Self) -> bool {
self.trigger == other.trigger && self.id == other.id
}
}
pub trait UpdateEl<T> {
fn update(self, el: &mut T);
}
impl<Ms> UpdateEl<El<Ms>> for Attrs {
fn update(self, el: &mut El<Ms>) {
el.attrs = self;
}
}
impl<Ms> UpdateEl<El<Ms>> for &Attrs {
fn update(self, el: &mut El<Ms>) {
el.attrs = self.clone();
}
}
impl<Ms> UpdateEl<El<Ms>> for Style {
fn update(self, el: &mut El<Ms>) {
el.style = self;
}
}
impl<Ms> UpdateEl<El<Ms>> for &Style {
fn update(self, el: &mut El<Ms>) {
el.style = self.clone();
}
}
impl<Ms> UpdateEl<El<Ms>> for Listener<Ms> {
fn update(self, el: &mut El<Ms>) {
el.listeners.push(self)
}
}
impl<Ms> UpdateEl<El<Ms>> for Vec<Listener<Ms>> {
fn update(self, el: &mut El<Ms>) {
for listener in self.into_iter() {
el.listeners.push(listener)
}
}
}
impl<Ms> UpdateEl<El<Ms>> for DidMount {
fn update(self, el: &mut El<Ms>) {
el.hooks.did_mount = Some(self.actions)
}
}
impl<Ms> UpdateEl<El<Ms>> for DidUpdate {
fn update(self, el: &mut El<Ms>) {
el.hooks.did_update = Some(self.actions)
}
}
impl<Ms> UpdateEl<El<Ms>> for WillUnmount {
fn update(self, el: &mut El<Ms>) {
el.hooks.will_unmount = Some(self.actions)
}
}
impl<Ms> UpdateEl<El<Ms>> for &str {
fn update(self, el: &mut El<Ms>) {
el.children.push(El::new_text(self))
}
}
impl<Ms> UpdateEl<El<Ms>> for Vec<El<Ms>> {
fn update(self, el: &mut El<Ms>) {
for child in self.into_iter() {
el.children.push(child);
}
}
}
impl<Ms> UpdateEl<El<Ms>> for El<Ms> {
fn update(self, el: &mut El<Ms>) {
el.children.push(self)
}
}
impl<Ms> UpdateEl<El<Ms>> for Tag {
fn update(self, el: &mut El<Ms>) {
el.tag = self;
}
}
macro_rules! make_attrs {
{ $($attr_camel:ident => $attr:expr),+ } => {
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum At {
$(
$attr_camel,
)+
Custom(String)
}
impl At {
pub fn as_str(&self) -> &str {
match self {
$ (
At::$attr_camel => $attr,
) +
At::Custom(val) => &val
}
}
}
impl From<&str> for At {
fn from(attr: &str) -> Self {
match attr {
$ (
$attr => At::$attr_camel,
) +
_ => {
At::Custom(attr.to_owned())
}
}
}
}
impl From<String> for At {
fn from(attr: String) -> Self {
match attr.as_ref() {
$ (
$attr => At::$attr_camel,
) +
_ => {
At::Custom(attr)
}
}
}
}
}
}
make_attrs! {
Accept => "accept", AcceptCharset => "accept-charset", AccessKey => "accesskey", Action => "action",
Alt => "alt", Async => "async", AutoComplete => "autocomplete", AutoFocus => "autofocus",
AutoPlay => "autoplay", Charset => "charset", Checked => "checked", Cite => "cite", Class => "class",
Color => "color", Cols => "cols", ColSpan => "colspan", Content => "content", ContentEditable => "contenteditable",
Controls => "controls", Coords => "coords", Data => "data", DateTime => "datetime", Default => "default",
Defer => "defer", Dir => "dir", DirName => "dirname", Disabled => "disabled", Download => "download",
Draggable => "draggable", DropZone => "dropzone", EncType => "enctype", For => "for", Form => "form",
FormAction => "formaction", Headers => "headers", Height => "height", Hidden => "hidden", High => "high",
Href => "href", Hreflang => "hreflang", HttpEquiv => "http-equiv", Id => "id", IsMap => "ismap",
Kind => "kind", Label => "label", Lang => "lang", List => "list", Loop => "loop", Low => "low",
Max => "max", MaxLength => "maxlength", Media => "media", Method => "method", Min => "min", Multiple => "multiple",
Muted => "muted", Name => "name", NoValidate => "novalidate", OnAbort => "onabort", OnAfterPrint => "onafterprint",
OnBeforePrint => "onbeforeprint", OnBeforeUnload => "onbeforeunload", OnBlur => "onblur", OnCanPlay => "oncanplay",
OnCanPlayThrough => "oncanplaythrough", OnChange => "onchange", OnClick => "onclick", OnContextMenu => "oncontextmenu",
OnCopy => "oncopy", OnCueChange => "oncuechange", OnCut => "oncut", OnDblClick => "ondblclick",
OnDrag => "ondrag", OnDragend => "ondragend", OnDragEnter => "ondragenter", OnDragLeave => "ondragleave",
OnDragOver => "ondragover", OnDragStart => "ondragstart", OnDrop => "ondrop", OnDurationChange => "ondurationchange",
OnEmptied => "onemptied", OnEnded => "onended", OnError => "onerror", OnFocus => "onfocus",
OnHashChange => "onhashchange", OnInput => "oninput", OnInvalid => "oninvalid", OnKeyDown => "onkeydown",
OnKeyPress => "onkeypress", OnKeyUp => "onkeyup", OnLoad => "onload", OnLoadedData => "onloadeddata",
OnLoadedMetaData => "onloadedmetadata", OnLoadStart => "onloadstart", OnMouseDown => "onmousedown",
OnMouseMove => "onmousemove", OnMouseOut => "onmouseout", OnMouseOver => "onmouseover", OnMouseUp => "onmouseup",
OnMouseWheel => "onmousewheel", OnOffline => "onoffline", OnOnline => "ononline", OnPageHide => "onpagehide",
OnPageShow => "onpageshow", OnPaste => "onpaste", OnPause => "onpause", OnPlay => "onplay",
OnPlaying => "onplaying", OnPopstate => "onpopstate", OnProgress => "onprogress", OnRateChangen => "onratechange",
OnRest => "onreset", OnResize => "onresize", OnScroll => "onscroll", OnSearch => "onsearch",
OnSeeked => "onseeked", OnSeeking => "onseeking", OnSelect => "onselect", OnStalled => "onstalled",
OnStorage => "onstorage", OnSubmit => "onsubmit", Onsuspend => "onsuspend", OnTimeUpdate => "ontimeupdate",
OnToggle => "ontoggle", OnUnload => "onunload", OnVolumeChange => "onvolumechange", OnWaiting => "onwaiting",
OnWheel => "onwheel", Open => "open", Optimum => "optimum", Pattern => "pattern", PlaceHolder => "placeholder",
Poster => "poster", Preload => "preload", ReadOnly => "readonly", Rel => "rel", Required => "required",
Reversed => "reversed", Rows => "rows", RowSpan => "rowspan", Sandbox => "sandbox", Scope => "scope",
Selected => "selected", Shape => "shape", Size => "size", Span => "span", SpellCheck => "spellcheck",
Src => "src", SrcDoc => "srcdoc", SrcLang => "srclang", SrcSet => "srcset", Start => "start",
Step => "step", Style => "style", TabIndex => "tabindex", Target => "target", Title => "title",
Translate => "translate", Type => "type", UseMap => "usemap", Value => "value", Width => "width",
Wrap => "wrap",
Dummy => "dummy-element",
AccentHeight => "accent-height", Accumulate => "accumulate", Additive => "additive",
AlignmentBaseline => "alignment-baseline", AllowReorder => "allowReorder", Amplitude => "amplitude",
ArabicForm => "arabic-form", Ascent => "ascent", AttributeName => "attributeName", attributeType => "attributeType",
AutoReverse => "autoReverse", Azimuth => "azimumth", BaseFrequenc => "baseFrequency", BaselineShift => "baseline-shift",
BaseProfile => "baseProfile", Bbox => "bbox", Begin => "begin", Bias => "bias", By => "by",
CalcMode => "calcMode", CapHeight => "cap-height", Clip => "clip",
Path => "path", D => "d", Xmlns => "xmlns", ViewBox => "ViewBox", Fill => "fill"
}
macro_rules! make_styles {
{ $($st_camel:ident => $st:expr),+ } => {
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum St {
$(
$st_camel,
)+
Custom(String)
}
impl St {
pub fn as_str(&self) -> &str {
match self {
$ (
St::$st_camel => $st,
) +
St::Custom(val) => &val
}
}
}
impl From<&str> for St {
fn from(st: &str) -> Self {
match st {
$ (
$st => St::$st_camel,
) +
_ => {
crate::log(&format!("Can't find this attribute: {}", st));
St::Background
}
}
}
}
impl From<String> for St {
fn from(st: String) -> Self {
match st.as_ref() {
$ (
$st => St::$st_camel,
) +
_ => {
crate::log(&format!("Can't find this attribute: {}", st));
St::Background
}
}
}
}
}
}
make_styles! {
AdditiveSymbols => "additive-symbols", AlignContent => "align-content", AlignItems => "align-items",
AlignSelf => "align-self", All => "all", Angle => "angle", Animation => "animation", AnimationDelay => "animation-delay",
AnimationDirection => "animation-direction", AnimationDuration => "animation-duration",
AnimationFillMode => "animation-fill-mode", AnimationIterationCount => "animation-iteration-count",
AnimationName => "animation-name", AnimationPlayState => "animation-play-state",
Background => "background", BackgroundAttachment => "background-attachment", BackgroundColor => "background-color",
BackgroundImage => "background-image", BackgroundPosition => "background-position", BackgroundRepeat => "background-repeat",
Border => "border", BorderBottom => "border-bottom", BorderBottomColor => "border-bottom-color",
BorderBottomStyle => "border-bottom-style", BorderBottomWidth => "border-bottom-width", BorderColor => "border-color"
}
#[derive(Clone, Debug, PartialEq)]
pub struct Attrs {
pub vals: HashMap<At, String>,
}
impl Attrs {
pub fn new(vals: HashMap<At, String>) -> Self {
Self { vals }
}
pub fn empty() -> Self {
Self {
vals: HashMap::new(),
}
}
pub fn from_id(name: &str) -> Self {
let mut result = Self::empty();
result.add(At::Id, name);
result
}
pub fn to_string(&self) -> String {
self.vals
.iter()
.map(|(k, v)| format!("{}=\"{}\"", k.as_str(), v))
.collect::<Vec<_>>()
.join(" ")
}
pub fn add(&mut self, key: At, val: &str) {
self.vals.insert(key, val.to_string());
}
pub fn add_multiple(&mut self, key: At, items: Vec<&str>) {
self.add(key, &items.join(" "));
}
pub fn merge(&self, other: &Self) -> Self {
let mut result = self.clone();
for (key, val) in &other.vals {
result.vals.insert(key.clone(), val.clone());
}
result
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct Style {
pub vals: HashMap<String, String>,
}
impl Style {
pub fn new(vals: HashMap<String, String>) -> Self {
let mut new_vals = HashMap::new();
for (key, val) in vals.into_iter() {
let val_backup = val.clone();
match val.parse::<u32>() {
Ok(_) => new_vals.insert(key, val_backup + "px"),
Err(_) => new_vals.insert(key, val_backup),
};
}
Self { vals: new_vals }
}
pub fn empty() -> Self {
Self {
vals: HashMap::new(),
}
}
pub fn add(&mut self, key: &str, val: &str) {
self.vals.insert(key.to_string(), val.to_string());
}
pub fn merge(&self, other: &Self) -> Self {
let mut result = self.clone();
for (key, val) in &other.vals {
result.vals.insert(key.clone(), val.clone());
}
result
}
}
impl ToString for Style {
fn to_string(&self) -> String {
if self.vals.keys().len() > 0 {
self.vals
.iter()
.map(|(k, v)| format!("{}:{}", k, v))
.collect::<Vec<_>>()
.join(";")
} else {
String::new()
}
}
}
macro_rules! make_events {
{ $($event_camel:ident => $event:expr),+ } => {
#[derive(Clone, Debug, PartialEq)]
pub enum Ev {
$(
$event_camel,
)+
}
impl Ev {
pub fn as_str(&self) -> &str {
match self {
$ (
Ev::$event_camel => $event,
) +
}
}
}
impl From<&str> for Ev {
fn from(event: &str) -> Self {
match event {
$ (
$event => Ev::$event_camel,
) +
_ => {
crate::log(&format!("Can't find this event: {}", event));
Ev::Click
}
}
}
}
impl From<String> for Ev {
fn from(event: String) -> Self {
match event.as_ref(){
$ (
$event => Ev::$event_camel,
) +
_ => {
crate::log(&format!("Can't find this event: {}", event));
Ev::Click
}
}
}
}
impl ToString for Ev {
fn to_string( & self ) -> String {
match self {
$ (
Ev::$ event_camel => $ event.into(),
) +
}
}
}
}
}
make_events! {
Cached => "cached", Error => "error", Abort => "abort", Load => "load", BeforeUnload => "beforeunload",
Unload => "unload", Online => "online", Offline => "offline", Focus => "focus", Blur => "blur",
Open => "open", Message => "message", Close => "close", PageHide => "pagehide",
PageShow => "pageshow", PopState => "popstate", AnimationStart => "animationstart", AnimationEnd => "animationend",
AnimationIteration => "animationiteration", TransitionStart => "transtionstart", TransitionEnd => "transitionend",
TranstionRun => "transitionrun",
Rest => "rest", Submit => "submit", BeforePrint => "beforeprint", AfterPrint => "afterprint",
CompositionStart => "compositionstart", CompositionUpdate => "compositionupdate", CompositionEnd => "compositionend",
FullScreenChange => "fullscreenchange", FullScreenError => "fullscreenerror", Resize => "resize",
Scroll => "scroll", Cut => "cut", Copy => "copy", Paste => "paste",
KeyDown => "keydown",
KeyPress => "keypress", AuxClick => "auxclick", Click => "click", ContextMenu => "contextmenu", DblClick => "dblclick",
MouseDown => "mousedown", MouseEnter => "mouseenter", MouseLeave => "mouseleave",
MouseMove => "mousemove", MouseOver => "mouseover", MouseOut => "mouseout", MouseUp => "mouseup",
PointerLockChange => "pointerlockchange", PointerLockError => "pointerlockerror", Select => "select",
Wheel => "wheel",
Drag => "drag", DragEnd => "dragend", DragEnter => "dragenter", DragStart => "dragstart", DragLeave => "dragleave",
DragOver => "dragover", Drop => "drop",
AudioProcess => "audioprocess", CanPlay => "canplay", CanPlayThrough => "canplaythrough", Complete => "complete",
DurationChange => "durationchange", Emptied => "emptied", Ended => "ended", LoadedData => "loadeddata",
LoadedMetaData => "loadedmetadata", Pause => "pause", Play => "play", Playing => "playing", RateChagne => "ratechange",
Seeked => "seeked", Seeking => "seeking", Stalled => "stalled", Suspend => "suspend", TimeUpdate => "timeupdate",
VolumeChange => "volumechange",
Change => "change",
Input => "input"
}
macro_rules! make_tags {
{ $($tag_camel:ident => $tag:expr),+ } => {
#[derive(Clone, Debug, PartialEq)]
pub enum Tag {
Custom(String),
$(
$tag_camel,
)+
}
impl Tag {
pub fn as_str(&self) -> &str {
match self {
Tag::Custom(name) => &name,
$ (
Tag::$tag_camel => $tag,
) +
}
}
}
impl From<String> for Tag {
fn from(tag: String) -> Self {
match tag.as_ref() {
$ (
$tag => Tag::$tag_camel,
) +
_ => {
Tag::Span
}
}
}
}
}
}
make_tags! {
Address => "address", Article => "article", Aside => "aside", Footer => "footer",
Header => "header", H1 => "h1",
H2 => "h2", H3 => "h3", H4 => "h4", H5 => "h5", H6 => "h6",
Hgroup => "hgroup", Main => "main", Nav => "nav", Section => "section",
BlockQuote => "blockquote",
Dd => "dd", Dir => "dir", Div => "div", Dl => "dl", Dt => "dt", FigCaption => "figcaption", Figure => "figure",
Hr => "hr", Li => "li", Ol => "ol", P => "p", Pre => "pre", Ul => "ul",
A => "a", Abbr => "abbr",
B => "b", Bdi => "bdi", Bdo => "bdo", Br => "br", Cite => "cite", Code => "code", Data => "data",
Dfn => "dfn", Em => "em", I => "i", Kbd => "kbd", Mark => "mark", Q => "q", Rb => "rb",
Rp => "rp", Rt => "rt", Rtc => "rtc", Ruby => "ruby", S => "s", Samp => "samp", Small => "small",
Span => "span", Strong => "strong", Sub => "sub", Sup => "sup", Time => "time", Tt => "tt",
U => "u", Var => "var", Wbr => "wbr",
Area => "area", Audio => "audio", Img => "img", Map => "map", Track => "track", Video => "video",
Applet => "applet", Embed => "embed", Iframe => "iframe",
NoEmbed => "noembed", Object => "object", Param => "param", Picture => "picture", Source => "source",
Canvas => "canvas", NoScript => "noscript", Script => "Script",
Del => "del", Ins => "ins",
Caption => "caption", Col => "col", ColGroup => "colgroup", Table => "table", Tbody => "tbody",
Td => "td", Tfoot =>"tfoot", Th => "th", Thead => "thead", Tr => "tr",
Button => "button", DataList => "datalist", FieldSet => "fieldset", Form => "form", Input => "input",
Label => "label", Legend => "legend", Meter => "meter", OptGroup => "optgroup", Option => "option",
Output => "output", Progress => "progress", Select => "select", TextArea => "textarea",
Details => "details", Dialog => "dialog", Menu => "menu", MenuItem => "menuitem", Summary => "summary",
Content => "content", Element => "element", Shadow => "shadow", Slot => "slot", Template => "template",
Animate => "animate", AnimateColor => "animateColor", AnimateMotion => "animateMotion",
AnimateTransform => "animateTransform", Discard => "discard", Mpath => "mpath", Set => "set",
Circle => "circle", Ellipse => "ellipse", Line => "line", Polygon => "polygon",
Polyline => "polyline", Rect => "rect", Mesh => "mesh", Path => "path",
Defs => "defs", G => "g", Marker => "marker", Mask => "mask", MissingGlyph => "missing-glyph",
Pattern => "pattern", Svg => "svg", Switch => "switch", Symbol => "symbol", Unknown => "unknown",
Desc => "desc", Metadata => "metadata", Title => "title",
FeBlend => "feBlend",
FeColorMatrix => "feColorMatrix",
FeComponentTransfer => "feComponentTransfer",
FeComposite => "feComposite",
FeConvolveMatrix => "feConvolveMatrix",
FeDiffuseLighting => "feDiffuseLighting",
FeDisplacementMap => "feDisplacementMap",
FeDropShadow => "feDropShadow",
FeFlood => "feFlood",
FeFuncA => "feFuncA",
FeFuncB => "feFuncB",
FeFuncG => "feFuncG",
FeFuncR => "feFuncR",
FeGaussianBlur => "feGaussianBlur",
FeImage => "feImage",
FeMerge => "feMerge",
FeMergeNode => "feMergeNode",
FeMorphology => "feMorphology",
FeOffset => "feOffset",
FeSpecularLighting => "feSpecularLighting",
FeTile => "feTile",
FeTurbulence => "feTurbulence",
FeDistantLight => "feDistantLight", FePointLight => "fePointLight", FeSpotLight => "feSpotLight",
Font => "font",
FontFace => "font-face",
FontFaceFormat => "font-face-format",
FontFaceName => "font-face-name",
FontFaceSrc => "font-face-src",
FontFaceUri => "font-face-uri",
HKern => "hkern",
VKern => "vkern",
LinearGradient => "linearGradient", MeshGradient => "meshGradient",
RadialGradient => "radialGradient", Stop => "stop",
Image => "image",
Use => "use",
Hatch => "hatch", SolidColor => "solidcolor",
AltGlyph => "altGlyph", AltGlyphDef => "altGlyphDef", AltGlyphItem => "altGlyphItem", Glyph => "glyph",
GlyphRef => "glyphRef", TextPath => "textPath", Text => "text", TRef => "tref", TSpan => "tspan",
ClipPath => "clipPath", ColorProfile => "color-profile", Cursor => "cursor", Filter => "filter",
ForeignObject => "foreignObject", HatchPath => "hatchpath", MeshPatch => "meshpatch", MeshRow => "meshrow",
Style => "style", View => "view"
}
#[derive(Debug)]
pub struct El<Ms: 'static> {
pub tag: Tag,
pub attrs: Attrs,
pub style: Style,
pub listeners: Vec<Listener<Ms>>,
pub text: Option<String>,
pub children: Vec<El<Ms>>,
pub id: Option<u32>,
pub nest_level: Option<u32>,
pub el_ws: Option<web_sys::Node>,
pub namespace: Option<Namespace>,
pub controlled: bool,
pub hooks: LifecycleHooks,
}
type HookFn = Box<FnMut(&web_sys::Node)>;
#[derive(Default)]
pub struct LifecycleHooks {
pub did_mount: Option<HookFn>,
pub did_update: Option<HookFn>,
pub will_unmount: Option<HookFn>,
}
fn fmt_hook_fn<T>(h: &Option<T>) -> &'static str {
match h {
Some(_) => "Some(.. a dynamic handler ..)",
None => "None",
}
}
impl fmt::Debug for LifecycleHooks {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"LifecycleHooks {{ did_mount:{:?}, did_update:{:?}, will_unmount:{} }}",
fmt_hook_fn(&self.did_mount),
fmt_hook_fn(&self.did_update),
fmt_hook_fn(&self.will_unmount)
)
}
}
impl<Ms> El<Ms> {
pub fn empty(tag: Tag) -> Self {
Self {
tag,
attrs: Attrs::empty(),
style: Style::empty(),
listeners: Vec::new(),
text: None,
children: Vec::new(),
id: None,
nest_level: None,
el_ws: None,
namespace: None,
controlled: false,
hooks: LifecycleHooks::default(),
}
}
pub fn new_text(text: &str) -> Self {
let mut result = Self::empty(Tag::Span);
result.text = Some(text.into());
result
}
pub fn empty_svg(tag: Tag) -> Self {
let mut el = El::empty(tag);
el.namespace = Some(Namespace::Svg);
el
}
pub fn from_markdown(markdown: &str) -> Vec<Self> {
let parser = pulldown_cmark::Parser::new(markdown);
let mut html_text = String::new();
pulldown_cmark::html::push_html(&mut html_text, parser);
Self::from_html(&html_text)
}
pub fn from_html(html: &str) -> Vec<Self> {
let el_ws_wrapper = util::document()
.create_element("div")
.expect("Problem creating web-sys element");
el_ws_wrapper.set_inner_html(html);
let mut result = Vec::new();
let children = el_ws_wrapper.child_nodes();
for i in 0..children.length() {
let child = children.get(i)
.expect("Can't find child in raw html element.");
if let Some(child_vdom) = websys_bridge::el_from_ws(&child) {
result.push(child_vdom)
}
}
result
}
pub fn add_child(&mut self, element: El<Ms>) {
self.children.push(element);
}
pub fn add_attr(&mut self, key: String, val: String) {
self.attrs.vals.insert(key.into(), val);
}
pub fn add_style(&mut self, key: String, val: String) {
self.style.vals.insert(key, val);
}
pub fn set_text(&mut self, text: &str) {
self.text = Some(text.into())
}
fn _html(&self) -> String {
let text = self.text.clone().unwrap_or_default();
let opening = String::from("<")
+ self.tag.as_str()
+ &self.attrs.to_string()
+ " style=\""
+ &self.style.to_string()
+ ">\n";
let inner = self
.children
.iter()
.fold(String::new(), |result, child| result + &child._html());
let closing = String::from("\n</") + self.tag.as_str() + ">";
opening + &text + &inner + &closing
}
pub fn quick_clone(&self) -> Self {
Self {
tag: self.tag.clone(),
attrs: Attrs::empty(),
style: Style::empty(),
listeners: Vec::new(),
text: None,
children: Vec::new(),
id: None,
nest_level: None,
el_ws: self.el_ws.clone(),
namespace: self.namespace.clone(),
controlled: self.controlled,
hooks: LifecycleHooks::default(),
}
}
pub fn get_text(&self) -> String {
let mut result = String::new();
for child in &self.children {
if let Some(text) = &child.text {
result += text;
}
}
result
}
pub fn is_dummy(&self) -> bool {
if let Tag::Del = self.tag {
if self.attrs.vals.get(&At::Dummy).is_some() {
return true;
}
}
false
}
}
impl<Ms> Clone for El<Ms> {
fn clone(&self) -> Self {
Self {
tag: self.tag.clone(),
attrs: self.attrs.clone(),
style: self.style.clone(),
text: self.text.clone(),
children: self.children.clone(),
id: self.id,
nest_level: self.nest_level,
el_ws: self.el_ws.clone(),
listeners: Vec::new(),
namespace: self.namespace.clone(),
controlled: self.controlled,
hooks: LifecycleHooks::default(),
}
}
}
impl<Ms> PartialEq for El<Ms> {
fn eq(&self, other: &Self) -> bool {
self.tag == other.tag &&
self.attrs == other.attrs &&
self.style == other.style &&
self.text == other.text &&
self.listeners == other.listeners &&
self.nest_level == other.nest_level
}
}
pub struct DidMount {
actions: Box<FnMut(&web_sys::Node)>,
}
pub struct DidUpdate {
actions: Box<FnMut(&web_sys::Node)>,
}
pub struct WillUnmount {
actions: Box<FnMut(&web_sys::Node)>,
}
pub fn did_mount(mut actions: impl FnMut(&web_sys::Node) + 'static) -> DidMount {
let closure = move |el: &web_sys::Node| actions(el);
DidMount {
actions: Box::new(closure),
}
}
pub fn did_update(mut actions: impl FnMut(&web_sys::Node) + 'static) -> DidUpdate {
let closure = move |el: &web_sys::Node| actions(el);
DidUpdate {
actions: Box::new(closure),
}
}
pub fn will_unmount(mut actions: impl FnMut(&web_sys::Node) + 'static) -> WillUnmount {
let closure = move |el: &web_sys::Node| actions(el);
WillUnmount {
actions: Box::new(closure),
}
}
#[cfg(test)]
use crate as seed;
use super::*;
use crate::{attrs, div, h1, p, section, span};
pub mod tests {
#[derive(Clone)]
enum Msg {
Placeholder,
}
}