hobo/
element.rs

1use crate::prelude::*;
2use futures_signals::signal::{Signal, SignalExt};
3pub use hobo_derive::AsElement;
4use std::{
5	any::TypeId,
6	borrow::Cow,
7	collections::{HashMap, HashSet},
8};
9
10/// An `Element` with specific type erased
11#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, AsElement)]
12pub struct Element(pub Entity);
13
14#[derive(Default, Debug)]
15pub(crate) struct Classes {
16	pub(crate) marks: HashSet<TypeId>,
17
18	/// A HashMap of:
19	///
20	/// * key:     `u64`        - Tag hash.
21	/// * value.0: `css::Style` - The style of the class.
22	/// * value.1: `usize`      - Ordinal number.
23	///
24	/// For example, if `.class` was called 4 times on an element, the ordinal number of the last class would be 3 (the index).
25	/// This is used for precedence.
26	pub(crate) styles: HashMap<u64, (css::Style, usize)>,
27}
28
29#[cfg(feature = "experimental")]
30pub struct InDom;
31
32#[cfg(feature = "experimental")]
33#[derive(Default)]
34struct OnDomAttachCbs(Vec<Box<dyn FnOnce() + Send + Sync + 'static>>);
35
36#[derive(Default)]
37struct SignalHandlesCollection(Vec<discard::DiscardOnDrop<futures_signals::CancelableFutureHandle>>);
38
39#[cfg(debug_assertions)]
40pub struct Complainer(i32, Closure<dyn Fn()>);
41
42#[cfg(debug_assertions)]
43impl Complainer {
44	pub fn new(entity: Entity) -> Self {
45		let f = Closure::wrap(Box::new(move || {
46			// taken from console_error_panic_hook
47			// unfortunately, we can't build PanicInfo to use their hook soooo just ctrlc ctrlv time
48			#[wasm_bindgen]
49			extern {
50				#[wasm_bindgen(js_namespace = console)]
51				fn error(msg: String);
52
53				type Error;
54
55				#[wasm_bindgen(constructor)]
56				fn new() -> Error;
57
58				#[wasm_bindgen(structural, method, getter)]
59				fn stack(error: &Error) -> String;
60			}
61
62			// we can't get location here because location is set in .add_child
63			log::warn!("[Complainer] Element {} wasn't parented in 1 sec, it's probably a bug\n\nStack:\n\n{}", entity.0, Error::new().stack());
64		}) as Box<dyn Fn()>);
65		let id = web_sys::window().unwrap().set_interval_with_callback_and_timeout_and_arguments_0(f.as_ref().unchecked_ref(), 1000).unwrap();
66
67		Complainer(id, f)
68	}
69}
70
71#[cfg(debug_assertions)]
72impl Drop for Complainer {
73	fn drop(&mut self) {
74		web_sys::window().unwrap().clear_interval_with_handle(self.0);
75	}
76}
77
78impl Element {
79	#[track_caller]
80	fn add_child(self, child: Element) {
81		if self.is_dead() { log::warn!("add_child parent dead {:?}", self.as_entity()); return; }
82		if child.is_dead() { log::warn!("add_child child dead {:?}", child.as_entity()); return; }
83
84		self.get_cmp_mut_or_default::<Children>().push(child.as_entity());
85		child.get_cmp_mut_or_default::<Parent>().0 = self.as_entity();
86
87		if let (Some(parent_node), Some(child_node)) = (self.try_get_cmp::<web_sys::Node>(), child.try_get_cmp::<web_sys::Node>()) {
88			parent_node.append_child(&child_node).expect("can't append child");
89		} else {
90			let parent_has = if self.has_cmp::<web_sys::Node>() { "has" } else { "doesn't have" };
91			let child_has = if child.has_cmp::<web_sys::Node>() { "has" } else { "doesn't have" };
92			log::warn!("trying to add_child, but child {child_has} web_sys::Node and parent {parent_has} web_sys::Node");
93		}
94
95		#[cfg(debug_assertions)] {
96			let caller = std::panic::Location::caller();
97			child.set_attr("data-location", &format!("{}:{}", caller.file(), caller.line()));
98		}
99
100		// HACK: this is not very good because,
101		// if after passing callbacks up, the child is detached or the parent is detached,
102		// the callbacks might be called while the child is elsewhere or never called if the parent is discarded
103		#[cfg(feature = "experimental")]
104		if !child.has_cmp::<InDom>() {
105			if self.has_cmp::<InDom>() {
106				child.add_component(InDom);
107				if let Some(mut callbacks) = child.try_get_cmp_mut::<OnDomAttachCbs>() {
108					for cb in std::mem::take(&mut callbacks.0) { cb(); }
109					child.remove_cmp::<OnDomAttachCbs>();
110				}
111			} else if let Some(mut callbacks) = child.try_get_cmp_mut::<OnDomAttachCbs>() {
112				self.get_cmp_mut_or_default::<OnDomAttachCbs>().0.append(&mut callbacks.0);
113			}
114		}
115
116		#[cfg(debug_assertions)]
117		child.remove_cmp::<Complainer>();
118	}
119
120	fn leave_parent(self) {
121		if self.is_dead() { log::warn!("leave_parent child dead {:?}", self.as_entity()); return; }
122		let parent = self.get_cmp::<Parent>().0;
123		if parent.is_dead() { log::warn!("leave_parent parent dead {:?}", self.as_entity()); return; }
124
125		if let (Some(parent_node), Some(child_node)) = (parent.try_get_cmp::<web_sys::Node>(), self.try_get_cmp::<web_sys::Node>()) {
126			parent_node.remove_child(&child_node).expect("can't remove child");
127		}
128
129		self.remove_cmp::<Parent>();
130		let mut siblings = parent.get_cmp_mut::<Children>();
131		if let Some(child_pos) = siblings.0.iter().position(|&x| x == self.as_entity()) {
132			siblings.0.remove(child_pos);
133		}
134	}
135
136	#[track_caller]
137	fn add_child_at(self, at_index: usize, child: Element) {
138		if self.is_dead() { log::warn!("add_child_at parent dead {:?}", self.as_entity()); return; }
139		if child.is_dead() { log::warn!("add_child_at child dead {:?}", child.as_entity()); return; }
140
141		let mut children = self.get_cmp_mut_or_default::<Children>();
142		let shifted_sibling = children.get(at_index).copied();
143		children.insert(at_index, child.as_entity());
144		child.get_cmp_mut_or_default::<Parent>().0 = self.as_entity();
145
146		if let (Some(parent_node), Some(child_node), shifted_sibling_node) = (
147			self.try_get_cmp::<web_sys::Node>(),
148			child.try_get_cmp::<web_sys::Node>(),
149			shifted_sibling.and_then(|x| x.try_get_cmp::<web_sys::Node>()),
150		) {
151			parent_node
152				.insert_before(&child_node, shifted_sibling_node.as_ref().map(|x| &**x as &web_sys::Node))
153				.expect("can't append child");
154
155
156			#[cfg(debug_assertions)] {
157				let caller = std::panic::Location::caller();
158				child.set_attr("data-location", &format!("{}:{}", caller.file(), caller.line()));
159			}
160		}
161	}
162
163	// this track_caller doesn't work exactly how I'd want, the `data-location` attr for the child is set to the `.replace_with` line
164	#[track_caller]
165	fn add_child_signal<S>(self, signal: S) where
166		S: Signal<Item = Element> + 'static,
167	{
168		// placeholder at first
169		let mut child = crate::create::div().class(crate::css::Display::None).as_element();
170		self.add_child(child);
171		let (handle, fut) = futures_signals::cancelable_future(signal.for_each(move |new_child| {
172			let new_child = new_child.as_element();
173			child.replace_with(new_child);
174			child = new_child;
175			std::future::ready(())
176		}), Default::default);
177
178		wasm_bindgen_futures::spawn_local(fut);
179		self.get_cmp_mut_or_default::<SignalHandlesCollection>().0.push(handle);
180	}
181
182	#[track_caller]
183	fn replace_with(self, other: Element) {
184		let other_entity = other.as_entity();
185		if self.is_dead() { log::warn!("replace_with dead {:?}", self.as_entity()); return; }
186
187		if let (Some(this), Some(other)) = (self.try_get_cmp::<web_sys::Element>(), other_entity.try_get_cmp::<web_sys::Node>()) {
188			this.replace_with_with_node_1(&other).unwrap();
189		} else {
190			let self_has = if self.has_cmp::<web_sys::Node>() { "has" } else { "doesn't have" };
191			let other_has = if other.has_cmp::<web_sys::Node>() { "has" } else { "doesn't have" };
192			log::warn!("trying to replace_with, but self {self_has} web_sys::Node and other {other_has} web_sys::Node");
193		}
194
195		#[cfg(debug_assertions)] {
196			let caller = std::panic::Location::caller();
197			other.set_attr("data-location", &format!("{}:{}", caller.file(), caller.line()));
198		}
199
200		// Fix up reference in parent
201		if let Some(parent) = self.try_get_cmp::<Parent>().map(|x| x.0) {
202			if parent.is_dead() { log::warn!("replace_with parent dead {:?}", parent); return; }
203			let mut children = parent.get_cmp_mut::<Children>();
204			let position = children.0.iter().position(|&x| x == self.as_entity()).expect("entity claims to be a child while missing in parent");
205			children.0[position] = other.as_entity();
206			other_entity.get_cmp_mut_or_default::<Parent>().0 = parent;
207
208			#[cfg(debug_assertions)]
209			other.remove_cmp::<Complainer>();
210		}
211
212		self.remove();
213	}
214}
215
216/// Marker trait for an entity that has `web_sys::Node`, `web_sys::Element`, `web_sys::EventTarget` and one of `web_sys::HtmlElement` or `web_sys::SvgElement` as attached components
217pub trait AsElement: AsEntity + Sized {
218	#[cfg(feature = "experimental")]
219	const MARK: Option<fn() -> std::any::TypeId> = None;
220
221	#[cfg(all(debug_assertions, feature = "experimental"))]
222	const TYPE: Option<fn() -> &'static str> = None;
223
224	#[track_caller]
225	fn add_child<T: AsElement>(&self, child: T) {
226		#[cfg(feature = "experimental")]
227		if let Some(mark) = T::MARK { child.get_cmp_mut_or_default::<Classes>().marks.insert(mark()); }
228
229		#[cfg(all(debug_assertions, feature = "experimental"))]
230		if let Some(type_id) = T::TYPE { child.set_attr("data-type", type_id()); }
231
232		Element::add_child(self.as_element(), child.as_element());
233	}
234	#[track_caller] #[must_use] fn child(self, child: impl AsElement) -> Self { self.add_child(child); self }
235	#[track_caller] #[must_use] fn with_child<T: AsElement>(self, f: impl FnOnce(&Self) -> T) -> Self { let c = f(&self); self.child(c) }
236	#[track_caller] fn add_children<Item: AsElement>(&self, children: impl IntoIterator<Item = Item>) { for child in children.into_iter() { self.add_child(child); } }
237	#[track_caller] #[must_use] fn children<Item: AsElement>(self, children: impl IntoIterator<Item = Item>) -> Self { self.add_children(children); self }
238	fn leave_parent(self) { Element::leave_parent(self.as_element()) }
239
240	/// add a child at an index, useful to update tables without regenerating the whole container element
241	#[track_caller]
242	fn add_child_at<T: AsElement>(&self, at_index: usize, child: T) {
243		#[cfg(feature = "experimental")]
244		if let Some(mark) = T::MARK { child.get_cmp_mut_or_default::<Classes>().marks.insert(mark()); }
245
246		#[cfg(all(debug_assertions, feature = "experimental"))]
247		if let Some(type_id) = T::TYPE { child.set_attr("data-type", type_id()); }
248
249		Element::add_child_at(self.as_element(), at_index, child.as_element());
250	}
251
252	// be mindful about holding child references with this one
253	#[track_caller]
254	fn add_child_signal<S, E>(&self, signal: S) where
255		E: AsElement,
256		S: Signal<Item = E> + 'static,
257	{
258		Element::add_child_signal(self.as_element(), signal.map(|x| x.as_element()));
259	}
260	#[track_caller]
261	#[must_use]
262	fn child_signal<S, E>(self, signal: S) -> Self where
263		E: AsElement,
264		S: Signal<Item = E> + 'static,
265	{ self.add_child_signal(signal); self }
266
267	fn set_class_tagged<Tag: std::hash::Hash + 'static>(&self, tag: Tag, style: impl Into<css::Style>) {
268		if self.is_dead() { log::warn!("set_class_tagged dead {:?}", self.as_entity()); return; }
269
270		// tested and different types with same byte-level representation hash to the same thing (not surprising)
271		// i.e. the type is not taken into account when hashing so I have to do it manually
272		let tag_hash = {
273			use std::hash::{Hash, Hasher};
274			let mut hasher = std::collections::hash_map::DefaultHasher::new();
275			TypeId::of::<Tag>().hash(&mut hasher);
276			tag.hash(&mut hasher);
277			hasher.finish()
278		};
279
280		let mut classes = self.get_cmp_mut_or_default::<Classes>();
281		let len = classes.styles.len();
282		classes.styles.insert(tag_hash, (style.into(), len));
283	}
284	fn set_class_typed<Type: 'static>(&self, style: impl Into<css::Style>) { self.set_class_tagged(TypeId::of::<Type>(), style) }
285	fn set_class(&self, style: impl Into<css::Style>) { self.set_class_tagged(0u64, style); }
286	fn add_class(&self, style: impl Into<css::Style>) {
287		let id = self.try_get_cmp::<Classes>().map(|x| x.styles.len() as u64).unwrap_or(0);
288		self.set_class_tagged(id, style);
289	}
290	#[must_use] fn class(self, style: impl Into<css::Style>) -> Self { self.add_class(style); self }
291	#[must_use] fn class_tagged<Tag: std::hash::Hash + 'static>(self, tag: Tag, style: impl Into<css::Style>) -> Self { self.set_class_tagged(tag, style); self }
292	#[must_use] fn class_typed<Type: 'static>(self, style: impl Into<css::Style>) -> Self { self.set_class_typed::<Type>(style); self }
293
294	fn set_class_signal<S, I>(&self, signal: S) where
295		I: Into<css::Style>,
296		S: Signal<Item = I> + 'static,
297	{
298		let entity = self.as_entity();
299		if entity.is_dead() { log::warn!("set_class_signal dead entity {:?}", entity); return; }
300		let (handle, fut) = futures_signals::cancelable_future(signal.for_each(move |class| {
301			Element(entity).set_class(class);
302			std::future::ready(())
303		}), Default::default);
304
305		wasm_bindgen_futures::spawn_local(fut);
306		self.get_cmp_mut_or_default::<SignalHandlesCollection>().0.push(handle);
307	}
308	#[must_use]
309	fn class_signal<S, I>(self, signal: S) -> Self where
310		I: Into<css::Style>,
311		S: Signal<Item = I> + 'static,
312	{ self.set_class_signal(signal); self }
313
314	fn set_class_typed_signal<Type, S, I>(&self, signal: S) where
315		Type: 'static,
316		I: Into<css::Style>,
317		S: Signal<Item = I> + 'static,
318	{
319		let entity = self.as_entity();
320		if entity.is_dead() { log::warn!("set_class_signal dead entity {:?}", entity); return; }
321		let (handle, fut) = futures_signals::cancelable_future(signal.for_each(move |class| {
322			Element(entity).set_class_typed::<Type>(class.into());
323			std::future::ready(())
324		}), Default::default);
325
326		wasm_bindgen_futures::spawn_local(fut);
327		self.get_cmp_mut_or_default::<SignalHandlesCollection>().0.push(handle);
328	}
329	#[must_use]
330	fn class_typed_signal<Type, S, I>(self, signal: S) -> Self where
331		Type: 'static,
332		I: Into<css::Style>,
333		S: Signal<Item = I> + 'static,
334	{ self.set_class_typed_signal::<Type, S, I>(signal); self }
335
336	fn set_class_tagged_signal<Tag, S, I>(&self, tag: Tag, signal: S) where
337		Tag: std::hash::Hash + Copy + 'static,
338		I: Into<css::Style>,
339		S: Signal<Item = I> + 'static,
340	{
341		let entity = self.as_entity();
342		if entity.is_dead() { log::warn!("set_class_signal dead entity {:?}", entity); return; }
343		let (handle, fut) = futures_signals::cancelable_future(signal.for_each(move |class| {
344			Element(entity).set_class_tagged(tag, class);
345			std::future::ready(())
346		}), Default::default);
347
348		wasm_bindgen_futures::spawn_local(fut);
349		self.get_cmp_mut_or_default::<SignalHandlesCollection>().0.push(handle);
350	}
351	#[must_use]
352	fn class_tagged_signal<Tag, S, I>(self, tag: Tag, signal: S) -> Self where
353		Tag: std::hash::Hash + Copy + 'static,
354		I: Into<css::Style>,
355		S: Signal<Item = I> + 'static,
356	{ self.set_class_tagged_signal::<Tag, S, I>(tag, signal); self }
357
358	fn get_attr<'k>(&self, key: impl Into<Cow<'k, str>>) -> Option<String> {
359		if self.is_dead() { log::warn!("get_attr dead {:?}", self.as_entity()); return None; }
360		let key = key.into();
361		self.get_cmp::<web_sys::Element>().get_attribute(&key)
362	}
363	fn set_attr<'k, 'v>(&self, key: impl Into<Cow<'k, str>>, value: impl Into<Cow<'v, str>>) {
364		if self.is_dead() { log::warn!("set_attr dead {:?}", self.as_entity()); return; }
365		let key = key.into();
366		let value = value.into();
367		self.get_cmp::<web_sys::Element>().set_attribute(&key, &value).unwrap_or_else(|_| panic!("can't set attribute {} to {}", key, value));
368	}
369	#[must_use] fn attr<'k, 'v>(self, key: impl Into<Cow<'k, str>>, value: impl Into<Cow<'v, str>>) -> Self { self.set_attr(key, value); self }
370	fn set_bool_attr<'k>(&self, key: impl Into<Cow<'k, str>>, value: bool) { if value { self.set_attr(key, "") } else { self.remove_attr(key) } }
371	#[must_use] fn bool_attr<'k>(self, key: impl Into<Cow<'k, str>>, value: bool) -> Self { self.set_bool_attr(key, value); self }
372	fn remove_attr<'k>(&self, key: impl Into<Cow<'k, str>>) {
373		if self.is_dead() { log::warn!("remove_attr dead {:?}", self.as_entity()); return; }
374		self.get_cmp::<web_sys::Element>().remove_attribute(&key.into()).expect("can't remove attribute");
375	}
376
377	fn set_attr_signal<'k, 'v, S, K, V>(&self, attr: K, signal: S) where
378		K: Into<Cow<'k, str>>,
379		V: Into<Cow<'v, str>>,
380		S: Signal<Item = V> + 'static,
381	{
382		let entity = self.as_entity();
383		if entity.is_dead() { log::warn!("set_attr_signal dead entity {:?}", entity); return; }
384		let attr = attr.into().into_owned();
385		let (handle, fut) = futures_signals::cancelable_future(signal.for_each(move |v| {
386			Element(entity).set_attr(&attr, v);
387			std::future::ready(())
388		}), Default::default);
389
390		wasm_bindgen_futures::spawn_local(fut);
391		self.get_cmp_mut_or_default::<SignalHandlesCollection>().0.push(handle);
392	}
393	#[must_use]
394	fn attr_signal<'k, 'v, S, K, V>(self, attr: K, signal: S) -> Self where
395		K: Into<Cow<'k, str>>,
396		V: Into<Cow<'v, str>>,
397		S: Signal<Item = V> + 'static,
398	{ self.set_attr_signal(attr, signal); self }
399
400	fn set_bool_attr_signal<'k, S, K>(&self, attr: K, signal: S) where
401		K: Into<Cow<'k, str>>,
402		S: Signal<Item = bool> + 'static,
403	{
404		let entity = self.as_entity();
405		if entity.is_dead() { log::warn!("set_attr_signal dead entity {:?}", entity); return; }
406		let attr = attr.into().into_owned();
407		let (handle, fut) = futures_signals::cancelable_future(signal.for_each(move |v| {
408			Element(entity).set_bool_attr(&attr, v);
409			std::future::ready(())
410		}), Default::default);
411
412		wasm_bindgen_futures::spawn_local(fut);
413		self.get_cmp_mut_or_default::<SignalHandlesCollection>().0.push(handle);
414	}
415	#[must_use]
416	fn bool_attr_signal<'k, S, K>(self, attr: K, signal: S) -> Self where
417		K: Into<Cow<'k, str>>,
418		S: Signal<Item = bool> + 'static,
419	{ self.set_bool_attr_signal(attr, signal); self }
420
421	fn set_text<'a>(&self, text: impl Into<std::borrow::Cow<'a, str>>) {
422		if self.is_dead() { log::warn!("set_text dead entity {:?}", self.as_entity()); return; }
423		self.get_cmp::<web_sys::Node>().set_text_content(Some(&text.into()));
424	}
425	#[must_use] fn text<'a>(self, x: impl Into<std::borrow::Cow<'a, str>>) -> Self { self.set_text(x); self }
426
427	fn set_text_signal<'a, S, I>(&self, signal: S) where
428		I: Into<Cow<'a, str>>,
429		S: Signal<Item = I> + 'static,
430	{
431		let entity = self.as_entity();
432		if entity.is_dead() { log::warn!("set_text_signal dead entity {:?}", entity); return; }
433		let (handle, fut) = futures_signals::cancelable_future(signal.for_each(move |text| {
434			Element(entity).set_text(text);
435			std::future::ready(())
436		}), Default::default);
437
438		wasm_bindgen_futures::spawn_local(fut);
439		self.get_cmp_mut_or_default::<SignalHandlesCollection>().0.push(handle);
440	}
441	#[must_use]
442	fn text_signal<'a, S, I>(self, x: S) -> Self where
443		I: Into<Cow<'a, str>>,
444		S: Signal<Item = I> + 'static,
445	{ self.set_text_signal(x); self }
446
447	fn set_style(&self, style: impl AppendProperty) {
448		let mut props = Vec::new();
449		style.append_property(&mut props);
450		self.set_attr(web_str::style(), props.iter().map(std::string::ToString::to_string).collect::<String>());
451	}
452	#[must_use] fn style(self, style: impl AppendProperty) -> Self { self.set_style(style); self }
453	fn remove_style(&self) { self.remove_attr(web_str::style()); }
454
455	fn set_style_signal<S, I>(&self, signal: S) where
456		I: AppendProperty,
457		S: Signal<Item = I> + 'static,
458	{
459		let entity = self.as_entity();
460		if entity.is_dead() { log::warn!("set_style_signal dead entity {:?}", entity); return; }
461		let (handle, fut) = futures_signals::cancelable_future(signal.for_each(move |style| {
462			Element(entity).set_style(style);
463			std::future::ready(())
464		}), Default::default);
465
466		wasm_bindgen_futures::spawn_local(fut);
467		self.get_cmp_mut_or_default::<SignalHandlesCollection>().0.push(handle);
468	}
469	#[must_use]
470	fn style_signal<S, I>(self, signal: S) -> Self where
471		I: AppendProperty,
472		S: Signal<Item = I> + 'static,
473	{ self.set_style_signal(signal); self }
474
475	fn mark<T: 'static>(self) -> Self {
476		if self.is_dead() { log::warn!("mark dead {:?}", self.as_entity()); return self; }
477		self.get_cmp_mut_or_default::<Classes>().marks.insert(TypeId::of::<T>());
478		self
479	}
480	fn unmark<T: 'static>(self) -> Self {
481		if self.is_dead() { log::warn!("unmark dead {:?}", self.as_entity()); return self; }
482		self.get_cmp_mut_or_default::<Classes>().marks.remove(&TypeId::of::<T>());
483		self
484	}
485	#[must_use]
486	fn mark_signal<T: 'static, S>(self, signal: S) -> Self where
487		S: Signal<Item = bool> + 'static,
488	{
489		let entity = self.as_entity();
490		if entity.is_dead() { log::warn!("mark_signal dead entity {:?}", entity); return self; }
491		let (handle, fut) = futures_signals::cancelable_future(signal.for_each(move |enabled| {
492			if enabled { Element(entity).mark::<T>(); } else { Element(entity).unmark::<T>(); }
493			std::future::ready(())
494		}), Default::default);
495
496		wasm_bindgen_futures::spawn_local(fut);
497		self.get_cmp_mut_or_default::<SignalHandlesCollection>().0.push(handle);
498		self
499	}
500
501	#[must_use] fn with_component<T: 'static>(self, f: impl FnOnce(&Self) -> T) -> Self { self.add_component(f(&self)); self }
502
503	// can't steal components because handlers would get invalidated
504	#[track_caller]
505	fn replace_with<T: AsElement>(&self, other: T) -> T {
506		#[cfg(feature = "experimental")]
507		if let Some(mark) = T::MARK { other.get_cmp_mut_or_default::<Classes>().marks.insert(mark()); }
508
509		#[cfg(all(debug_assertions, feature = "experimental"))]
510		if let Some(type_id) = T::TYPE { other.set_attr("data-type", type_id()); }
511
512		Element::replace_with(self.as_element(), other.as_element());
513		other
514	}
515
516	fn parent(&self) -> Element {
517		let parent = self.get_cmp::<Parent>().0;
518		debug_assert!(parent.try_get_cmp::<web_sys::HtmlElement>().is_some());
519		Element(parent)
520	}
521
522	#[cfg(feature = "experimental")]
523	fn add_on_dom_attach(&self, cb: impl FnOnce() + Send + Sync + 'static) {
524		if self.has_cmp::<InDom>() { cb(); return; }
525		self.get_cmp_mut_or_default::<OnDomAttachCbs>().0.push(Box::new(cb));
526	}
527	#[cfg(feature = "experimental")]
528	fn on_dom_attach(self, cb: impl FnOnce() + Send + Sync + 'static) -> Self { self.add_on_dom_attach(cb); self }
529
530	#[deprecated = "use .tap() instead"]
531	fn with(self, f: impl FnOnce(&Self)) -> Self { f(&self); self }
532	fn as_element(&self) -> Element { Element(self.as_entity()) }
533}
534
535impl<T: AsElement> AsElement for &T {}
536impl<T: AsElement> AsElement for &mut T {}