TermUI.Container behaviour (TermUI v0.2.0)
View SourceBehaviour for container components that manage children.
Container extends StatefulComponent with child management capabilities. Use this for components that contain and organize other components, like panels, forms, tabs, or split views.
Basic Usage
defmodule MyApp.Panel do
use TermUI.Container
@impl true
def init(props) do
{:ok, %{title: props[:title] || "Panel"}}
end
@impl true
def children(_state) do
[
{MyApp.Label, %{text: "Header"}, :header},
{MyApp.Content, %{}, :content}
]
end
@impl true
def layout(children, state, area) do
# Arrange children within available area
header_area = %{area | height: 1}
content_area = %{area | y: area.y + 1, height: area.height - 1}
[
{Enum.at(children, 0), header_area},
{Enum.at(children, 1), content_area}
]
end
@impl true
def render(state, _area) do
# Container render is called after children
# Return empty if children handle all rendering
empty()
end
@impl true
def handle_event(_event, state) do
{:ok, state}
end
endChild Specifications
Children are specified as tuples:
{Module, props}- Child with auto-generated ID{Module, props, id}- Child with explicit ID
IDs are used for event routing and child lookup.
Layout
The layout/3 callback positions children within the container's area.
It receives the list of child specs and must return tuples of
{child_spec, area} assigning each child its rendering bounds.
Event Routing
Containers can route events to specific children or handle them directly.
Override route_event/2 to customize event routing.
Summary
Types
Child with assigned area
Child specification
Command for side effects
Event from user input
Available rendering area
Render tree output
Event routing target
Component state
Callbacks
Returns the list of child components.
Called when a child emits a message.
Handles input events.
Initializes container state from props.
Lays out children within the available area.
Renders the container.
Routes an event to the appropriate handler.
Types
@type child_layout() :: {child_spec(), rect()}
Child with assigned area
Child specification
@type command() :: term()
Command for side effects
@type event() :: term()
Event from user input
Available rendering area
@type render_tree() :: TermUI.Component.RenderNode.t() | [render_tree()] | String.t()
Render tree output
@type route_target() :: :self | {:child, id :: term()} | :broadcast
Event routing target
@type state() :: term()
Component state
Callbacks
@callback children(state()) :: [child_spec()]
Returns the list of child components.
Called to determine which children the container should manage. Children are specified as tuples with module, props, and optional ID.
Parameters
state- Current container state
Returns
List of child specifications.
Examples
@impl true
def children(state) do
[
{Label, %{text: state.title}, :title},
{Button, %{label: "OK"}, :ok_button},
{Button, %{label: "Cancel"}, :cancel_button}
]
end
@callback handle_child_message(child_id :: term(), message :: term(), state()) :: {:ok, state()} | {:ok, state(), [command()]}
Called when a child emits a message.
Use to handle messages bubbling up from child components.
Parameters
child_id- ID of the child that sent the messagemessage- The message from the childstate- Current container state
@callback handle_event(event(), state()) :: {:ok, state()} | {:ok, state(), [command()]} | {:stop, term(), state()}
Handles input events.
Same as StatefulComponent.handle_event/2.
Initializes container state from props.
Same as StatefulComponent.init/1.
@callback layout([child_spec()], state(), rect()) :: [child_layout()]
Lays out children within the available area.
Determines the position and size of each child component. The default implementation stacks children vertically.
Parameters
children- List of child specifications fromchildren/1state- Current container statearea- Available area for the container
Returns
List of {child_spec, area} tuples.
Examples
@impl true
def layout(children, _state, area) do
# Horizontal layout with equal widths
child_width = div(area.width, length(children))
children
|> Enum.with_index()
|> Enum.map(fn {child, i} ->
child_area = %{
x: area.x + i * child_width,
y: area.y,
width: child_width,
height: area.height
}
{child, child_area}
end)
end
@callback render(state(), rect()) :: render_tree()
Renders the container.
Called after children are rendered. Can render container chrome (borders, titles) or return empty if children handle everything.
Same signature as StatefulComponent.render/2.
@callback route_event(event(), state()) :: route_target()
Routes an event to the appropriate handler.
Override to customize how events are distributed to children. Default routes all events to self.
Parameters
event- The input eventstate- Current container state
Returns
:self- Handle event in this container{:child, id}- Route to specific child:broadcast- Send to all children