///|
/// Mutation Types and Records
///
/// Based on the MutationObserver API, with extensions for style changes.
/// Used for batching DOM changes before applying them to the layout tree.
///|
/// Type of DOM mutation
pub enum MutationType {
/// Child nodes added or removed
ChildList
/// Attribute changed
Attributes
/// Text content changed (for Text/Comment nodes)
CharacterData
/// Style property changed (extension for layout optimization)
StyleChange
} derive(Eq, Debug)
///|
/// Record of a single DOM mutation
pub struct MutationRecord {
/// Type of mutation
type_ : MutationType
/// Target node ID (the node that was mutated)
target : NodeId
/// For ChildList: nodes that were added
added_nodes : Array[NodeId]
/// For ChildList: nodes that were removed
removed_nodes : Array[NodeId]
/// For Attributes: the name of the changed attribute
attribute_name : String?
/// For Attributes: the old value
old_value : String?
/// For Attributes: the new value
new_value : String?
/// For StyleChange: which CSS properties changed
changed_properties : Array[String]
} derive(Debug)
///|
pub impl Show for MutationType with fn output(self, logger) {
let name = match self {
ChildList => "ChildList"
Attributes => "Attributes"
CharacterData => "CharacterData"
StyleChange => "StyleChange"
}
logger.write_string(name)
}
///|
pub impl Show for MutationRecord with fn output(self, logger) {
logger.write_string("{type_: \{self.type_}, target: \{self.target}, ")
logger.write_string("added_nodes: ")
logger.write_object(to_repr(self.added_nodes))
logger.write_string(", removed_nodes: ")
logger.write_object(to_repr(self.removed_nodes))
logger.write_string(", attribute_name: ")
logger.write_object(to_repr(self.attribute_name))
logger.write_string(", old_value: ")
logger.write_object(to_repr(self.old_value))
logger.write_string(", new_value: ")
logger.write_object(to_repr(self.new_value))
logger.write_string(", changed_properties: ")
logger.write_object(to_repr(self.changed_properties))
logger.write_string("}")
}
///|
/// Create a ChildList mutation record
pub fn MutationRecord::child_list(
target : NodeId,
added? : Array[NodeId] = [],
removed? : Array[NodeId] = [],
) -> MutationRecord {
{
type_: ChildList,
target,
added_nodes: added,
removed_nodes: removed,
attribute_name: None,
old_value: None,
new_value: None,
changed_properties: [],
}
}
///|
/// Create an Attributes mutation record
pub fn MutationRecord::attribute(
target : NodeId,
name : String,
old_value? : String? = None,
new_value? : String? = None,
) -> MutationRecord {
{
type_: Attributes,
target,
added_nodes: [],
removed_nodes: [],
attribute_name: Some(name),
old_value,
new_value,
changed_properties: [],
}
}
///|
/// Create a CharacterData mutation record
pub fn MutationRecord::character_data(
target : NodeId,
old_value? : String? = None,
new_value? : String? = None,
) -> MutationRecord {
{
type_: CharacterData,
target,
added_nodes: [],
removed_nodes: [],
attribute_name: None,
old_value,
new_value,
changed_properties: [],
}
}
///|
/// Create a StyleChange mutation record
pub fn MutationRecord::style_change(
target : NodeId,
properties : Array[String],
) -> MutationRecord {
{
type_: StyleChange,
target,
added_nodes: [],
removed_nodes: [],
attribute_name: None,
old_value: None,
new_value: None,
changed_properties: properties,
}
}
///|
/// Check if this mutation affects layout (vs paint-only)
pub fn MutationRecord::affects_layout(self : MutationRecord) -> Bool {
match self.type_ {
ChildList => true // Adding/removing children always affects layout
CharacterData => true // Text changes affect intrinsic size
Attributes =>
// Only certain attributes affect layout
match self.attribute_name {
Some("class") | Some("style") => true
Some("width") | Some("height") => true
_ => false
}
StyleChange =>
// Check if any changed property affects layout
self.changed_properties.iter().any(is_layout_property)
}
}
///|
/// Check if a CSS property affects layout (vs paint-only)
pub fn is_layout_property(prop : String) -> Bool {
match prop {
// Box model
"width"
| "height"
| "min-width"
| "max-width"
| "min-height"
| "max-height" => true
"margin"
| "margin-top"
| "margin-right"
| "margin-bottom"
| "margin-left" => true
"padding"
| "padding-top"
| "padding-right"
| "padding-bottom"
| "padding-left" => true
"border"
| "border-width"
| "border-top-width"
| "border-right-width"
| "border-bottom-width"
| "border-left-width" => true
// Display and positioning
"display" | "position" | "top" | "right" | "bottom" | "left" => true
"float" | "clear" => true
// Flexbox
"flex" | "flex-grow" | "flex-shrink" | "flex-basis" => true
"flex-direction" | "flex-wrap" | "flex-flow" => true
"justify-content" | "align-items" | "align-self" | "align-content" => true
"order" => true
// Grid
"grid-template-rows" | "grid-template-columns" | "grid-template-areas" =>
true
"grid-auto-rows" | "grid-auto-columns" | "grid-auto-flow" => true
"grid-row" | "grid-column" | "grid-row-start" | "grid-row-end" => true
"grid-column-start" | "grid-column-end" => true
"gap" | "row-gap" | "column-gap" => true
// Other layout
"overflow" | "overflow-x" | "overflow-y" => true
"box-sizing" => true
"contain" => true
"aspect-ratio" => true
// Text layout
"font-size" | "line-height" | "letter-spacing" | "word-spacing" => true
"text-align" | "white-space" | "word-break" | "overflow-wrap" => true
// Paint-only properties (return false)
_ => false
}
}