#[cfg(debug_assertions)]
use std::collections::HashSet;
use std::{
self, fmt,
future::Future,
marker::PhantomData,
pin::{pin, Pin},
};
use discard::DiscardOnDrop;
use futures::StreamExt;
use futures_signals::{
cancelable_future,
signal::{Signal, SignalExt},
signal_vec::{always, SignalVec, SignalVecExt},
CancelableFutureHandle,
};
use include_doc::function_body;
use silkenweb_base::document;
use silkenweb_signals_ext::value::{Executor, RefSignalOrValue, SignalOrValue, Value};
use wasm_bindgen::{JsCast, JsValue};
use self::child_vec::{ChildVec, ParentUnique};
use super::{ChildNode, Node, Resource};
use crate::{
attribute::Attribute,
clone,
dom::{
private::{DomElement, DomText, EventStore, InstantiableDomElement},
DefaultDom, Dom, Hydro, InDom, InstantiableDom, Template, Wet,
},
empty_str,
hydration::HydrationStats,
intern_str,
node::text,
task,
};
pub(crate) mod child_vec;
pub struct GenericElement<D: Dom = DefaultDom, Mutability = Mut> {
static_child_count: usize,
child_vec: Option<Pin<Box<dyn SignalVec<Item = Node<D>>>>>,
resources: Vec<Resource>,
events: EventStore,
element: D::Element,
#[cfg(debug_assertions)]
attributes: HashSet<String>,
phantom: PhantomData<Mutability>,
}
impl<D: Dom> GenericElement<D> {
pub fn new(namespace: &Namespace, tag: &str) -> Self {
Self::from_dom(D::Element::new(namespace, tag), 0)
}
pub fn freeze(mut self) -> GenericElement<D, Const> {
self.build();
GenericElement {
static_child_count: self.static_child_count,
child_vec: self.child_vec,
resources: self.resources,
events: self.events,
element: self.element,
#[cfg(debug_assertions)]
attributes: self.attributes,
phantom: PhantomData,
}
}
pub fn observe_mutations<F>(mut self, f: F) -> Self
where
F: for<'a> FnOnce(GenericElementObserver<'a, D>) -> GenericElementObserver<'a, D>,
{
f(GenericElementObserver(&mut self));
self
}
pub(crate) fn from_dom(element: D::Element, static_child_count: usize) -> Self {
Self {
static_child_count,
child_vec: None,
resources: Vec::new(),
events: EventStore::default(),
element,
#[cfg(debug_assertions)]
attributes: HashSet::new(),
phantom: PhantomData,
}
}
pub(crate) fn store_child(&mut self, mut child: Self) {
child.build();
self.resources.append(&mut child.resources);
self.events.combine(child.events);
}
fn check_attribute_unique(&mut self, name: &str) {
#[cfg(debug_assertions)]
debug_assert!(self.attributes.insert(name.into()));
let _ = name;
}
async fn class_signal<T>(mut element: D::Element, class: impl Signal<Item = T>)
where
T: AsRef<str>,
{
let mut class = pin!(class.to_stream());
if let Some(new_value) = class.next().await {
Self::check_class(&new_value);
element.add_class(intern_str(new_value.as_ref()));
let mut previous_value = new_value;
while let Some(new_value) = class.next().await {
Self::check_class(&new_value);
element.remove_class(previous_value.as_ref());
element.add_class(intern_str(new_value.as_ref()));
previous_value = new_value;
}
}
}
async fn classes_signal<T>(
mut element: D::Element,
classes: impl Signal<Item = impl IntoIterator<Item = T>>,
) where
T: AsRef<str>,
{
let mut classes = pin!(classes.to_stream());
let mut previous_classes: Vec<T> = Vec::new();
while let Some(new_classes) = classes.next().await {
for to_remove in previous_classes.drain(..) {
element.remove_class(to_remove.as_ref());
}
for to_add in new_classes {
Self::check_class(&to_add);
element.add_class(intern_str(to_add.as_ref()));
previous_classes.push(to_add);
}
}
}
fn check_class(name: &impl AsRef<str>) {
assert!(
!name.as_ref().is_empty(),
"Class names must not be empty. Use `.classes` with `Option::None` to unset an optional class."
);
debug_assert!(
!name.as_ref().contains(char::is_whitespace),
"Class names must not contain whitespace."
)
}
}
pub struct GenericElementObserver<'a, D: Dom>(&'a mut GenericElement<D>);
impl<D: Dom> GenericElementObserver<'_, D> {
pub fn attribute(
self,
name: String,
mut f: impl FnMut(&web_sys::Element, Option<String>) + 'static,
) -> Self {
let name = name.to_string();
self.0.element.observe_attributes(
move |changes, _observer| {
for change in changes
.to_vec()
.into_iter()
.map(|change| change.unchecked_into::<web_sys::MutationRecord>())
{
if change.type_() == "attributes"
&& change.attribute_name().as_ref() == Some(&name)
{
if let Some(elem) =
change.target().and_then(|target| target.dyn_into().ok())
{
f(&elem, change.old_value())
}
}
}
},
&mut self.0.events,
);
self
}
}
impl<D: Dom, Mutability> GenericElement<D, Mutability> {
fn build(&mut self) {
if let Some(children) = self.child_vec.take() {
let child_vec =
ChildVec::<D, ParentUnique>::new(self.element.clone(), self.static_child_count);
let handle = child_vec.run(children);
self.resources.push(Resource::Any(Box::new(handle)));
}
}
}
impl<Param, D> GenericElement<Template<Param, D>>
where
Param: 'static,
D: InstantiableDom,
{
pub fn on_instantiate(
mut self,
f: impl 'static + Fn(GenericElement<D>, &Param) -> GenericElement<D>,
) -> Self {
self.element.on_instantiate(f);
self
}
}
impl<D: Dom> TextParentElement<D> for GenericElement<D> {
fn text<'a, T>(mut self, child: impl RefSignalOrValue<'a, Item = T>) -> Self
where
T: 'a + AsRef<str> + Into<String>,
{
if self.child_vec.is_some() {
return self.child(child.map(|child| text(child.as_ref())));
}
self.static_child_count += 1;
child.select_spawn(
|parent, child| {
parent
.element
.append_child(&D::Text::new(child.as_ref()).into());
},
|parent, child_signal| {
let mut text_node = D::Text::new(empty_str());
parent.element.append_child(&text_node.clone().into());
child_signal.for_each(move |new_value| {
text_node.set_text(new_value.as_ref());
async {}
})
},
&mut self,
);
self
}
}
impl<D: Dom> ParentElement<D> for GenericElement<D> {
fn optional_child(self, child: impl SignalOrValue<Item = Option<impl ChildNode<D>>>) -> Self {
child.select(
|mut parent, child| {
if let Some(child) = child {
if parent.child_vec.is_some() {
return parent.children_signal(always(vec![child]));
}
parent.static_child_count += 1;
let child = child.into();
parent.element.append_child(&child.node);
parent.resources.append(&mut child.resources.into_vec());
parent.events.combine(child.events);
}
parent
},
|parent, child| {
let child_vec = child
.map(|child| child.into_iter().collect::<Vec<_>>())
.to_signal_vec();
parent.children_signal(child_vec)
},
self,
)
}
fn children<N>(mut self, children: impl IntoIterator<Item = N>) -> Self
where
N: Into<Node<D>>,
{
if self.child_vec.is_some() {
let children = children
.into_iter()
.map(|node| node.into())
.collect::<Vec<_>>();
return self.children_signal(always(children));
}
for child in children {
self = self.child(child.into());
}
self
}
fn children_signal<N>(mut self, children: impl SignalVec<Item = N> + 'static) -> Self
where
N: Into<Node<D>>,
{
let new_children = children.map(|child| child.into());
let boxed_children = if let Some(child_vec) = self.child_vec.take() {
child_vec.chain(new_children).boxed_local()
} else {
new_children.boxed_local()
};
self.child_vec = Some(boxed_children);
self
}
}
impl<Mutability> GenericElement<Wet, Mutability> {
pub(crate) fn dom_element(&self) -> web_sys::Element {
self.element.dom_element()
}
}
impl<Mutability> GenericElement<Hydro, Mutability> {
pub(crate) fn hydrate(
mut self,
element: &web_sys::Element,
tracker: &mut HydrationStats,
) -> GenericElement<Wet, Const> {
self.build();
GenericElement {
static_child_count: self.static_child_count,
child_vec: None,
resources: self.resources,
events: self.events,
element: self.element.hydrate(element, tracker),
#[cfg(debug_assertions)]
attributes: self.attributes,
phantom: PhantomData,
}
}
}
impl<D: InstantiableDom> ShadowRootParent<D> for GenericElement<D> {
fn attach_shadow_children<N>(mut self, children: impl IntoIterator<Item = N> + 'static) -> Self
where
N: Into<Node<D>>,
{
let children: Vec<_> = children
.into_iter()
.map(|child| {
let child = child.into();
let child_node = child.node;
self.resources.append(&mut child.resources.into_vec());
self.events.combine(child.events);
child_node
})
.collect();
self.element.attach_shadow_children(children);
self
}
}
impl<D: Dom> Element for GenericElement<D> {
type Dom = D;
type DomElement = web_sys::Element;
fn class<'a, T>(mut self, class: impl RefSignalOrValue<'a, Item = T>) -> Self
where
T: 'a + AsRef<str>,
{
class.select_spawn(
|elem, class| {
Self::check_class(&class);
elem.element.add_class(intern_str(class.as_ref()))
},
|elem, class| Self::class_signal(elem.element.clone(), class),
&mut self,
);
self
}
fn classes<'a, T, Iter>(mut self, classes: impl RefSignalOrValue<'a, Item = Iter>) -> Self
where
T: 'a + AsRef<str>,
Iter: 'a + IntoIterator<Item = T>,
{
classes.select_spawn(
|elem, classes| {
for class in classes {
Self::check_class(&class);
elem.element.add_class(intern_str(class.as_ref()));
}
},
|elem, classes| Self::classes_signal(elem.element.clone(), classes),
&mut self,
);
self
}
fn attribute<'a>(
mut self,
name: &str,
value: impl RefSignalOrValue<'a, Item = impl Attribute>,
) -> Self {
self.check_attribute_unique(name);
value.select_spawn(
|elem, value| elem.element.attribute(name, value),
|elem, value| {
let name = name.to_owned();
clone!(mut elem.element);
value.for_each(move |new_value| {
element.attribute(&name, new_value);
async {}
})
},
&mut self,
);
self
}
fn style_property<'a>(
mut self,
name: impl Into<String>,
value: impl RefSignalOrValue<'a, Item = impl AsRef<str> + 'a>,
) -> Self {
#[cfg(debug_assertions)]
debug_assert!(!self.attributes.contains("style"));
let name = name.into();
value.select_spawn(
|elem, value| elem.element.style_property(&name, value.as_ref()),
|elem, value| {
clone!(name, mut elem.element);
value.for_each(move |new_value| {
element.style_property(&name, new_value.as_ref());
async {}
})
},
&mut self,
);
self
}
fn effect(mut self, f: impl FnOnce(&Self::DomElement) + 'static) -> Self {
self.element.effect(f);
self
}
fn effect_signal<T>(
self,
sig: impl Signal<Item = T> + 'static,
f: impl Clone + Fn(&Self::DomElement, T) + 'static,
) -> Self
where
T: 'static,
{
clone!(mut self.element);
let future = sig.for_each(move |x| {
clone!(f);
element.effect(move |elem| f(elem, x));
async {}
});
self.spawn_future(future)
}
fn map_element(self, f: impl FnOnce(&Self::DomElement) + 'static) -> Self {
if let Some(element) = self.element.try_dom_element() {
f(&element);
}
self
}
fn map_element_signal<T>(
self,
sig: impl Signal<Item = T> + 'static,
f: impl Clone + Fn(&Self::DomElement, T) + 'static,
) -> Self
where
T: 'static,
{
clone!(mut self.element);
let future = sig.for_each(move |x| {
if let Some(element) = element.try_dom_element() {
f(&element, x);
}
async {}
});
self.spawn_future(future)
}
fn handle(&self) -> ElementHandle<Self::Dom, Self::DomElement> {
ElementHandle(self.element.clone(), PhantomData)
}
fn spawn_future(mut self, future: impl Future<Output = ()> + 'static) -> Self {
self.spawn(future);
self
}
fn on(mut self, name: &'static str, f: impl FnMut(JsValue) + 'static) -> Self {
self.element.on(name, f, &mut self.events);
self
}
}
impl<D: Dom> Executor for GenericElement<D> {
fn spawn(&mut self, future: impl Future<Output = ()> + 'static) {
self.resources
.push(Resource::FutureHandle(spawn_cancelable_future(future)));
}
}
impl<D: Dom, Mutability> Value for GenericElement<D, Mutability> {}
impl<D: Dom, Mutability> InDom for GenericElement<D, Mutability> {
type Dom = D;
}
impl<D: Dom, Mutability> From<GenericElement<D, Mutability>> for Node<D> {
fn from(mut elem: GenericElement<D, Mutability>) -> Self {
elem.build();
Self {
node: elem.element.into(),
resources: elem.resources.into_boxed_slice(),
events: elem.events,
}
}
}
pub trait ChildElement<D: Dom = DefaultDom>:
Into<GenericElement<D, Const>> + Into<Node<D>> + Value + 'static
{
}
impl<D, T> ChildElement<D> for T
where
D: Dom,
T: Into<GenericElement<D, Const>> + Into<Node<D>> + Value + 'static,
{
}
pub trait Element: Sized {
type Dom: Dom;
type DomElement: JsCast + 'static;
#[doc = function_body!("tests/doc/node/element.rs", static_single_class_name, [])]
#[doc = function_body!("tests/doc/node/element.rs", dynamic_single_class_name, [])]
fn class<'a, T>(self, class: impl RefSignalOrValue<'a, Item = T>) -> Self
where
T: 'a + AsRef<str>;
#[doc = function_body!("tests/doc/node/element.rs", static_class_names, [])]
#[doc = function_body!("tests/doc/node/element.rs", dynamic_class_names, [])]
fn classes<'a, T, Iter>(self, classes: impl RefSignalOrValue<'a, Item = Iter>) -> Self
where
T: 'a + AsRef<str>,
Iter: 'a + IntoIterator<Item = T>;
fn attribute<'a>(
self,
name: &str,
value: impl RefSignalOrValue<'a, Item = impl Attribute>,
) -> Self;
fn style_property<'a>(
self,
name: impl Into<String>,
value: impl RefSignalOrValue<'a, Item = impl AsRef<str> + 'a>,
) -> Self;
#[doc = function_body!("tests/doc/node/element.rs", effect, [])]
fn effect(self, f: impl FnOnce(&Self::DomElement) + 'static) -> Self;
fn effect_signal<T: 'static>(
self,
sig: impl Signal<Item = T> + 'static,
f: impl Fn(&Self::DomElement, T) + Clone + 'static,
) -> Self;
fn map_element(self, f: impl FnOnce(&Self::DomElement) + 'static) -> Self;
fn map_element_signal<T: 'static>(
self,
sig: impl Signal<Item = T> + 'static,
f: impl Fn(&Self::DomElement, T) + Clone + 'static,
) -> Self;
#[doc = function_body!("tests/doc/node/element.rs", handle, [])]
fn handle(&self) -> ElementHandle<Self::Dom, Self::DomElement>;
fn spawn_future(self, future: impl Future<Output = ()> + 'static) -> Self;
fn on(self, name: &'static str, f: impl FnMut(JsValue) + 'static) -> Self;
}
pub trait TextParentElement<D: Dom = DefaultDom>: Element {
#[doc = function_body!("tests/doc/node/element.rs", static_text, [])]
#[doc = function_body!("tests/doc/node/element.rs", dynamic_text, [])]
fn text<'a, T>(self, child: impl RefSignalOrValue<'a, Item = T>) -> Self
where
T: 'a + AsRef<str> + Into<String>;
}
pub trait ParentElement<D: Dom = DefaultDom>: TextParentElement<D> {
#[doc = function_body!("tests/doc/node/element.rs", static_child, [])]
#[doc = function_body!("tests/doc/node/element.rs", dynamic_child, [])]
fn child(self, child: impl SignalOrValue<Item = impl ChildNode<D>>) -> Self {
self.optional_child(child.map(Some))
}
#[doc = function_body!("tests/doc/node/element.rs", static_optional_child, [])]
#[doc = function_body!("tests/doc/node/element.rs", dynamic_optional_child, [])]
fn optional_child(self, child: impl SignalOrValue<Item = Option<impl ChildNode<D>>>) -> Self;
#[doc = function_body!("tests/doc/node/element.rs", children, [])]
fn children<N>(self, children: impl IntoIterator<Item = N>) -> Self
where
N: Into<Node<D>>;
fn children_signal<N>(self, children: impl SignalVec<Item = N> + 'static) -> Self
where
N: Into<Node<D>>;
}
pub trait ShadowRootParent<D: InstantiableDom = DefaultDom>: Element {
fn attach_shadow_children<N>(self, children: impl IntoIterator<Item = N> + 'static) -> Self
where
N: Into<Node<D>>;
}
impl<D> fmt::Display for GenericElement<D, Const>
where
D: Dom,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.element.fmt(f)
}
}
impl<Param, D> GenericElement<Template<Param, D>, Const>
where
D: InstantiableDom,
Param: 'static,
{
pub fn instantiate(&self, param: &Param) -> GenericElement<D> {
self.element.instantiate(param)
}
}
fn spawn_cancelable_future(
future: impl Future<Output = ()> + 'static,
) -> DiscardOnDrop<CancelableFutureHandle> {
let (handle, cancelable_future) = cancelable_future(future, || ());
task::spawn_local(cancelable_future);
handle
}
pub struct ElementHandle<D: Dom, DomElement>(D::Element, PhantomData<DomElement>);
impl<D: Dom, DomElement> Clone for ElementHandle<D, DomElement> {
fn clone(&self) -> Self {
Self(self.0.clone(), PhantomData)
}
}
impl<D: Dom, DomElement: JsCast + Clone> ElementHandle<D, DomElement> {
pub fn try_dom_element(&self) -> Option<DomElement> {
self.0
.try_dom_element()
.map(|elem| elem.dyn_into().unwrap())
}
pub fn dom_element(&self) -> DomElement {
self.0.dom_element().dyn_into().unwrap()
}
}
impl<D: Dom> ElementHandle<D, web_sys::Element> {
pub fn cast<T: JsCast>(self) -> ElementHandle<D, T> {
ElementHandle(self.0, PhantomData)
}
}
#[derive(Clone, Eq, PartialEq)]
pub enum Namespace {
Html,
Svg,
MathML,
Other(String),
}
impl Namespace {
pub(crate) fn create_element(&self, tag: &str) -> web_sys::Element {
match self {
Namespace::Html => document::create_element(tag),
_ => document::create_element_ns(intern_str(self.as_str()), tag),
}
}
pub(crate) fn as_str(&self) -> &str {
match self {
Namespace::Html => "http://www.w3.org/1999/xhtml",
Namespace::Svg => "http://www.w3.org/2000/svg",
Namespace::MathML => "http://www.w3.org/1998/Math/MathML",
Namespace::Other(ns) => ns,
}
}
}
pub struct Mut;
pub struct Const;