Source code for sidekick.row

"""Provides the Row class for horizontally arranging components in Sidekick.

Use the `sidekick.Row` class to create a container that lays out its child
components horizontally, one after the other, from left to right.

Components are added to a `Row` in several ways:

1.  By passing the `Row` instance as the `parent` argument when creating the
    child component:
    `my_row = sidekick.Row(instance_id="button-bar")`
    `button_in_row = sidekick.Button("Hi", parent=my_row)`
2.  By calling the `row.add_child(component)` method after both the `Row` and
    the child component have been created:
    `my_button = sidekick.Button("Click")`
    `my_row = sidekick.Row()`
    `my_row.add_child(my_button)`
3.  By passing existing components directly to the `Row` constructor:
    `button1 = sidekick.Button("One")`
    `label1 = sidekick.Label("Info")`
    `my_row = sidekick.Row(button1, label1)`

Rows themselves can be nested within other containers (like `Column` or another `Row`).
You can also provide an `instance_id` to uniquely identify the Row.
"""

from . import logger
from .component import Component
from .events import ErrorEvent
from typing import Optional, Dict, Any, Union, Callable, Tuple, Coroutine

[docs] class Row(Component): """Represents a Row layout container instance in the Sidekick UI. Child components added to this container will be arranged horizontally, from left to right. Attributes: instance_id (str): The unique identifier for this row instance. """
[docs] def __init__( self, *children: Component, instance_id: Optional[str] = None, parent: Optional[Union['Component', str]] = None, on_error: Optional[Callable[[ErrorEvent], Union[None, Coroutine[Any, Any, None]]]] = None, ): """Initializes the Row layout container and creates the UI element. This function is called when you create a new Row, for example: `my_row = sidekick.Row()` or with initial children: `button = sidekick.Button("OK")` `my_row = sidekick.Row(button, parent=another_container, instance_id="action-row")` It sends a message to the Sidekick UI to display a new horizontal layout container. Args: *children (Component): Zero or more Sidekick component instances (e.g., `Button`, `Label`, another `Row`) to be immediately added as children to this row. These child components must already exist. When `Row` is created, it will attempt to move each of these children into itself. instance_id (Optional[str]): An optional, user-defined unique identifier for this Row. If `None`, an ID will be auto-generated. Must be unique if provided. parent (Optional[Union['Component', str]]): The parent container (e.g., a `sidekick.Column`) where this Row itself should be placed. If `None` (the default), the Row is added to the main Sidekick panel area. on_error (Optional[Callable[[ErrorEvent], Union[None, Coroutine[Any, Any, None]]]]): A function to call if an error message related to this specific Row container (not its children) is sent back from the Sidekick UI. The function should accept one `ErrorEvent` object as an argument. The callback can be a regular function or a coroutine function (async def). Defaults to `None`. Raises: ValueError: If the provided `instance_id` is invalid or a duplicate, or if any of `children` are not valid Sidekick components. TypeError: If `parent` is an invalid type, or if `on_error` is provided but is not a callable function. """ # Row spawn payload is currently empty according to the protocol. # Layout properties (like alignment or gap) might be added here in the future. spawn_payload: Dict[str, Any] = {} super().__init__( component_type="row", payload=spawn_payload, instance_id=instance_id, parent=parent, on_error=on_error ) logger.info(f"Row layout container '{self.instance_id}' initialized.") # Use self.instance_id # Add any children provided directly in the constructor. if children: logger.debug(f"Row '{self.instance_id}': Adding {len(children)} children from constructor.") # Use self.instance_id for child_idx, child in enumerate(children): if isinstance(child, Component): try: # The add_child method handles sending the "changeParent" update # for the child component. self.add_child(child) except Exception as e_add: # Log the error but continue processing other children. # This prevents one bad child from stopping the whole row init. child_id_str = getattr(child, 'instance_id', f"child at index {child_idx}") # Use child's instance_id logger.error( f"Row '{self.instance_id}': Error adding child '{child_id_str}' " # Use self.instance_id f"from constructor. Error: {e_add}" ) # Depending on strictness, you might choose to re-raise here. else: # Log a TypeError if a non-component was passed. logger.error( f"Row '{self.instance_id}': Invalid child type passed to constructor " # Use self.instance_id f"at index {child_idx}: {type(child).__name__}. Expected a Sidekick component." ) # Raise a TypeError to stop if an invalid child type is provided. raise TypeError( f"Invalid child type in Row constructor: Expected a Sidekick component " f"(e.g., Button, Label), but got {type(child).__name__}." )
[docs] def add_child(self, child_component: Component): """Moves an existing component into this Row container. This method sends a command to the Sidekick UI instructing it to make the provided `child_component` a child of this `Row`. The child component will be visually placed inside the row layout, typically appended to the end of existing children (to the right of the last child). Note: The `child_component` must already exist (i.e., its `__init__` must have been called). This method changes the parentage of an existing component, effectively moving it. Args: child_component (Component): The Sidekick component instance (e.g., a `Button`, `Textbox`, `Canvas`, another `Row`) to add as a child to this row. Raises: TypeError: If `child_component` is not a valid Sidekick component instance (i.e., not derived from `Component`). SidekickConnectionError: If sending the update command to the UI fails. Example: >>> row_container = sidekick.Row(instance_id="my-toolbar") >>> my_button = sidekick.Button("Click Me") # Initially in root >>> my_label = sidekick.Label("Info") # Initially in root >>> >>> # Move the button and label into the row >>> row_container.add_child(my_button) >>> row_container.add_child(my_label) >>> # Now my_button and my_label appear horizontally inside row_container """ if not isinstance(child_component, Component): raise TypeError( f"Invalid child type for Row.add_child(): Expected a Sidekick component " f"instance (e.g., Button, Label), but got {type(child_component).__name__}." ) # The child component is responsible for sending the 'update' command about itself, # specifying the 'changeParent' action and targeting this Row instance # (using its instance_id) as the new parent. logger.debug( f"Row '{self.instance_id}': Requesting to move child component " # Use self.instance_id f"'{child_component.instance_id}' (type: {child_component.component_type}) " # Use child's instance_id f"into this row." ) try: # The payload structure for 'changeParent' is defined by the protocol. # It needs the 'parent' ID (which is self.instance_id for this Row) # and optionally 'insertBefore' for ordering. # For simple append, 'insertBefore' can be omitted. child_component._send_update({ "action": "changeParent", "options": { "parent": self.instance_id # This Row's instance_id # 'insertBefore': null or omitted appends to the end by default. } }) except Exception as e: logger.error( f"Row '{self.instance_id}': Failed to send 'changeParent' update for " # Use self.instance_id f"child '{child_component.instance_id}'. Error: {e}" # Use child's instance_id ) # Re-raise the original exception (e.g., SidekickConnectionError) # so the caller is aware of the failure. raise e
# Row, as a layout container, doesn't typically have its own specific events # triggered by user interaction directly on itself from the UI (like a button click). # Its primary role is to contain other components. # The base class's _internal_message_handler is sufficient for handling # potential 'error' messages related to the Row component itself. # No need to override _internal_message_handler for specific "event" types. def _reset_specific_callbacks(self): """Internal: Resets row-specific callbacks (none currently). Called by `Component.remove()`. Row currently has no specific user-settable callbacks beyond `on_error` (handled by base). """ super()._reset_specific_callbacks() # No specific callbacks unique to Row to reset at this time. logger.debug(f"Row '{self.instance_id}': No specific callbacks to reset.") # Use self.instance_id