dioxus_web_component/
lib.rs1#![doc = include_str!("../README.md")]
2#![allow(clippy::multiple_crate_versions)]
3
4use std::sync::Arc;
5use std::sync::RwLock;
6
7use dioxus::dioxus_core::Element;
8use dioxus::hooks::UnboundedSender;
9use dioxus::logger::tracing::debug;
10use futures::channel::oneshot;
11use wasm_bindgen::prelude::*;
12use web_sys::HtmlElement;
13
14use crate::rust_component::RustComponent;
15
16pub use dioxus_web_component_macro::web_component;
17
18mod event;
19pub use self::event::*;
20
21mod style;
22pub use self::style::*;
23
24mod rust_component;
25
26pub use futures::StreamExt;
28
29#[derive(Debug)]
31#[non_exhaustive]
32pub enum Message {
33 SetAttribute {
35 name: String,
37 value: Option<String>,
39 },
40 Get {
42 name: String,
44 tx: oneshot::Sender<SharedJsValue>,
46 },
47 Set {
49 name: String,
51 value: SharedJsValue,
53 },
54}
55
56#[derive(Clone)]
57struct SharedEventTarget(web_sys::HtmlElement);
58
59#[allow(unsafe_code)]
60unsafe impl Send for SharedEventTarget {}
64
65#[allow(unsafe_code)]
66unsafe impl Sync for SharedEventTarget {}
70
71#[doc(hidden)]
72#[derive(Debug, Clone)]
73pub struct SharedJsValue(JsValue);
74
75#[allow(unsafe_code)]
76unsafe impl Send for SharedJsValue {}
80
81#[allow(unsafe_code)]
82unsafe impl Sync for SharedJsValue {}
86
87#[derive(Clone)]
89pub struct Shared {
90 attributes: Vec<String>,
91 event_target: SharedEventTarget,
92 tx: Arc<RwLock<Option<UnboundedSender<Message>>>>,
93}
94
95impl Shared {
96 #[must_use]
98 pub fn event_target(&self) -> &HtmlElement {
99 &self.event_target.0
100 }
101
102 pub fn set_tx(&mut self, tx: UnboundedSender<Message>) {
104 let trg = self.event_target();
106 for attr in &self.attributes {
107 let Some(value) = trg.get_attribute(attr) else {
108 continue;
109 };
110 let _ = tx.unbounded_send(Message::SetAttribute {
111 name: attr.to_string(),
112 value: Some(value),
113 });
114 }
115
116 if let Ok(mut cell) = self.tx.write() {
118 *cell = Some(tx);
119 }
120 }
121}
122
123pub trait DioxusWebComponent {
125 fn set_attribute(&mut self, attribute: &str, value: Option<String>) {
127 let _ = value;
128 let _ = attribute;
129 }
130
131 fn set_property(&mut self, property: &str, value: JsValue) {
133 let _ = value;
134 let _ = property;
135 }
136
137 fn get_property(&mut self, property: &str) -> JsValue {
139 let _ = property;
140 JsValue::undefined()
141 }
142
143 fn handle_message(&mut self, message: Message) {
145 debug!(?message, "handle message");
146 match message {
147 Message::SetAttribute { name, value } => self.set_attribute(&name, value),
148 Message::Get { name, tx } => {
149 let value = self.get_property(&name);
150 let _ = tx.send(SharedJsValue(value));
151 }
152 Message::Set { name, value } => self.set_property(&name, value.0),
153 }
154 }
155}
156
157#[wasm_bindgen(skip_typescript)]
159#[derive(Debug, Clone)]
160pub struct Property {
161 name: String,
163 readonly: bool,
165}
166
167impl Property {
168 pub fn new(name: impl Into<String>, readonly: bool) -> Self {
170 let name = name.into();
171 Self { name, readonly }
172 }
173}
174
175#[wasm_bindgen]
176impl Property {
177 #[wasm_bindgen(getter)]
179 #[must_use]
180 pub fn name(&self) -> String {
181 self.name.clone()
182 }
183
184 #[wasm_bindgen(getter)]
186 #[must_use]
187 pub fn readonly(&self) -> bool {
188 self.readonly
189 }
190}
191
192pub fn register_dioxus_web_component(
194 custom_tag: &str,
195 attributes: Vec<String>,
196 properties: Vec<Property>,
197 style: InjectedStyle,
198 dx_el_builder: fn() -> Element,
199) {
200 let rust_component = RustComponent {
201 attributes,
202 properties,
203 style,
204 dx_el_builder,
205 };
206 register_web_component(custom_tag, rust_component);
207}
208
209#[wasm_bindgen(module = "/src/shim.js")]
210extern "C" {
211 #[allow(unsafe_code)]
212 fn register_web_component(custom_tag: &str, rust_component: RustComponent);
213}