1use super::{ElementTarget, JsValue, Scope, TemplateComponent, component::Retained};
3use crate::{OwnerHandle, untrack};
4use std::rc::Rc;
5
6type Factory = dyn Fn(&OwnerHandle) -> Result<Scope, JsValue>;
7
8#[derive(Clone)]
15#[must_use = "content is lazy; supply it to a slot to mount it"]
16pub struct Content(Rc<Factory>);
17
18impl Content {
19 pub fn new<C: TemplateComponent>(make: impl Fn(OwnerHandle) -> C + 'static) -> Self {
20 Self::try_new(move |owner| Ok(make(owner)))
21 }
22
23 pub fn try_new<C: TemplateComponent>(
26 make: impl Fn(OwnerHandle) -> Result<C, JsValue> + 'static,
27 ) -> Self {
28 Self(Rc::new(move |parent| C::prepare(parent, &make)))
29 }
30
31 #[doc(hidden)]
34 pub fn from_prepared(make: impl Fn(&OwnerHandle) -> Result<Scope, JsValue> + 'static) -> Self {
35 Self(Rc::new(make))
36 }
37
38 pub fn prepare(&self, parent: &OwnerHandle) -> Result<Scope, JsValue> {
42 let scope = (self.0)(parent)?;
43 let owner = scope.owner();
44 if owner.is_disposed()
45 || owner.is_active()
46 || !owner.is_child_of(parent)
47 || scope.root().parent_node().is_some()
48 {
49 return Err(JsValue::from_str(
50 "fusor: slot content must be a detached, prepared child of its receiving owner",
51 ));
52 }
53 Ok(scope)
54 }
55}
56
57impl PartialEq for Content {
58 fn eq(&self, other: &Self) -> bool {
59 Rc::ptr_eq(&self.0, &other.0)
60 }
61}
62
63impl Eq for Content {}
64
65impl Scope {
66 pub fn slot<R: Into<Option<Content>>>(
70 &mut self,
71 target: impl ElementTarget,
72 read: impl Fn() -> R + 'static,
73 ) -> Result<(), JsValue> {
74 self.slot_with(target, move || read().into().map(|content| ((), content)))
75 }
76
77 pub fn slot_with<K: PartialEq + 'static>(
80 &mut self,
81 target: impl ElementTarget,
82 read: impl Fn() -> Option<(K, Content)> + 'static,
83 ) -> Result<(), JsValue> {
84 let container = target.resolve(self)?;
85 let parent = self.owner();
86 let mut current: Retained<(K, Content)> = Retained::default();
87 self.bind(move || {
88 let next = read();
89 untrack(|| {
90 let Some(identity) = next else {
91 current.clear();
92 return Ok(());
93 };
94 if current.key() == Some(&identity) {
95 return Ok(());
96 }
97 let child = identity.1.prepare(&parent)?;
98 current.replace(identity, child, |child| child.attach(&container))
99 })
100 })
101 }
102}