#[doc(hidden)]
pub mod vcomp;
#[doc(hidden)]
pub mod vlist;
#[doc(hidden)]
pub mod vnode;
#[doc(hidden)]
pub mod vtag;
#[doc(hidden)]
pub mod vtext;
use crate::html::AnyScope;
use cfg_if::cfg_if;
use indexmap::set::IndexSet;
use std::collections::HashMap;
use std::fmt;
use std::rc::Rc;
cfg_if! {
if #[cfg(feature = "std_web")] {
use crate::html::EventListener;
use stdweb::web::{Element, Node};
} else if #[cfg(feature = "web_sys")] {
use gloo::events::EventListener;
use web_sys::{Element, Node};
}
}
#[doc(inline)]
pub use self::vcomp::{VChild, VComp};
#[doc(inline)]
pub use self::vlist::VList;
#[doc(inline)]
pub use self::vnode::VNode;
#[doc(inline)]
pub use self::vtag::VTag;
#[doc(inline)]
pub use self::vtext::VText;
pub trait Listener {
fn kind(&self) -> &'static str;
fn attach(&self, element: &Element) -> EventListener;
}
impl fmt::Debug for dyn Listener {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Listener {{ kind: {} }}", self.kind())
}
}
type Listeners = Vec<Rc<dyn Listener>>;
type Attributes = HashMap<String, String>;
#[derive(Debug, Clone, Default)]
pub struct Classes {
set: IndexSet<String>,
}
impl Classes {
pub fn new() -> Self {
Self {
set: IndexSet::new(),
}
}
pub fn push(&mut self, class: &str) {
let classes_to_add: Classes = class.into();
self.set.extend(classes_to_add.set);
}
pub fn contains(&self, class: &str) -> bool {
self.set.contains(class)
}
pub fn is_empty(&self) -> bool {
self.set.is_empty()
}
pub fn extend<T: Into<Classes>>(mut self, other: T) -> Self {
self.set.extend(other.into().set.into_iter());
self
}
}
impl ToString for Classes {
fn to_string(&self) -> String {
self.set
.iter()
.map(String::as_str)
.collect::<Vec<&str>>()
.join(" ")
}
}
impl From<&str> for Classes {
fn from(t: &str) -> Self {
let set = t
.split_whitespace()
.map(String::from)
.filter(|c| !c.is_empty())
.collect();
Self { set }
}
}
impl From<String> for Classes {
fn from(t: String) -> Self {
Classes::from(t.as_str())
}
}
impl From<&String> for Classes {
fn from(t: &String) -> Self {
Classes::from(t.as_str())
}
}
impl<T: AsRef<str>> From<Option<T>> for Classes {
fn from(t: Option<T>) -> Self {
t.as_ref()
.map(|s| <Classes as From<&str>>::from(s.as_ref()))
.unwrap_or_default()
}
}
impl<T: AsRef<str>> From<&Option<T>> for Classes {
fn from(t: &Option<T>) -> Self {
t.as_ref()
.map(|s| <Classes as From<&str>>::from(s.as_ref()))
.unwrap_or_default()
}
}
impl<T: AsRef<str>> From<Vec<T>> for Classes {
fn from(t: Vec<T>) -> Self {
let set = t
.iter()
.map(|x| x.as_ref())
.flat_map(|s| s.split_whitespace())
.map(String::from)
.filter(|c| !c.is_empty())
.collect();
Self { set }
}
}
impl PartialEq for Classes {
fn eq(&self, other: &Self) -> bool {
self.set.len() == other.set.len() && self.set.iter().eq(other.set.iter())
}
}
#[derive(Debug, PartialEq)]
enum Patch<ID, T> {
Add(ID, T),
Replace(ID, T),
Remove(ID),
}
enum Reform {
Keep,
Before(Option<Node>),
}
pub(crate) trait VDiff {
fn detach(&mut self, parent: &Element) -> Option<Node>;
fn apply(
&mut self,
scope: &AnyScope,
parent: &Element,
previous_sibling: Option<&Node>,
ancestor: Option<VNode>,
) -> Option<Node>;
}
pub trait Transformer<FROM, TO> {
fn transform(from: FROM) -> TO;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_is_initially_empty() {
let subject = Classes::new();
assert!(subject.is_empty());
}
#[test]
fn it_pushes_value() {
let mut subject = Classes::new();
subject.push("foo");
assert!(!subject.is_empty());
assert!(subject.contains("foo"));
}
#[test]
fn it_adds_values_via_extend() {
let mut other = Classes::new();
other.push("bar");
let subject = Classes::new().extend(other);
assert!(subject.contains("bar"));
}
#[test]
fn it_contains_both_values() {
let mut other = Classes::new();
other.push("bar");
let mut subject = Classes::new().extend(other);
subject.push("foo");
assert!(subject.contains("foo"));
assert!(subject.contains("bar"));
}
#[test]
fn it_splits_class_with_spaces() {
let mut subject = Classes::new();
subject.push("foo bar");
assert!(subject.contains("foo"));
assert!(subject.contains("bar"));
}
}