1use super::{JsValue, Scope};
3use wasm_bindgen::JsCast;
4use web_sys::{Element, Node};
5
6#[derive(Clone)]
13pub struct MountPoint {
14 pub(super) start: Node,
15 pub(super) end: Node,
16}
17
18#[must_use = "dropping the anchors removes the range"]
21pub struct Anchors(MountPoint);
22
23impl Drop for Anchors {
24 fn drop(&mut self) {
25 for node in [&self.0.start, &self.0.end] {
26 if let Some(parent) = node.parent_node() {
27 let _ = parent.remove_child(node);
28 }
29 }
30 }
31}
32
33impl MountPoint {
34 pub fn append(container: &Element) -> Result<(Self, Anchors), JsValue> {
36 let document = super::document()?;
37 let start: Node = document.create_comment("fusor:mount").into();
38 let end: Node = document.create_comment("fusor:end").into();
39 container.append_child(&start)?;
40 if let Err(error) = container.append_child(&end) {
41 let _ = container.remove_child(&start);
42 return Err(error);
43 }
44 let point = Self { start, end };
45 Ok((point.clone(), Anchors(point)))
46 }
47
48 pub fn parent_element(&self) -> Result<Element, JsValue> {
50 self.validate()?;
51 self.end
52 .parent_element()
53 .ok_or_else(|| JsValue::from_str("mount range has no element parent"))
54 }
55
56 pub(super) fn validate(&self) -> Result<Node, JsValue> {
58 self.walk(|_| {})?;
59 Ok(self
60 .start
61 .parent_node()
62 .expect("sibling anchors have a parent"))
63 }
64
65 fn walk(&self, mut visit: impl FnMut(Node)) -> Result<(), JsValue> {
68 let mut cursor = self.start.next_sibling();
69 while let Some(node) = cursor {
70 if node.is_same_node(Some(&self.end)) {
71 return Ok(());
72 }
73 cursor = node.next_sibling();
74 visit(node);
75 }
76 let (start, end) = (self.start.parent_node(), self.end.parent_node());
77 Err(JsValue::from_str(match (start, end) {
78 (None, _) => "detached mount range",
79 (Some(start), Some(end)) if start.is_same_node(Some(&end)) => {
80 "mount range anchors are out of order"
81 }
82 _ => "mount range anchors have different parents",
83 }))
84 }
85
86 pub(super) fn is_empty(&self) -> Result<bool, JsValue> {
87 let mut empty = true;
88 self.walk(|_| empty = false)?;
89 Ok(empty)
90 }
91
92 pub(super) fn hydrated_root(&self, present: bool) -> Result<Option<Element>, JsValue> {
95 let mut inner = Vec::new();
96 self.walk(|node| inner.push(node))?;
97 match (present, inner.as_slice()) {
98 (false, []) => Ok(None),
99 (true, [root]) if root.node_type() == Node::ELEMENT_NODE => {
100 Ok(Some(root.clone().unchecked_into()))
101 }
102 _ => Err(JsValue::from_str(
103 "server component identity/shape mismatch",
104 )),
105 }
106 }
107
108 pub(super) fn nodes(&self) -> Result<Vec<Node>, JsValue> {
110 let mut nodes = vec![self.start.clone()];
111 self.walk(|node| nodes.push(node))?;
112 nodes.push(self.end.clone());
113 Ok(nodes)
114 }
115
116 pub(super) fn contains(&self, target: &Node) -> bool {
117 self.nodes()
118 .is_ok_and(|nodes| nodes.iter().any(|node| node.contains(Some(target))))
119 }
120
121 pub(super) fn precedes_end(&self, node: &Node) -> bool {
123 node.next_sibling()
124 .is_some_and(|next| next.is_same_node(Some(&self.end)))
125 }
126
127 pub(super) fn push(&self, node: &Node) -> Result<(), JsValue> {
129 self.end
130 .parent_node()
131 .ok_or_else(|| JsValue::from_str("detached mount range"))?
132 .insert_before(node, Some(&self.end))?;
133 Ok(())
134 }
135
136 pub(super) fn remove(&self) {
138 let Ok(nodes) = self.nodes() else {
139 return;
140 };
141 for node in nodes {
142 #[cfg(feature = "islands")]
143 if let Some(element) = node.dyn_ref::<Element>() {
144 super::delivery::dispose_tree(element);
145 }
146 if let Some(parent) = node.parent_node() {
147 let _ = parent.remove_child(&node);
148 }
149 }
150 }
151}
152
153impl Scope {
154 pub fn mount_point(&mut self, container: &Element) -> Result<MountPoint, JsValue> {
158 let (point, anchors) = MountPoint::append(container)?;
159 self.retain(anchors);
160 Ok(point)
161 }
162
163 pub fn attach_at(&mut self, target: &MountPoint) -> Result<(), JsValue> {
167 if self.fragment.is_some() {
168 return self.attach_fragment(target);
169 }
170 target
171 .validate()?
172 .insert_before(&self.root, Some(&target.end))?;
173 self.remove_on_drop = true;
174 Ok(())
175 }
176
177 pub(super) fn attach_fragment(&self, target: &MountPoint) -> Result<(), JsValue> {
179 let parent = target.validate()?;
180 let fragment = self
181 .fragment
182 .as_ref()
183 .ok_or_else(|| JsValue::from_str("expected a children fragment"))?;
184 for node in fragment.nodes()? {
185 parent.insert_before(&node, Some(&target.end))?;
186 }
187 Ok(())
188 }
189}