terrazzo_client/
attribute.rs1use nameth::NamedType as _;
4use nameth::nameth;
5use wasm_bindgen::JsCast as _;
6use web_sys::Element;
7use web_sys::HtmlElement;
8
9use self::inner::AttributeTemplateInner;
10use crate::debug_correlation_id::DebugCorrelationId;
11use crate::prelude::OrElseLog;
12use crate::prelude::diagnostics::trace;
13use crate::prelude::diagnostics::warn;
14use crate::signal::depth::Depth;
15use crate::signal::reactive_closure::reactive_closure_builder::Consumers;
16use crate::string::XString;
17use crate::template::IsTemplate;
18use crate::template::IsTemplated;
19use crate::utils::Ptr;
20
21#[nameth]
46pub struct XAttribute {
47 pub name: XAttributeName,
49
50 pub value: XAttributeValue,
52}
53
54#[derive(Clone, PartialEq, Eq, Hash)]
56pub enum XAttributeName {
57 Attribute(XString),
59
60 Style(XString),
75}
76
77pub enum XAttributeValue {
81 Null,
83
84 Static(XString),
86
87 Dynamic(XDynamicAttribute),
89
90 Generated {
92 template: XAttributeTemplate,
93 consumers: Consumers,
94 },
95}
96
97pub struct XDynamicAttribute(pub Box<dyn Fn(XAttributeTemplate) -> Consumers>);
99
100impl<F: Fn(XAttributeTemplate) -> Consumers + 'static> From<F> for XDynamicAttribute {
101 fn from(value: F) -> Self {
102 let _ = XAttribute {
103 name: "type".into(),
104 value: "text".into(),
105 };
106 Self(Box::new(value))
107 }
108}
109
110#[derive(Clone)]
112pub struct XAttributeTemplate(Ptr<AttributeTemplateInner>);
113
114mod inner {
115 use std::ops::Deref;
116
117 use web_sys::Element;
118
119 use super::XAttributeName;
120 use super::XAttributeTemplate;
121 use crate::debug_correlation_id::DebugCorrelationId;
122 use crate::signal::depth::Depth;
123
124 pub struct AttributeTemplateInner {
125 pub element: Element,
126 pub attribute_name: XAttributeName,
127 pub(super) debug_id: DebugCorrelationId<String>,
128 pub(super) depth: Depth,
129 }
130
131 impl Deref for XAttributeTemplate {
132 type Target = AttributeTemplateInner;
133
134 fn deref(&self) -> &Self::Target {
135 &self.0
136 }
137 }
138}
139
140impl IsTemplate for XAttributeTemplate {
141 type Value = XAttributeValue;
142
143 fn apply<R: Into<Self::Value>>(self, new: impl FnOnce() -> R) {
144 let mut new = XAttribute {
145 name: self.attribute_name.clone(),
146 value: new().into(),
147 };
148 new.merge(self.depth, None, &self.element);
149 }
150
151 fn depth(&self) -> Depth {
152 self.depth
153 }
154
155 fn debug_id(&self) -> &DebugCorrelationId<impl std::fmt::Display> {
156 &self.debug_id
157 }
158}
159
160impl IsTemplated for XAttributeValue {
161 type Template = XAttributeTemplate;
162}
163
164impl<T> From<T> for XAttributeValue
165where
166 XString: From<T>,
167{
168 fn from(value: T) -> Self {
169 Self::Static(value.into())
170 }
171}
172
173impl<T> From<Option<T>> for XAttributeValue
174where
175 XString: From<T>,
176{
177 fn from(value: Option<T>) -> Self {
178 match value {
179 Some(value) => Self::Static(value.into()),
180 None => Self::Null,
181 }
182 }
183}
184
185impl XAttribute {
186 pub fn merge(
187 &mut self,
188 depth: Depth,
189 old_attribute_value: Option<XAttributeValue>,
190 element: &Element,
191 ) {
192 let new_attribute = self;
193 let attribute_name = &new_attribute.name;
194 match &new_attribute.value {
195 XAttributeValue::Null => {
196 merge_static_atttribute(element, attribute_name, None, old_attribute_value);
197 }
198 XAttributeValue::Static(new_attribute_value) => {
199 merge_static_atttribute(
200 element,
201 attribute_name,
202 Some(new_attribute_value),
203 old_attribute_value,
204 );
205 }
206 XAttributeValue::Dynamic(XDynamicAttribute(new_attribute_value)) => {
207 new_attribute.value = merge_dynamic_atttribute(
208 depth,
209 element,
210 attribute_name,
211 new_attribute_value,
212 old_attribute_value,
213 );
214 }
215 XAttributeValue::Generated { .. } => {
216 warn!("Illegal {} state", XAttribute::type_name());
217 debug_assert!(false);
218 }
219 }
220 }
221}
222
223fn merge_static_atttribute(
224 element: &Element,
225 attribute_name: &XAttributeName,
226 new_value: Option<&XString>,
227 old_value: Option<XAttributeValue>,
228) {
229 if let Some((XAttributeValue::Static(old_attribute_value), new_value)) =
230 old_value.as_ref().zip(new_value)
231 {
232 if new_value == old_attribute_value {
233 trace!("Attribute '{attribute_name}' is still '{new_value}'");
234 return;
235 }
236 }
237 drop(old_value);
238 let Some(new_value) = new_value else {
239 match attribute_name {
240 XAttributeName::Attribute(name) => match element.remove_attribute(name.as_str()) {
241 Ok(()) => trace!("Removed attribute {name}"),
242 Err(error) => warn!("Removed attribute {name} failed: {error:?}"),
243 },
244 XAttributeName::Style(name) => {
245 let html_element: &HtmlElement = element.dyn_ref().or_throw("HtmlElement");
246 let style = html_element.style();
247 match style.remove_property(name) {
248 Ok(value) => trace!("Removed style {name}: {value}"),
249 Err(error) => warn!("Removed style {name} failed: {error:?}"),
250 }
251 }
252 }
253 return;
254 };
255 match attribute_name {
256 XAttributeName::Attribute(name) => match element.set_attribute(name, new_value) {
257 Ok(()) => trace!("Set attribute '{name}' to '{new_value}'"),
258 Err(error) => warn!("Set attribute '{name}' to '{new_value}' failed: {error:?}"),
259 },
260 XAttributeName::Style(name) => {
261 let html_element: &HtmlElement = element.dyn_ref().or_throw("HtmlElement");
262 let style = html_element.style();
263 match style.set_property(name, new_value) {
264 Ok(()) => trace!("Set style {name}: {new_value}"),
265 Err(error) => warn!("Set style {name}: {new_value} failed: {error:?}"),
266 }
267 }
268 }
269}
270
271fn merge_dynamic_atttribute(
272 depth: Depth,
273 element: &Element,
274 attribute_name: &XAttributeName,
275 new_attribute_value: &dyn Fn(XAttributeTemplate) -> Consumers,
276 old_attribute_value: Option<XAttributeValue>,
277) -> XAttributeValue {
278 let new_template = if let Some(XAttributeValue::Generated {
279 template: old_template,
280 consumers: old_consumers,
281 }) = old_attribute_value
282 {
283 trace!("Reuse exising attribute template {attribute_name}");
284 drop(old_consumers);
285 old_template
286 } else {
287 trace!("Create a new attribute template {attribute_name}");
288 XAttributeTemplate(Ptr::new(AttributeTemplateInner {
289 element: element.clone(),
290 attribute_name: attribute_name.clone(),
291 debug_id: DebugCorrelationId::new(|| format!("attribute_template:{attribute_name}")),
292 depth: depth.next(),
293 }))
294 };
295 XAttributeValue::Generated {
296 template: new_template.clone(),
297 consumers: new_attribute_value(new_template),
298 }
299}
300
301impl<T> From<T> for XAttributeName
302where
303 T: Into<XString>,
304{
305 fn from(value: T) -> Self {
306 Self::Attribute(value.into())
307 }
308}
309
310impl std::fmt::Display for XAttributeName {
311 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
312 match self {
313 Self::Attribute(name) => std::fmt::Display::fmt(name, f),
314 Self::Style(name) => write!(f, "style::{name}"),
315 }
316 }
317}