custom_element.rs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358
  1. use dioxus::prelude::*;
  2. use dioxus_native_core::{custom_element::CustomElement, prelude::*};
  3. use dioxus_native_core_macro::partial_derive_state;
  4. use shipyard::Component;
  5. #[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Component)]
  6. pub struct ColorState {
  7. color: usize,
  8. }
  9. #[partial_derive_state]
  10. impl State for ColorState {
  11. type ParentDependencies = (Self,);
  12. type ChildDependencies = ();
  13. type NodeDependencies = ();
  14. // The color state should not be effected by the shadow dom
  15. const TRAVERSE_SHADOW_DOM: bool = false;
  16. const NODE_MASK: NodeMaskBuilder<'static> = NodeMaskBuilder::new()
  17. .with_attrs(AttributeMaskBuilder::Some(&["color"]))
  18. .with_element();
  19. fn update<'a>(
  20. &mut self,
  21. _: NodeView,
  22. _: <Self::NodeDependencies as Dependancy>::ElementBorrowed<'a>,
  23. parent: Option<<Self::ParentDependencies as Dependancy>::ElementBorrowed<'a>>,
  24. _: Vec<<Self::ChildDependencies as Dependancy>::ElementBorrowed<'a>>,
  25. _: &SendAnyMap,
  26. ) -> bool {
  27. if let Some((parent,)) = parent {
  28. self.color = parent.color;
  29. }
  30. true
  31. }
  32. fn create<'a>(
  33. node_view: NodeView<()>,
  34. node: <Self::NodeDependencies as Dependancy>::ElementBorrowed<'a>,
  35. parent: Option<<Self::ParentDependencies as Dependancy>::ElementBorrowed<'a>>,
  36. children: Vec<<Self::ChildDependencies as Dependancy>::ElementBorrowed<'a>>,
  37. context: &SendAnyMap,
  38. ) -> Self {
  39. let mut myself = Self::default();
  40. myself.update(node_view, node, parent, children, context);
  41. myself
  42. }
  43. }
  44. #[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Component)]
  45. pub struct LayoutState {
  46. size: usize,
  47. }
  48. #[partial_derive_state]
  49. impl State for LayoutState {
  50. type ParentDependencies = (Self,);
  51. type ChildDependencies = ();
  52. type NodeDependencies = ();
  53. // The layout state should be effected by the shadow dom
  54. const TRAVERSE_SHADOW_DOM: bool = true;
  55. const NODE_MASK: NodeMaskBuilder<'static> = NodeMaskBuilder::new()
  56. .with_attrs(AttributeMaskBuilder::Some(&["size"]))
  57. .with_element();
  58. fn update<'a>(
  59. &mut self,
  60. view: NodeView,
  61. _: <Self::NodeDependencies as Dependancy>::ElementBorrowed<'a>,
  62. parent: Option<<Self::ParentDependencies as Dependancy>::ElementBorrowed<'a>>,
  63. _: Vec<<Self::ChildDependencies as Dependancy>::ElementBorrowed<'a>>,
  64. _: &SendAnyMap,
  65. ) -> bool {
  66. println!(
  67. "Updating layout state @{:?} {:?}",
  68. parent.as_ref().map(|(p,)| p.size),
  69. view.node_id()
  70. );
  71. if let Some(size) = view
  72. .attributes()
  73. .into_iter()
  74. .flatten()
  75. .find(|attr| attr.attribute.name == "size")
  76. {
  77. self.size = size
  78. .value
  79. .as_float()
  80. .or_else(|| size.value.as_int().map(|i| i as f64))
  81. .or_else(|| size.value.as_text().and_then(|i| i.parse().ok()))
  82. .unwrap_or(0.0) as usize;
  83. } else if let Some((parent,)) = parent {
  84. if parent.size > 0 {
  85. self.size -= 1;
  86. }
  87. }
  88. true
  89. }
  90. fn create<'a>(
  91. node_view: NodeView<()>,
  92. node: <Self::NodeDependencies as Dependancy>::ElementBorrowed<'a>,
  93. parent: Option<<Self::ParentDependencies as Dependancy>::ElementBorrowed<'a>>,
  94. children: Vec<<Self::ChildDependencies as Dependancy>::ElementBorrowed<'a>>,
  95. context: &SendAnyMap,
  96. ) -> Self {
  97. let mut myself = Self::default();
  98. myself.update(node_view, node, parent, children, context);
  99. myself
  100. }
  101. }
  102. mod dioxus_elements {
  103. macro_rules! builder_constructors {
  104. (
  105. $(
  106. $(#[$attr:meta])*
  107. $name:ident {
  108. $(
  109. $(#[$attr_method:meta])*
  110. $fil:ident: $vil:ident,
  111. )*
  112. };
  113. )*
  114. ) => {
  115. $(
  116. #[allow(non_camel_case_types)]
  117. $(#[$attr])*
  118. pub struct $name;
  119. #[allow(non_upper_case_globals)]
  120. impl $name {
  121. pub const TAG_NAME: &'static str = stringify!($name);
  122. pub const NAME_SPACE: Option<&'static str> = None;
  123. $(
  124. pub const $fil: (&'static str, Option<&'static str>, bool) = (stringify!($fil), None, false);
  125. )*
  126. }
  127. impl GlobalAttributes for $name {}
  128. )*
  129. }
  130. }
  131. pub trait GlobalAttributes {}
  132. pub trait SvgAttributes {}
  133. builder_constructors! {
  134. customelementslot {
  135. size: attr,
  136. };
  137. customelementnoslot {
  138. };
  139. testing132 {
  140. };
  141. }
  142. }
  143. struct CustomElementWithSlot {
  144. root: NodeId,
  145. slot: NodeId,
  146. }
  147. impl CustomElement for CustomElementWithSlot {
  148. const NAME: &'static str = "customelementslot";
  149. fn create(dom: &mut RealDom<()>) -> Self {
  150. let child = dom.create_node(ElementNode {
  151. tag: "div".into(),
  152. namespace: None,
  153. attributes: Default::default(),
  154. listeners: Default::default(),
  155. });
  156. let slot_id = child.id();
  157. let mut root = dom.create_node(ElementNode {
  158. tag: "div".into(),
  159. namespace: None,
  160. attributes: Default::default(),
  161. listeners: Default::default(),
  162. });
  163. root.add_child(slot_id);
  164. Self {
  165. root: root.id(),
  166. slot: slot_id,
  167. }
  168. }
  169. fn slot(&self) -> Option<NodeId> {
  170. Some(self.slot)
  171. }
  172. fn roots(&self) -> Vec<NodeId> {
  173. vec![self.root]
  174. }
  175. fn attributes_changed(
  176. &mut self,
  177. node: NodeMut<()>,
  178. attributes: &dioxus_native_core::node_ref::AttributeMask,
  179. ) {
  180. println!("attributes_changed");
  181. println!("{:?}", attributes);
  182. println!("{:?}: {:#?}", node.id(), &*node.node_type());
  183. }
  184. }
  185. struct CustomElementWithNoSlot {
  186. root: NodeId,
  187. }
  188. impl CustomElement for CustomElementWithNoSlot {
  189. const NAME: &'static str = "customelementnoslot";
  190. fn create(dom: &mut RealDom<()>) -> Self {
  191. let root = dom.create_node(ElementNode {
  192. tag: "div".into(),
  193. namespace: None,
  194. attributes: Default::default(),
  195. listeners: Default::default(),
  196. });
  197. Self { root: root.id() }
  198. }
  199. fn roots(&self) -> Vec<NodeId> {
  200. vec![self.root]
  201. }
  202. fn attributes_changed(
  203. &mut self,
  204. node: NodeMut<()>,
  205. attributes: &dioxus_native_core::node_ref::AttributeMask,
  206. ) {
  207. println!("attributes_changed");
  208. println!("{:?}", attributes);
  209. println!("{:?}: {:#?}", node.id(), &*node.node_type());
  210. }
  211. }
  212. #[test]
  213. fn custom_elements_work() {
  214. fn app(cx: Scope) -> Element {
  215. let count = use_state(cx, || 0);
  216. use_future!(cx, |count| async move {
  217. count.with_mut(|count| *count += 1);
  218. });
  219. cx.render(rsx! {
  220. customelementslot {
  221. size: "{count}",
  222. customelementslot {
  223. testing132 {}
  224. }
  225. }
  226. })
  227. }
  228. let rt = tokio::runtime::Builder::new_current_thread()
  229. .enable_time()
  230. .build()
  231. .unwrap();
  232. rt.block_on(async {
  233. let mut rdom = RealDom::new([LayoutState::to_type_erased(), ColorState::to_type_erased()]);
  234. rdom.register_custom_element::<CustomElementWithSlot>();
  235. let mut dioxus_state = DioxusState::create(&mut rdom);
  236. let mut dom = VirtualDom::new(app);
  237. let mutations = dom.rebuild();
  238. dioxus_state.apply_mutations(&mut rdom, mutations);
  239. let ctx = SendAnyMap::new();
  240. rdom.update_state(ctx);
  241. for _ in 0..10 {
  242. dom.wait_for_work().await;
  243. let mutations = dom.render_immediate();
  244. dioxus_state.apply_mutations(&mut rdom, mutations);
  245. let ctx = SendAnyMap::new();
  246. rdom.update_state(ctx);
  247. // render...
  248. rdom.traverse_depth_first(|node| {
  249. let node_type = &*node.node_type();
  250. let indent = " ".repeat(node.height() as usize);
  251. let color = *node.get::<ColorState>().unwrap();
  252. let size = *node.get::<LayoutState>().unwrap();
  253. let id = node.id();
  254. println!("{indent}{id:?} {color:?} {size:?} {node_type:?}");
  255. });
  256. }
  257. });
  258. }
  259. #[test]
  260. #[should_panic]
  261. fn slotless_custom_element_cant_have_children() {
  262. fn app(cx: Scope) -> Element {
  263. cx.render(rsx! {
  264. customelementnoslot {
  265. testing132 {}
  266. }
  267. })
  268. }
  269. let rt = tokio::runtime::Builder::new_current_thread()
  270. .enable_time()
  271. .build()
  272. .unwrap();
  273. rt.block_on(async {
  274. let mut rdom = RealDom::new([LayoutState::to_type_erased(), ColorState::to_type_erased()]);
  275. rdom.register_custom_element::<CustomElementWithNoSlot>();
  276. let mut dioxus_state = DioxusState::create(&mut rdom);
  277. let mut dom = VirtualDom::new(app);
  278. let mutations = dom.rebuild();
  279. dioxus_state.apply_mutations(&mut rdom, mutations);
  280. let ctx = SendAnyMap::new();
  281. rdom.update_state(ctx);
  282. });
  283. }
  284. #[test]
  285. fn slotless_custom_element() {
  286. fn app(cx: Scope) -> Element {
  287. cx.render(rsx! {
  288. customelementnoslot {
  289. }
  290. })
  291. }
  292. let rt = tokio::runtime::Builder::new_current_thread()
  293. .enable_time()
  294. .build()
  295. .unwrap();
  296. rt.block_on(async {
  297. let mut rdom = RealDom::new([LayoutState::to_type_erased(), ColorState::to_type_erased()]);
  298. rdom.register_custom_element::<CustomElementWithNoSlot>();
  299. let mut dioxus_state = DioxusState::create(&mut rdom);
  300. let mut dom = VirtualDom::new(app);
  301. let mutations = dom.rebuild();
  302. dioxus_state.apply_mutations(&mut rdom, mutations);
  303. let ctx = SendAnyMap::new();
  304. rdom.update_state(ctx);
  305. });
  306. }