use super::{ElementTarget, JsValue, Scope, TemplateComponent};
use crate::{OwnerHandle, untrack};
use std::rc::Rc;
type Factory = dyn Fn(&OwnerHandle) -> Result<Scope, JsValue>;
#[derive(Clone)]
#[must_use = "content is lazy; supply it to a slot to mount it"]
pub struct Content(Rc<Factory>);
impl Content {
pub fn new<C: TemplateComponent>(make: impl Fn(OwnerHandle) -> C + 'static) -> Self {
Self::try_new(move |owner| Ok(make(owner)))
}
pub fn try_new<C: TemplateComponent>(
make: impl Fn(OwnerHandle) -> Result<C, JsValue> + 'static,
) -> Self {
Self(Rc::new(move |parent| C::prepare(parent, &make)))
}
#[doc(hidden)]
pub fn from_prepared(make: impl Fn(&OwnerHandle) -> Result<Scope, JsValue> + 'static) -> Self {
Self(Rc::new(make))
}
pub fn prepare(&self, parent: &OwnerHandle) -> Result<Scope, JsValue> {
let scope = (self.0)(parent)?;
let owner = scope.owner();
if owner.is_disposed()
|| owner.is_active()
|| !owner.is_child_of(parent)
|| scope.root().parent_node().is_some()
{
return Err(JsValue::from_str(
"fusor: slot content must be a detached, prepared child of its receiving owner",
));
}
Ok(scope)
}
}
impl PartialEq for Content {
fn eq(&self, other: &Self) -> bool {
Rc::ptr_eq(&self.0, &other.0)
}
}
impl Eq for Content {}
impl Scope {
pub fn slot<R: Into<Option<Content>>>(
&mut self,
target: impl ElementTarget,
read: impl Fn() -> R + 'static,
) -> Result<(), JsValue> {
self.slot_with(target, move || read().into().map(|content| ((), content)))
}
pub fn slot_with<K: PartialEq + 'static>(
&mut self,
target: impl ElementTarget,
read: impl Fn() -> Option<(K, Content)> + 'static,
) -> Result<(), JsValue> {
let container = target.resolve(self)?;
let parent = self.owner();
let mut current: Option<(K, Content, Scope)> = None;
self.bind(move || {
let next = read();
untrack(|| {
let Some((key, content)) = next else {
current.take();
return Ok(());
};
if current.as_ref().is_some_and(|(old_key, old_content, _)| {
old_key == &key && old_content == &content
}) {
return Ok(());
}
let mut child = content.prepare(&parent)?;
child.attach(&container)?;
child.finish_prepare()?;
current = Some((key, content, child));
current.as_ref().expect("just inserted").2.commit();
Ok(())
})
})
}
}