use rdom_core::{Dom, Mutation, MutationObserver, ObserverId};
use rdom_style::TuiStyle;
use crate::TuiExt;
pub fn install(dom: &mut Dom<TuiExt>) -> ObserverId {
dom.add_mutation_observer(Box::new(InlineStyleObserver))
}
struct InlineStyleObserver;
impl MutationObserver<TuiExt> for InlineStyleObserver {
fn observe(&mut self, dom: &mut Dom<TuiExt>, record: &Mutation) {
let (id, new_value) = match record {
Mutation::AttributeChanged { id, name, new, .. } if name == "style" => {
(*id, new.clone())
}
_ => return,
};
if super::reentry::is_in_cssom_write() {
return;
}
let parsed = match new_value {
Some(text) => rdom_css::parse_inline(&text).style,
None => TuiStyle::new(),
};
if let Some(ext) = dom.node_mut(id).ext_mut() {
ext.inline_style = parsed;
}
}
}
#[cfg(test)]
mod tests {
use crate::node::TuiNodeExt;
use crate::{TuiAccessorsMut, TuiDom};
use rdom_style::{Color, TuiColor, Value};
fn dom_with_div() -> (TuiDom, rdom_core::NodeId) {
let mut dom: TuiDom = TuiDom::new();
let root = dom.root();
let div = dom.create_element("div");
dom.append_child(root, div).unwrap();
super::install(&mut dom);
(dom, div)
}
#[test]
fn external_style_attribute_write_refreshes_inline_style() {
let (mut dom, div) = dom_with_div();
dom.set_attribute(div, "style", "color: red").unwrap();
let fg = dom.node(div).tui_ext().unwrap().inline_style.fg.clone();
assert_eq!(
fg,
Some(Value::Specified(TuiColor::Literal(Color::Rgb(255, 0, 0))))
);
}
#[test]
fn external_style_attribute_removal_clears_inline_style() {
let (mut dom, div) = dom_with_div();
dom.set_attribute(div, "style", "color: red").unwrap();
dom.remove_attribute(div, "style").unwrap();
let fg = dom.node(div).tui_ext().unwrap().inline_style.fg.clone();
assert!(fg.is_none(), "removing style attribute should clear fg");
}
#[test]
fn cssom_write_does_not_double_parse() {
let (mut dom, div) = dom_with_div();
dom.node_mut(div)
.style_mut()
.unwrap()
.set_property_important("color", "red")
.unwrap();
let inline = dom.node(div).tui_ext().unwrap().inline_style.clone();
assert_eq!(
inline.fg,
Some(Value::Specified(TuiColor::Literal(Color::Rgb(255, 0, 0))))
);
use rdom_style::ImportantMask;
assert!(inline.important.contains(ImportantMask::FG));
}
#[test]
fn observer_ignores_non_style_attribute_writes() {
let (mut dom, div) = dom_with_div();
dom.set_attribute(div, "class", "hero").unwrap();
let fg = dom.node(div).tui_ext().unwrap().inline_style.fg.clone();
assert!(fg.is_none());
}
#[test]
fn invalid_value_clears_or_warns_silently() {
let (mut dom, div) = dom_with_div();
dom.set_attribute(div, "style", "bogus-property: x")
.unwrap();
let fg = dom.node(div).tui_ext().unwrap().inline_style.fg.clone();
assert!(fg.is_none());
}
#[test]
fn install_default_observers_wires_inline_style_observer() {
let mut dom: TuiDom = TuiDom::new();
let root = dom.root();
let div = dom.create_element("div");
dom.append_child(root, div).unwrap();
crate::cssom::install_default_observers(&mut dom);
dom.set_attribute(div, "style", "color: red").unwrap();
let fg = dom.node(div).tui_ext().unwrap().inline_style.fg.clone();
assert_eq!(
fg,
Some(Value::Specified(TuiColor::Literal(Color::Rgb(255, 0, 0))))
);
}
}