diff --git a/packages/yew/src/dom_bundle/btag/attributes.rs b/packages/yew/src/dom_bundle/btag/attributes.rs index 3bdfcf3e5bb..1b158f54d54 100644 --- a/packages/yew/src/dom_bundle/btag/attributes.rs +++ b/packages/yew/src/dom_bundle/btag/attributes.rs @@ -181,6 +181,21 @@ impl Attributes { } } + /// Like [`set`](Self::set), but skips writing an `Attribute` when the + /// element already carries the same value. + #[cfg(feature = "hydration")] + fn set_if_changed(el: &Element, key: &str, value: &AttributeOrProperty) { + match value { + AttributeOrProperty::Attribute(value) => { + let key = intern(key); + if el.get_attribute(key).as_deref() != Some(value.as_ref()) { + el.set_attribute(key, value).expect("invalid attribute key"); + } + } + AttributeOrProperty::Property(_) => Self::set(el, key, value), + } + } + fn remove(el: &Element, key: &str, old_value: &AttributeOrProperty) { match old_value { AttributeOrProperty::Attribute(_) => el @@ -223,6 +238,31 @@ impl Apply for Attributes { self } + #[cfg(feature = "hydration")] + fn hydrate(self, _root: &BSubtree, el: &Element) -> Self { + #[expect(deprecated)] + match &self { + Self::Static(arr) => { + for (k, v) in arr.iter() { + Self::set_if_changed(el, k, v); + } + } + Self::Dynamic { keys, values } => { + for (k, v) in keys.iter().zip(values.iter()) { + if let Some(v) = v { + Self::set_if_changed(el, k, v) + } + } + } + Self::IndexMap(m) => { + for (k, v) in m.iter() { + Self::set_if_changed(el, k, v) + } + } + } + self + } + fn apply_diff(self, _root: &BSubtree, el: &Element, bundle: &mut Self) { #[inline] fn ptr_eq(a: &[T], b: &[T]) -> bool { diff --git a/packages/yew/src/dom_bundle/btag/mod.rs b/packages/yew/src/dom_bundle/btag/mod.rs index 11733c7d8d0..3b822409e7b 100644 --- a/packages/yew/src/dom_bundle/btag/mod.rs +++ b/packages/yew/src/dom_bundle/btag/mod.rs @@ -35,6 +35,17 @@ trait Apply { /// Apply diff between [self] and `bundle` to [Element](Self::Element). fn apply_diff(self, root: &BSubtree, el: &Self::Element, bundle: &mut Self::Bundle); + + /// Apply contained values to an existing [Element](Self::Element). + /// + /// The default implementation delegates to [`apply`](Self::apply). + #[cfg(feature = "hydration")] + fn hydrate(self, root: &BSubtree, el: &Self::Element) -> Self::Bundle + where + Self: Sized, + { + self.apply(root, el) + } } /// [BTag] fields that are specific to different [BTag] kinds. @@ -401,8 +412,8 @@ mod feat_hydration { ); } } - // We simply register listeners and update all attributes. - let attributes = attributes.apply(root, &el); + // Register listeners and attributes. + let attributes = attributes.hydrate(root, &el); let listeners = listeners.apply(root, &el); // For input and textarea elements, we update their value anyways.