Source code for duck.html.components.core.children

"""
Module containing a specialized children list for managing child components insertion and deletion.
"""
from duck.utils.eventlist import EventList
from duck.html.components.core.mutation import (
    on_mutation,
    Mutation,
    MutationCode,
)


[docs] class ChildrenList(EventList): """ A specialized list to manage child components of an InnerComponent. This class ensures that child components maintain correct parent-child relationships by updating references whenever a child is added or removed. Inherits from: EventList: A custom list that provides hooks for item insertion and deletion. """ def __init__(self, parent: "Component", initlist = None, skip_initlist_events: bool = False): """ Initializes the ChildrenList with a reference to its parent component. Args: parent (Component): The parent component to which children belong. initlist (Any): The initialization list to start with. skip_initlist_events (bool): Whether to skip calling event handler for each item in initlist. Raises: ComponentError: If the parent is not a valid InnerComponent. """ from duck.html.components import ( Component, InnerComponent, ComponentError, ) if not isinstance(parent, Component): raise ComponentError( f"Parent should be an instance of HtmlComponent, not {type(parent)}." ) if not isinstance(parent, InnerComponent): raise ComponentError( "Parent should be an `InnerHtmlComponent` instance. " "The provided component does not support children." ) self.parent = parent # Super initialization super().__init__( initlist=initlist, on_new_item=self.on_new_child, on_delete_item=self.on_delete_child, skip_initlist_events=skip_initlist_events, )
[docs] def on_new_child(self, child: "Component", component_loaded_check: bool = True): """ Attach a new child to this component, ensuring correct parent/root. """ from duck.html.components import ( Component, InnerComponent, ComponentError, ) if not isinstance(child, Component): raise ComponentError( f"Child {child} must be an instance of HtmlComponent, not {type(child)}." ) if child.parent: raise ComponentError( f"Child component {child} is already added to a parent {child.parent}. " "Please remove it first before adding it to a new parent." ) # Check if component is loaded if not component_loaded_check: self.parent.raise_if_not_loaded( f"Component {self.parent} is not loaded. " f"This might mean that this is a lazy component." ) # Reset UID so root can assign it later child.uid = None # Set the current component with this children list current = self.parent # Set parent and compute new root child.parent = current new_root = current.root if current.root else current # Update root for child + fix subtree if stale child.root is not new_root if child.root is not new_root: child.root = new_root if isinstance(child, InnerComponent) and child.children: self._update_root_iterative(child, new_root) # Call on_parent event. child.on_parent(current) # Call on_mutation on_mutation( child, Mutation( target=child, code=MutationCode.INSERT_CHILD, payload={"parent": child.parent}, ), )
[docs] def _update_root_iterative(self, node: "Component", new_root: "Component"): """ Iteratively update roots of a subtree. Uses a stack (DFS) to avoid recursion overhead. """ from duck.html.components import InnerComponent stack = [node] while stack: current = stack.pop() for subchild in getattr(current, "children", []): if subchild.root is not new_root: subchild.root = new_root if isinstance(subchild, InnerComponent) and subchild.children: stack.append(subchild)
[docs] def on_delete_child(self, child: "Component"): """ Handler called when a child is removed or replaced from the list. Clears the parent and root references of the child component. Args: child (Component): The child component being removed. Raises: ComponentError: If the child is not a valid component or not in the list. """ from duck.html.components import ( Component, ComponentError, ) if not isinstance(child, Component): raise ComponentError( f"Child component {child} must be an instance of HtmlComponent, not {type(child)}." ) if child not in self: raise ComponentError( f"Child component {repr(child)} not found in children list." ) # Call on_mutation whilst child still has a root on_mutation( child, Mutation( target=child, code=MutationCode.DELETE_CHILD, payload={"parent": child.parent}, ), ) # Detach the child from its parent and root context. child.parent = None child.root = None child.uid = None # Unset child UID