Skip to content

xnano.components

xnano.components

xnano.components


Components for text, input, data display, navigation, and feedback.

Modules:

  • bar

    xnano.components.bar

  • button

    xnano.components.button

  • chart

    xnano.components.chart

  • component

    xnano.components.component

  • dropdown

    xnano.components.dropdown

  • image

    xnano.components.image

  • input

    xnano.components.input

  • link

    xnano.components.link

  • loader

    xnano.components.loader

  • markdown

    xnano.components.markdown

  • options

    xnano.components.options

  • schema

    xnano.components.schema

  • scrollbar

    xnano.components.scrollbar

  • table

    xnano.components.table

  • text

    xnano.components.text

Classes:

  • Bar

    Compact inline bar chart for a sequence of samples.

  • Button

    Focusable styled text button.

  • Chart

    Declarative multi-series chart.

  • Series

    Describe one chart series.

  • Bars

    Render grouped bars.

  • Canvas

    Render geometric shapes in numeric coordinate space.

  • CellCanvas

    A rectangular sequence of styled cell rows.

  • Clear

    Clear the assigned render area.

  • Component

    Base class for custom xnano components.

  • ComponentRenderContext

    Render-time scope passed into component paint hooks.

  • Gauge

    Render a filled progress gauge.

  • Items

    Render a selectable list of text items.

  • LineGauge

    Render progress as a single horizontal line.

  • Panel

    Decorate child content with a frame and padding.

  • Plot

    Render one or more numeric data series.

  • Run

    One styled text run.

  • ScrollbarContent

    Render scroll position and viewport proportion.

  • Sparkline

    Render a compact sequence of vertical samples.

  • Stack

    Lay out child content in one direction.

  • TableGrid

    Render tabular rows with optional selection.

  • TextBlock

    Wrapped plain text or lines of styled runs.

  • Dropdown

    Collapsible choice list.

  • Image

    A native-resolution terminal image or real-time GIF component.

  • ImageData

    Decoded image frames independent of Pillow and the render engine.

  • ImageFrame

    One native-resolution RGB frame and its display duration.

  • Input

    Editable text field.

  • Link

    Focusable hyperlink label.

  • Loader

    Determinate progress indicator or indeterminate spinner.

  • Markdown

    Display Markdown with terminal-friendly styling.

  • Option

    A labeled choice with optional distinct value and disabled flag.

  • Options

    Always-visible, selectable choice list.

  • Scrollbar

    Display the visible range of scrollable content.

  • Column

    Describe one table column.

  • Table

    Declarative data table.

  • Text

    Display styled, marked-up, highlighted, or editable text.

Attributes:

BarDirection module-attribute

BarDirection: TypeAlias = Literal['up', 'down']

Whether bar samples grow upward or downward.

BarGlyphPreset module-attribute

BarGlyphPreset: TypeAlias = Literal[
    "blocks", "braille", "ascii"
]

Named glyph ladders for compact bar rendering.

BarGlyphs module-attribute

Explicit ordered symbols or a named preset.

Content module-attribute

Any content primitive.

ImageFit module-attribute

ImageFit: TypeAlias = Literal[
    "crop", "contain", "cover", "stretch", "smart"
]

How source pixels are placed inside the available terminal area.

ImageSource module-attribute

ImageSource: TypeAlias = (
    str | os.PathLike[str] | bytes | BinaryIO | ImageData
)

A filesystem path, encoded image bytes, stream, or decoded image data.

LoaderStyle module-attribute

LoaderStyle: TypeAlias = Literal['spinner', 'bar', 'line']

Visual style for a Loader component.

Bar dataclass

Bar(
    data: Sequence[int | float] = tuple(),
    foreground: "ColorLike | None" = None,
    colors: Sequence["ColorLike"] | None = None,
    background: "ColorLike | None" = None,
    max_value: int | float | None = None,
    direction: BarDirection = "up",
    glyphs: BarGlyphs = "blocks",
    absent_color: "ColorLike | None" = None,
    absent_glyph: str | None = None,
    *,
    visible: bool = True,
    z: int = 0,
    fit_content: bool = False
)

Bases: Component

Compact inline bar chart for a sequence of samples.

Fills its layout slot by default and scales sample heights across the available vertical area when using the native sparkline path. Supply colors with one entry per sample to tint individual bars.

Example

Bar(data=(2, 5, 3, 8), color="cyan")

Attributes:

Methods:

  • get_frame

    Optional frame/panel chrome around composed content.

  • get_size

    Return the preferred cell size of this component.

  • before_render

    Called before rendering; returns the effective render area.

  • after_render

    Called after rendering for optional post-paint work.

  • compose_extra_small

    Compose content when the viewport is extra small (< 40 cols).

  • compose_small

    Compose content when the viewport is small (40–79 cols).

  • compose_medium

    Compose content when the viewport is medium (80–119 cols).

  • compose_large

    Compose content when the viewport is large (120–159 cols).

  • compose_extra_large

    Compose content when the viewport is extra large (>= 160 cols).

  • handle_keyboard

    Optional keyboard handler while focused.

  • handle_paste

    Optional paste handler while focused.

  • component_post_init

    Resolve and validate the glyph ladder once.

  • compose

    Compose sparkline content, with a native/canvas paint path.

visible class-attribute instance-attribute

visible: bool = dataclasses.field(
    default=True, kw_only=True
)

Whether this component paints at all.

z class-attribute instance-attribute

z: int = dataclasses.field(default=0, kw_only=True)

Stacking order among sibling content.

focused property

focused: bool

Whether this component currently holds field focus.

data class-attribute instance-attribute

data: Sequence[int | float] = dataclasses.field(
    default_factory=tuple
)

Sequence of sample values.

foreground class-attribute instance-attribute

foreground: 'ColorLike | None' = None

Default bar foreground color (deprecated alias: color).

colors class-attribute instance-attribute

colors: Sequence['ColorLike'] | None = None

Optional per-bar foreground colors, one per data entry.

background class-attribute instance-attribute

background: 'ColorLike | None' = None

Widget background color.

max_value class-attribute instance-attribute

max_value: int | float | None = None

Explicit y-axis ceiling; None auto-scales to the dataset max.

direction class-attribute instance-attribute

direction: BarDirection = 'up'

Whether samples grow "up" or "down".

glyphs class-attribute instance-attribute

glyphs: BarGlyphs = 'blocks'

Ordered fill ladder or a named preset (blocks/braille/ascii).

absent_color class-attribute instance-attribute

absent_color: 'ColorLike | None' = None

Color applied to zero or absent samples.

absent_glyph class-attribute instance-attribute

absent_glyph: str | None = None

Glyph drawn for absent samples.

fit_content class-attribute instance-attribute

fit_content: bool = dataclasses.field(
    default=False, kw_only=True
)

Whether layout should use the sparkline's natural width.

resolved_glyphs property

resolved_glyphs: tuple[str, ...]

Resolved ordered glyph ladder used for rendering.

get_frame

get_frame() -> Any | None

Optional frame/panel chrome around composed content.

Source code in xnano/components/component.py
def get_frame(self) -> Any | None:
    """Optional frame/panel chrome around composed content."""
    return None

get_size

get_size(ctx: ComponentRenderContext[StateT]) -> Size

Return the preferred cell size of this component.

Source code in xnano/components/component.py
def get_size(self, ctx: ComponentRenderContext[StateT]) -> Size:
    """Return the preferred cell size of this component."""
    return Size(width=0, height=0)

before_render

before_render(
    ctx: ComponentRenderContext[StateT], area: "Area"
) -> "Area"

Called before rendering; returns the effective render area.

Source code in xnano/components/component.py
def before_render(
    self,
    ctx: ComponentRenderContext[StateT],
    area: "Area",
) -> "Area":
    """Called before rendering; returns the effective render area."""
    return area

after_render

after_render(
    ctx: ComponentRenderContext[StateT], area: "Area"
) -> None

Called after rendering for optional post-paint work.

Source code in xnano/components/component.py
def after_render(
    self,
    ctx: ComponentRenderContext[StateT],
    area: "Area",
) -> None:
    """Called after rendering for optional post-paint work."""
    return None

compose_extra_small

compose_extra_small(
    ctx: ComponentRenderContext[StateT],
) -> Content | None

Compose content when the viewport is extra small (< 40 cols).

Optional responsive counterpart to :meth:compose. When overridden, it is used instead of compose while the window is in this size tier. Overriding any compose_* variant opts the component into breakpoint dispatch; a component that overrides none pays no per-frame cost.

Source code in xnano/components/component.py
@responsive_noop
def compose_extra_small(
    self, ctx: ComponentRenderContext[StateT]
) -> Content | None:
    """Compose content when the viewport is extra small (< 40 cols).

    Optional responsive counterpart to :meth:`compose`. When
    overridden, it is used *instead of* ``compose`` while the window
    is in this size tier. Overriding any ``compose_*`` variant opts the
    component into breakpoint dispatch; a component that overrides none
    pays no per-frame cost.
    """
    return None

compose_small

compose_small(
    ctx: ComponentRenderContext[StateT],
) -> Content | None

Compose content when the viewport is small (40–79 cols).

See :meth:compose_extra_small.

Source code in xnano/components/component.py
@responsive_noop
def compose_small(
    self, ctx: ComponentRenderContext[StateT]
) -> Content | None:
    """Compose content when the viewport is small (40–79 cols).

    See :meth:`compose_extra_small`.
    """
    return None

compose_medium

compose_medium(
    ctx: ComponentRenderContext[StateT],
) -> Content | None

Compose content when the viewport is medium (80–119 cols).

See :meth:compose_extra_small.

Source code in xnano/components/component.py
@responsive_noop
def compose_medium(
    self, ctx: ComponentRenderContext[StateT]
) -> Content | None:
    """Compose content when the viewport is medium (80–119 cols).

    See :meth:`compose_extra_small`.
    """
    return None

compose_large

compose_large(
    ctx: ComponentRenderContext[StateT],
) -> Content | None

Compose content when the viewport is large (120–159 cols).

See :meth:compose_extra_small.

Source code in xnano/components/component.py
@responsive_noop
def compose_large(
    self, ctx: ComponentRenderContext[StateT]
) -> Content | None:
    """Compose content when the viewport is large (120–159 cols).

    See :meth:`compose_extra_small`.
    """
    return None

compose_extra_large

compose_extra_large(
    ctx: ComponentRenderContext[StateT],
) -> Content | None

Compose content when the viewport is extra large (>= 160 cols).

See :meth:compose_extra_small.

Source code in xnano/components/component.py
@responsive_noop
def compose_extra_large(
    self, ctx: ComponentRenderContext[StateT]
) -> Content | None:
    """Compose content when the viewport is extra large (>= 160 cols).

    See :meth:`compose_extra_small`.
    """
    return None

handle_keyboard

handle_keyboard(keyboard: 'KeyboardEventData') -> bool

Optional keyboard handler while focused.

Returns:

  • bool

    True when the event was consumed.

Source code in xnano/components/component.py
def handle_keyboard(self, keyboard: "KeyboardEventData") -> bool:
    """Optional keyboard handler while focused.

    Returns:
        ``True`` when the event was consumed.
    """
    return False

handle_paste

handle_paste(text: str) -> bool

Optional paste handler while focused.

Returns:

  • bool

    True when the paste was consumed.

Source code in xnano/components/component.py
def handle_paste(self, text: str) -> bool:
    """Optional paste handler while focused.

    Returns:
        ``True`` when the paste was consumed.
    """
    return False

component_post_init

component_post_init() -> None

Resolve and validate the glyph ladder once.

Source code in xnano/components/bar.py
def component_post_init(self) -> None:
    """Resolve and validate the glyph ladder once."""
    self._resolved_glyphs = resolve_bar_glyphs(self.glyphs)
    if self.direction not in ("up", "down"):
        raise ValueError(f"Unsupported bar direction: {self.direction!r}")
    if self.absent_glyph is not None and not _is_single_cell_glyph(
        self.absent_glyph
    ):
        raise ValueError(
            "absent_glyph must occupy a single terminal cell."
        )
    if self.colors is not None and len(self.colors) != len(self.data):
        raise ValueError("colors must contain one entry per data sample.")

compose

compose(ctx: 'ComponentRenderContext')

Compose sparkline content, with a native/canvas paint path.

Returns:

  • Interface-neutral content for this bar.

Source code in xnano/components/bar.py
def compose(self, ctx: "ComponentRenderContext"):
    """Compose sparkline content, with a native/canvas paint path.

    Returns:
        Interface-neutral content for this bar.
    """
    if not self._uses_native_sparkline():
        return self._compose_glyph_canvas(ctx)

    return self._compose_sparkline_content()

Button dataclass

Button(
    label: str | Any = "",
    disabled: bool = False,
    focusable: bool = True,
    foreground: ColorLike | None = None,
    background: ColorLike | None = None,
    focused_color: ColorLike | None = "black",
    focused_background: ColorLike | None = "white",
    disabled_color: ColorLike | None = "gray",
    disabled_background: ColorLike | None = None,
    left: str = "[ ",
    right: str = " ]",
    activation_keys: Sequence[str] = ("enter", "space"),
    *,
    visible: bool = True,
    z: int = 0,
    fit_content: bool = True
)

Bases: Component

Focusable styled text button.

Buttons use the same hooks as every other field. Assign a group and handle clicks or activation keys on the grid:

class Form(BaseGrid):
    submit: Button = Field(
        default=Button("Submit"),
        group="submit",
    )

    @on_click(group="submit")
@on_keyboard("enter", group="submit")
def submit_form(self, ctx: Context) -> None: ...
Example

Button(label="Save", focused_background="blue")

Attributes:

Methods:

  • component_post_init

    Initialize subclass state after dataclass fields are assigned.

  • get_frame

    Optional frame/panel chrome around composed content.

  • get_size

    Return the preferred cell size of this component.

  • before_render

    Called before rendering; returns the effective render area.

  • after_render

    Called after rendering for optional post-paint work.

  • compose_extra_small

    Compose content when the viewport is extra small (< 40 cols).

  • compose_small

    Compose content when the viewport is small (40–79 cols).

  • compose_medium

    Compose content when the viewport is medium (80–119 cols).

  • compose_large

    Compose content when the viewport is large (120–159 cols).

  • compose_extra_large

    Compose content when the viewport is extra large (>= 160 cols).

  • handle_paste

    Optional paste handler while focused.

  • get_label_text

    Return the plain-text form of label.

  • handle_keyboard

    Leave keyboard activation to the grid's event hooks.

  • compose

    Compose a styled label, wrapped in a panel when focused.

visible class-attribute instance-attribute

visible: bool = dataclasses.field(
    default=True, kw_only=True
)

Whether this component paints at all.

z class-attribute instance-attribute

z: int = dataclasses.field(default=0, kw_only=True)

Stacking order among sibling content.

fit_content class-attribute instance-attribute

fit_content: bool = dataclasses.field(
    default=True, kw_only=True
)

When True, paint at natural size inside a larger slot.

focused property

focused: bool

Whether this component currently holds field focus.

label class-attribute instance-attribute

label: str | Any = ''

Button caption (plain string or styled Text).

disabled class-attribute instance-attribute

disabled: bool = False

When True, paints disabled colors and ignores activation.

focusable class-attribute instance-attribute

focusable: bool = True

Whether this button participates in field focus (tab order).

foreground class-attribute instance-attribute

foreground: ColorLike | None = None

Idle foreground color (deprecated alias: color).

background class-attribute instance-attribute

background: ColorLike | None = None

Idle background color.

focused_color class-attribute instance-attribute

focused_color: ColorLike | None = 'black'

Foreground while focused.

focused_background class-attribute instance-attribute

focused_background: ColorLike | None = 'white'

Background while focused.

disabled_color class-attribute instance-attribute

disabled_color: ColorLike | None = 'gray'

Foreground while disabled.

disabled_background class-attribute instance-attribute

disabled_background: ColorLike | None = None

Background while disabled.

left class-attribute instance-attribute

left: str = '[ '

Prefix chrome around the label.

right class-attribute instance-attribute

right: str = ' ]'

Suffix chrome around the label.

activation_keys class-attribute instance-attribute

activation_keys: Sequence[str] = ('enter', 'space')

Bindings that bubble to hooks (never consumed here).

component_post_init

component_post_init() -> None

Initialize subclass state after dataclass fields are assigned.

Override this method instead of __post_init__.

Source code in xnano/components/component.py
def component_post_init(self) -> None:
    """Initialize subclass state after dataclass fields are assigned.

    Override this method instead of ``__post_init__``.
    """
    return None

get_frame

get_frame() -> Any | None

Optional frame/panel chrome around composed content.

Source code in xnano/components/component.py
def get_frame(self) -> Any | None:
    """Optional frame/panel chrome around composed content."""
    return None

get_size

get_size(ctx: ComponentRenderContext[StateT]) -> Size

Return the preferred cell size of this component.

Source code in xnano/components/component.py
def get_size(self, ctx: ComponentRenderContext[StateT]) -> Size:
    """Return the preferred cell size of this component."""
    return Size(width=0, height=0)

before_render

before_render(
    ctx: ComponentRenderContext[StateT], area: "Area"
) -> "Area"

Called before rendering; returns the effective render area.

Source code in xnano/components/component.py
def before_render(
    self,
    ctx: ComponentRenderContext[StateT],
    area: "Area",
) -> "Area":
    """Called before rendering; returns the effective render area."""
    return area

after_render

after_render(
    ctx: ComponentRenderContext[StateT], area: "Area"
) -> None

Called after rendering for optional post-paint work.

Source code in xnano/components/component.py
def after_render(
    self,
    ctx: ComponentRenderContext[StateT],
    area: "Area",
) -> None:
    """Called after rendering for optional post-paint work."""
    return None

compose_extra_small

compose_extra_small(
    ctx: ComponentRenderContext[StateT],
) -> Content | None

Compose content when the viewport is extra small (< 40 cols).

Optional responsive counterpart to :meth:compose. When overridden, it is used instead of compose while the window is in this size tier. Overriding any compose_* variant opts the component into breakpoint dispatch; a component that overrides none pays no per-frame cost.

Source code in xnano/components/component.py
@responsive_noop
def compose_extra_small(
    self, ctx: ComponentRenderContext[StateT]
) -> Content | None:
    """Compose content when the viewport is extra small (< 40 cols).

    Optional responsive counterpart to :meth:`compose`. When
    overridden, it is used *instead of* ``compose`` while the window
    is in this size tier. Overriding any ``compose_*`` variant opts the
    component into breakpoint dispatch; a component that overrides none
    pays no per-frame cost.
    """
    return None

compose_small

compose_small(
    ctx: ComponentRenderContext[StateT],
) -> Content | None

Compose content when the viewport is small (40–79 cols).

See :meth:compose_extra_small.

Source code in xnano/components/component.py
@responsive_noop
def compose_small(
    self, ctx: ComponentRenderContext[StateT]
) -> Content | None:
    """Compose content when the viewport is small (40–79 cols).

    See :meth:`compose_extra_small`.
    """
    return None

compose_medium

compose_medium(
    ctx: ComponentRenderContext[StateT],
) -> Content | None

Compose content when the viewport is medium (80–119 cols).

See :meth:compose_extra_small.

Source code in xnano/components/component.py
@responsive_noop
def compose_medium(
    self, ctx: ComponentRenderContext[StateT]
) -> Content | None:
    """Compose content when the viewport is medium (80–119 cols).

    See :meth:`compose_extra_small`.
    """
    return None

compose_large

compose_large(
    ctx: ComponentRenderContext[StateT],
) -> Content | None

Compose content when the viewport is large (120–159 cols).

See :meth:compose_extra_small.

Source code in xnano/components/component.py
@responsive_noop
def compose_large(
    self, ctx: ComponentRenderContext[StateT]
) -> Content | None:
    """Compose content when the viewport is large (120–159 cols).

    See :meth:`compose_extra_small`.
    """
    return None

compose_extra_large

compose_extra_large(
    ctx: ComponentRenderContext[StateT],
) -> Content | None

Compose content when the viewport is extra large (>= 160 cols).

See :meth:compose_extra_small.

Source code in xnano/components/component.py
@responsive_noop
def compose_extra_large(
    self, ctx: ComponentRenderContext[StateT]
) -> Content | None:
    """Compose content when the viewport is extra large (>= 160 cols).

    See :meth:`compose_extra_small`.
    """
    return None

handle_paste

handle_paste(text: str) -> bool

Optional paste handler while focused.

Returns:

  • bool

    True when the paste was consumed.

Source code in xnano/components/component.py
def handle_paste(self, text: str) -> bool:
    """Optional paste handler while focused.

    Returns:
        ``True`` when the paste was consumed.
    """
    return False

get_label_text

get_label_text() -> str

Return the plain-text form of label.

Source code in xnano/components/button.py
def get_label_text(self) -> str:
    """Return the plain-text form of ``label``."""
    if isinstance(self.label, str):
        return self.label
    value = getattr(self.label, "value", None)
    if isinstance(value, str):
        return value
    content = getattr(self.label, "content", None)
    if isinstance(content, str):
        return content
    return str(self.label)

handle_keyboard

handle_keyboard(keyboard: 'KeyboardEventData') -> bool

Leave keyboard activation to the grid's event hooks.

Parameters:

  • keyboard ('KeyboardEventData') –

    The keyboard event payload.

Returns:

  • bool

    False so the event continues to hooks.

Source code in xnano/components/button.py
def handle_keyboard(self, keyboard: "KeyboardEventData") -> bool:
    """Leave keyboard activation to the grid's event hooks.

    Args:
        keyboard: The keyboard event payload.

    Returns:
        ``False`` so the event continues to hooks.
    """
    del keyboard
    return False

compose

compose(ctx: 'ComponentRenderContext') -> Any

Compose a styled label, wrapped in a panel when focused.

Parameters:

  • ctx ('ComponentRenderContext') –

    Render-time scope for this paint.

Returns:

  • Any

    A TextBlock, or a Panel around it while focused and

  • Any

    not disabled.

Source code in xnano/components/button.py
def compose(self, ctx: "ComponentRenderContext") -> Any:
    """Compose a styled label, wrapped in a panel when focused.

    Args:
        ctx: Render-time scope for this paint.

    Returns:
        A ``TextBlock``, or a ``Panel`` around it while focused and
        not disabled.
    """
    from xnano.core.content import Panel, Run, TextBlock

    del ctx
    foreground, background = self._resolved_colors()
    caption = f"{self.left}{self.get_label_text()}{self.right}"
    block = TextBlock(
        lines=(
            (
                Run(
                    text=caption,
                    foreground=foreground,
                    background=background,
                ),
            ),
        ),
        foreground=foreground,
        background=background,
        z=self.z,
        visible=self.visible,
    )
    if self.focused and not self.disabled:
        return Panel(
            child=block,
            background=background,
            border_color=foreground,
            z=self.z,
            visible=self.visible,
        )
    return block

Chart dataclass

Chart(
    series: dict[str, SeriesData] = dict(),
    kind: GraphTypeLike = "line",
    colors: Sequence["ColorLike"] | None = None,
    x_bounds: tuple[float, float] | None = None,
    y_bounds: tuple[float, float] | None = None,
    x_label: str | None = None,
    y_label: str | None = None,
    x_labels: Sequence[str] | None = None,
    y_labels: Sequence[str] | None = None,
    legend: bool = True,
    legend_position: LegendPositionLike = "top_right",
    marker: CanvasMarkerLike | None = None,
    hidden_series: Sequence[str] = (),
    *,
    visible: bool = True,
    z: int = 0,
    fit_content: bool = False
)

Bases: Component

Declarative multi-series chart.

Hand it a mapping of series name → points and it derives the datasets, legend, axis bounds, and a cycled color per series. Points may be (x, y) pairs or bare y values (the x-axis becomes the index).

Example

Chart(series={"requests": (3, 5, 4, 8)})

Attributes:

Methods:

  • component_post_init

    Initialize subclass state after dataclass fields are assigned.

  • get_frame

    Optional frame/panel chrome around composed content.

  • get_size

    Return the preferred cell size of this component.

  • before_render

    Called before rendering; returns the effective render area.

  • after_render

    Called after rendering for optional post-paint work.

  • compose_extra_small

    Compose content when the viewport is extra small (< 40 cols).

  • compose_small

    Compose content when the viewport is small (40–79 cols).

  • compose_medium

    Compose content when the viewport is medium (80–119 cols).

  • compose_large

    Compose content when the viewport is large (120–159 cols).

  • compose_extra_large

    Compose content when the viewport is extra large (>= 160 cols).

  • handle_keyboard

    Optional keyboard handler while focused.

  • handle_paste

    Optional paste handler while focused.

  • compose

    Compose Plot content with a native ChartNode paint fallback.

visible class-attribute instance-attribute

visible: bool = dataclasses.field(
    default=True, kw_only=True
)

Whether this component paints at all.

z class-attribute instance-attribute

z: int = dataclasses.field(default=0, kw_only=True)

Stacking order among sibling content.

focused property

focused: bool

Whether this component currently holds field focus.

series class-attribute instance-attribute

series: dict[str, SeriesData] = dataclasses.field(
    default_factory=dict
)

Mapping of series name → points.

kind class-attribute instance-attribute

kind: GraphTypeLike = 'line'

Default plot kind for series that do not override it.

colors class-attribute instance-attribute

colors: Sequence['ColorLike'] | None = None

Palette cycled across series; None uses a built-in palette.

x_bounds class-attribute instance-attribute

x_bounds: tuple[float, float] | None = None

x-axis (min, max); None auto-fits to the data.

y_bounds class-attribute instance-attribute

y_bounds: tuple[float, float] | None = None

y-axis (min, max); None auto-fits to the data.

x_label class-attribute instance-attribute

x_label: str | None = None

Optional x-axis title.

y_label class-attribute instance-attribute

y_label: str | None = None

Optional y-axis title.

x_labels class-attribute instance-attribute

x_labels: Sequence[str] | None = None

Optional x-axis tick labels.

y_labels class-attribute instance-attribute

y_labels: Sequence[str] | None = None

Optional y-axis tick labels.

legend class-attribute instance-attribute

legend: bool = True

Whether to show the legend (labelled from series names).

legend_position class-attribute instance-attribute

legend_position: LegendPositionLike = 'top_right'

Legend placement when legend is enabled.

marker class-attribute instance-attribute

marker: CanvasMarkerLike | None = None

Default marker glyph set for series that do not override it.

hidden_series class-attribute instance-attribute

hidden_series: Sequence[str] = ()

Series names omitted from paint and bounds.

fit_content class-attribute instance-attribute

fit_content: bool = dataclasses.field(
    default=False, kw_only=True
)

Whether layout should use the plot's natural size.

datasets property

datasets: tuple[ResolvedDataset, ...]

Read-only resolved view of plotted datasets.

component_post_init

component_post_init() -> None

Initialize subclass state after dataclass fields are assigned.

Override this method instead of __post_init__.

Source code in xnano/components/component.py
def component_post_init(self) -> None:
    """Initialize subclass state after dataclass fields are assigned.

    Override this method instead of ``__post_init__``.
    """
    return None

get_frame

get_frame() -> Any | None

Optional frame/panel chrome around composed content.

Source code in xnano/components/component.py
def get_frame(self) -> Any | None:
    """Optional frame/panel chrome around composed content."""
    return None

get_size

get_size(ctx: ComponentRenderContext[StateT]) -> Size

Return the preferred cell size of this component.

Source code in xnano/components/component.py
def get_size(self, ctx: ComponentRenderContext[StateT]) -> Size:
    """Return the preferred cell size of this component."""
    return Size(width=0, height=0)

before_render

before_render(
    ctx: ComponentRenderContext[StateT], area: "Area"
) -> "Area"

Called before rendering; returns the effective render area.

Source code in xnano/components/component.py
def before_render(
    self,
    ctx: ComponentRenderContext[StateT],
    area: "Area",
) -> "Area":
    """Called before rendering; returns the effective render area."""
    return area

after_render

after_render(
    ctx: ComponentRenderContext[StateT], area: "Area"
) -> None

Called after rendering for optional post-paint work.

Source code in xnano/components/component.py
def after_render(
    self,
    ctx: ComponentRenderContext[StateT],
    area: "Area",
) -> None:
    """Called after rendering for optional post-paint work."""
    return None

compose_extra_small

compose_extra_small(
    ctx: ComponentRenderContext[StateT],
) -> Content | None

Compose content when the viewport is extra small (< 40 cols).

Optional responsive counterpart to :meth:compose. When overridden, it is used instead of compose while the window is in this size tier. Overriding any compose_* variant opts the component into breakpoint dispatch; a component that overrides none pays no per-frame cost.

Source code in xnano/components/component.py
@responsive_noop
def compose_extra_small(
    self, ctx: ComponentRenderContext[StateT]
) -> Content | None:
    """Compose content when the viewport is extra small (< 40 cols).

    Optional responsive counterpart to :meth:`compose`. When
    overridden, it is used *instead of* ``compose`` while the window
    is in this size tier. Overriding any ``compose_*`` variant opts the
    component into breakpoint dispatch; a component that overrides none
    pays no per-frame cost.
    """
    return None

compose_small

compose_small(
    ctx: ComponentRenderContext[StateT],
) -> Content | None

Compose content when the viewport is small (40–79 cols).

See :meth:compose_extra_small.

Source code in xnano/components/component.py
@responsive_noop
def compose_small(
    self, ctx: ComponentRenderContext[StateT]
) -> Content | None:
    """Compose content when the viewport is small (40–79 cols).

    See :meth:`compose_extra_small`.
    """
    return None

compose_medium

compose_medium(
    ctx: ComponentRenderContext[StateT],
) -> Content | None

Compose content when the viewport is medium (80–119 cols).

See :meth:compose_extra_small.

Source code in xnano/components/component.py
@responsive_noop
def compose_medium(
    self, ctx: ComponentRenderContext[StateT]
) -> Content | None:
    """Compose content when the viewport is medium (80–119 cols).

    See :meth:`compose_extra_small`.
    """
    return None

compose_large

compose_large(
    ctx: ComponentRenderContext[StateT],
) -> Content | None

Compose content when the viewport is large (120–159 cols).

See :meth:compose_extra_small.

Source code in xnano/components/component.py
@responsive_noop
def compose_large(
    self, ctx: ComponentRenderContext[StateT]
) -> Content | None:
    """Compose content when the viewport is large (120–159 cols).

    See :meth:`compose_extra_small`.
    """
    return None

compose_extra_large

compose_extra_large(
    ctx: ComponentRenderContext[StateT],
) -> Content | None

Compose content when the viewport is extra large (>= 160 cols).

See :meth:compose_extra_small.

Source code in xnano/components/component.py
@responsive_noop
def compose_extra_large(
    self, ctx: ComponentRenderContext[StateT]
) -> Content | None:
    """Compose content when the viewport is extra large (>= 160 cols).

    See :meth:`compose_extra_small`.
    """
    return None

handle_keyboard

handle_keyboard(keyboard: 'KeyboardEventData') -> bool

Optional keyboard handler while focused.

Returns:

  • bool

    True when the event was consumed.

Source code in xnano/components/component.py
def handle_keyboard(self, keyboard: "KeyboardEventData") -> bool:
    """Optional keyboard handler while focused.

    Returns:
        ``True`` when the event was consumed.
    """
    return False

handle_paste

handle_paste(text: str) -> bool

Optional paste handler while focused.

Returns:

  • bool

    True when the paste was consumed.

Source code in xnano/components/component.py
def handle_paste(self, text: str) -> bool:
    """Optional paste handler while focused.

    Returns:
        ``True`` when the paste was consumed.
    """
    return False

compose

compose(ctx: 'ComponentRenderContext')

Compose Plot content with a native ChartNode paint fallback.

Returns:

  • Interface-neutral content for this chart.

Source code in xnano/components/chart.py
def compose(self, ctx: "ComponentRenderContext"):
    """Compose Plot content with a native ChartNode paint fallback.

    Returns:
        Interface-neutral content for this chart.
    """
    return self._compose_plot()

Series dataclass

Series(
    label: str | None = None,
    color: "ColorLike | None" = None,
    kind: "GraphTypeLike | None" = None,
    marker: "CanvasMarkerLike | None" = None,
)

Bases: ComponentDescriptor

Describe one chart series.

Attributes:

  • label (str | None) –

    Legend label; None derives it from the attribute name.

  • color ('ColorLike | None') –

    Series color.

  • kind ('GraphTypeLike | None') –

    Plot representation.

  • marker ('CanvasMarkerLike | None') –

    Glyph set used to paint the series.

Methods:

name class-attribute instance-attribute

name: str = dataclasses.field(default='', init=False)

Attribute name assigned by the component class.

label class-attribute instance-attribute

label: str | None = None

Legend label.

color class-attribute instance-attribute

color: 'ColorLike | None' = None

Series color.

kind class-attribute instance-attribute

kind: 'GraphTypeLike | None' = None

Graph representation.

marker class-attribute instance-attribute

marker: 'CanvasMarkerLike | None' = None

Marker used to paint samples.

resolve_label

resolve_label() -> str

Return the displayed legend label.

Source code in xnano/components/schema.py
def resolve_label(self) -> str:
    """Return the displayed legend label."""
    return self.label if self.label is not None else self.name

Bars dataclass

Bars(
    *,
    style: Style | None = None,
    z: int = 0,
    visible: bool = True,
    groups: tuple[BarGroup, ...] = (),
    bar_width: int = 1,
    bar_gap: int = 1,
    group_gap: int = 0,
    max_value: int | None = None,
    direction: Direction = "vertical",
    color: ColorLike | None = None,
    value_color: ColorLike | None = None,
    label_color: ColorLike | None = None
)

Bases: ContentBase

Render grouped bars.

Example

Bars(groups=(BarGroup(bars=(Bar(value=8, label="A"),)),))

Attributes:

style class-attribute instance-attribute

style: Style | None = None

Optional shared style.

z class-attribute instance-attribute

z: int = 0

Sibling-local paint order.

visible class-attribute instance-attribute

visible: bool = True

Whether this content paints.

groups class-attribute instance-attribute

groups: tuple[BarGroup, ...] = ()

Bar groups.

bar_width class-attribute instance-attribute

bar_width: int = 1

Width of each bar in cells.

bar_gap class-attribute instance-attribute

bar_gap: int = 1

Cells between bars.

group_gap class-attribute instance-attribute

group_gap: int = 0

Cells between groups.

max_value class-attribute instance-attribute

max_value: int | None = None

Explicit value ceiling.

direction class-attribute instance-attribute

direction: Direction = 'vertical'

Bar growth direction.

color class-attribute instance-attribute

color: ColorLike | None = None

Default bar color.

value_color class-attribute instance-attribute

value_color: ColorLike | None = None

Default value-label color.

label_color class-attribute instance-attribute

label_color: ColorLike | None = None

Bar-label color.

Canvas dataclass

Canvas(
    *,
    style: Style | None = None,
    z: int = 0,
    visible: bool = True,
    shapes: tuple[CanvasShape, ...] = (),
    x_bounds: tuple[float, float] = (0.0, 1.0),
    y_bounds: tuple[float, float] = (0.0, 1.0),
    marker: CanvasMarkerLike = "braille",
    background: ColorLike | None = None
)

Bases: ContentBase

Render geometric shapes in numeric coordinate space.

Example

Canvas(shapes=(CanvasCircle(x=0.5, y=0.5, radius=0.25),))

Attributes:

style class-attribute instance-attribute

style: Style | None = None

Optional shared style.

z class-attribute instance-attribute

z: int = 0

Sibling-local paint order.

visible class-attribute instance-attribute

visible: bool = True

Whether this content paints.

shapes class-attribute instance-attribute

shapes: tuple[CanvasShape, ...] = ()

Shapes painted in declaration order.

x_bounds class-attribute instance-attribute

x_bounds: tuple[float, float] = (0.0, 1.0)

Horizontal coordinate bounds.

y_bounds class-attribute instance-attribute

y_bounds: tuple[float, float] = (0.0, 1.0)

Vertical coordinate bounds.

marker class-attribute instance-attribute

marker: CanvasMarkerLike = 'braille'

Canvas point marker.

background class-attribute instance-attribute

background: ColorLike | None = None

Canvas background.

CellCanvas dataclass

CellCanvas(
    *,
    style: Style | None = None,
    z: int = 0,
    visible: bool = True,
    rows: tuple[tuple[CellSpan, ...], ...] = (),
    width: int = 0,
    height: int = 0
)

Bases: ContentBase

A rectangular sequence of styled cell rows.

Example

CellCanvas.from_rows(((CellSpan("OK", color="green"),),))

Attributes:

Methods:

  • from_rows

    Create a cell canvas from styled span rows.

style class-attribute instance-attribute

style: Style | None = None

Optional shared style.

z class-attribute instance-attribute

z: int = 0

Sibling-local paint order.

visible class-attribute instance-attribute

visible: bool = True

Whether this content paints.

rows class-attribute instance-attribute

rows: tuple[tuple[CellSpan, ...], ...] = ()

Rows of styled spans.

width class-attribute instance-attribute

width: int = 0

Canvas width in cells.

height class-attribute instance-attribute

height: int = 0

Canvas height in cells.

from_rows classmethod

from_rows(
    rows: Sequence[Sequence[CellSpan | str]],
    *,
    width: int | None = None,
    style: Style | None = None,
    z: int = 0,
    visible: bool = True
) -> "CellCanvas"

Create a cell canvas from styled span rows.

Source code in xnano/core/content.py
@classmethod
def from_rows(
    cls,
    rows: Sequence[Sequence[CellSpan | str]],
    *,
    width: int | None = None,
    style: Style | None = None,
    z: int = 0,
    visible: bool = True,
) -> "CellCanvas":
    """Create a cell canvas from styled span rows."""
    normalized = tuple(
        tuple(
            span if isinstance(span, CellSpan) else CellSpan(text=span)
            for span in row
        )
        for row in rows
    )
    measured_width = max(
        (sum(len(span.text) for span in row) for row in normalized),
        default=0,
    )
    return cls(
        rows=normalized,
        width=measured_width if width is None else width,
        height=len(normalized),
        style=style,
        z=z,
        visible=visible,
    )

Clear dataclass

Clear(
    *,
    style: Style | None = None,
    z: int = 0,
    visible: bool = True
)

Bases: ContentBase

Clear the assigned render area.

Attributes:

  • style (Style | None) –

    Optional shared style.

  • z (int) –

    Sibling-local paint order.

  • visible (bool) –

    Whether this content paints.

style class-attribute instance-attribute

style: Style | None = None

Optional shared style.

z class-attribute instance-attribute

z: int = 0

Sibling-local paint order.

visible class-attribute instance-attribute

visible: bool = True

Whether this content paints.

Component dataclass

Component(
    *,
    visible: bool = True,
    z: int = 0,
    fit_content: bool = True
)

Base class for custom xnano components.

Component attributes are live state: changing an attribute changes the next rendered frame. Implement compose() to return content. Interactive components can also implement handle_keyboard() or handle_paste().

The framework updates the read-only focused property when the component gains or loses field focus.

Example

Text(content="Ready", color="green")

Attributes:

  • visible (bool) –

    Whether the component is rendered.

  • z (int) –

    Paint order relative to sibling components.

  • fit_content (bool) –

    Whether layout should prefer the natural content size.

Methods:

  • component_post_init

    Initialize subclass state after dataclass fields are assigned.

  • get_frame

    Optional frame/panel chrome around composed content.

  • get_size

    Return the preferred cell size of this component.

  • before_render

    Called before rendering; returns the effective render area.

  • after_render

    Called after rendering for optional post-paint work.

  • compose

    Compose interface-neutral content for this component.

  • compose_extra_small

    Compose content when the viewport is extra small (< 40 cols).

  • compose_small

    Compose content when the viewport is small (40–79 cols).

  • compose_medium

    Compose content when the viewport is medium (80–119 cols).

  • compose_large

    Compose content when the viewport is large (120–159 cols).

  • compose_extra_large

    Compose content when the viewport is extra large (>= 160 cols).

  • handle_keyboard

    Optional keyboard handler while focused.

  • handle_paste

    Optional paste handler while focused.

visible class-attribute instance-attribute

visible: bool = dataclasses.field(
    default=True, kw_only=True
)

Whether this component paints at all.

z class-attribute instance-attribute

z: int = dataclasses.field(default=0, kw_only=True)

Stacking order among sibling content.

fit_content class-attribute instance-attribute

fit_content: bool = dataclasses.field(
    default=True, kw_only=True
)

When True, paint at natural size inside a larger slot.

focused property

focused: bool

Whether this component currently holds field focus.

component_post_init

component_post_init() -> None

Initialize subclass state after dataclass fields are assigned.

Override this method instead of __post_init__.

Source code in xnano/components/component.py
def component_post_init(self) -> None:
    """Initialize subclass state after dataclass fields are assigned.

    Override this method instead of ``__post_init__``.
    """
    return None

get_frame

get_frame() -> Any | None

Optional frame/panel chrome around composed content.

Source code in xnano/components/component.py
def get_frame(self) -> Any | None:
    """Optional frame/panel chrome around composed content."""
    return None

get_size

get_size(ctx: ComponentRenderContext[StateT]) -> Size

Return the preferred cell size of this component.

Source code in xnano/components/component.py
def get_size(self, ctx: ComponentRenderContext[StateT]) -> Size:
    """Return the preferred cell size of this component."""
    return Size(width=0, height=0)

before_render

before_render(
    ctx: ComponentRenderContext[StateT], area: "Area"
) -> "Area"

Called before rendering; returns the effective render area.

Source code in xnano/components/component.py
def before_render(
    self,
    ctx: ComponentRenderContext[StateT],
    area: "Area",
) -> "Area":
    """Called before rendering; returns the effective render area."""
    return area

after_render

after_render(
    ctx: ComponentRenderContext[StateT], area: "Area"
) -> None

Called after rendering for optional post-paint work.

Source code in xnano/components/component.py
def after_render(
    self,
    ctx: ComponentRenderContext[StateT],
    area: "Area",
) -> None:
    """Called after rendering for optional post-paint work."""
    return None

compose

compose(
    ctx: ComponentRenderContext[StateT],
) -> Content | None

Compose interface-neutral content for this component.

Returns:

  • Content | None

    A Content tree, or None when nothing should paint.

Source code in xnano/components/component.py
def compose(self, ctx: ComponentRenderContext[StateT]) -> Content | None:
    """Compose interface-neutral content for this component.

    Returns:
        A ``Content`` tree, or ``None`` when nothing should paint.
    """
    return None

compose_extra_small

compose_extra_small(
    ctx: ComponentRenderContext[StateT],
) -> Content | None

Compose content when the viewport is extra small (< 40 cols).

Optional responsive counterpart to :meth:compose. When overridden, it is used instead of compose while the window is in this size tier. Overriding any compose_* variant opts the component into breakpoint dispatch; a component that overrides none pays no per-frame cost.

Source code in xnano/components/component.py
@responsive_noop
def compose_extra_small(
    self, ctx: ComponentRenderContext[StateT]
) -> Content | None:
    """Compose content when the viewport is extra small (< 40 cols).

    Optional responsive counterpart to :meth:`compose`. When
    overridden, it is used *instead of* ``compose`` while the window
    is in this size tier. Overriding any ``compose_*`` variant opts the
    component into breakpoint dispatch; a component that overrides none
    pays no per-frame cost.
    """
    return None

compose_small

compose_small(
    ctx: ComponentRenderContext[StateT],
) -> Content | None

Compose content when the viewport is small (40–79 cols).

See :meth:compose_extra_small.

Source code in xnano/components/component.py
@responsive_noop
def compose_small(
    self, ctx: ComponentRenderContext[StateT]
) -> Content | None:
    """Compose content when the viewport is small (40–79 cols).

    See :meth:`compose_extra_small`.
    """
    return None

compose_medium

compose_medium(
    ctx: ComponentRenderContext[StateT],
) -> Content | None

Compose content when the viewport is medium (80–119 cols).

See :meth:compose_extra_small.

Source code in xnano/components/component.py
@responsive_noop
def compose_medium(
    self, ctx: ComponentRenderContext[StateT]
) -> Content | None:
    """Compose content when the viewport is medium (80–119 cols).

    See :meth:`compose_extra_small`.
    """
    return None

compose_large

compose_large(
    ctx: ComponentRenderContext[StateT],
) -> Content | None

Compose content when the viewport is large (120–159 cols).

See :meth:compose_extra_small.

Source code in xnano/components/component.py
@responsive_noop
def compose_large(
    self, ctx: ComponentRenderContext[StateT]
) -> Content | None:
    """Compose content when the viewport is large (120–159 cols).

    See :meth:`compose_extra_small`.
    """
    return None

compose_extra_large

compose_extra_large(
    ctx: ComponentRenderContext[StateT],
) -> Content | None

Compose content when the viewport is extra large (>= 160 cols).

See :meth:compose_extra_small.

Source code in xnano/components/component.py
@responsive_noop
def compose_extra_large(
    self, ctx: ComponentRenderContext[StateT]
) -> Content | None:
    """Compose content when the viewport is extra large (>= 160 cols).

    See :meth:`compose_extra_small`.
    """
    return None

handle_keyboard

handle_keyboard(keyboard: 'KeyboardEventData') -> bool

Optional keyboard handler while focused.

Returns:

  • bool

    True when the event was consumed.

Source code in xnano/components/component.py
def handle_keyboard(self, keyboard: "KeyboardEventData") -> bool:
    """Optional keyboard handler while focused.

    Returns:
        ``True`` when the event was consumed.
    """
    return False

handle_paste

handle_paste(text: str) -> bool

Optional paste handler while focused.

Returns:

  • bool

    True when the paste was consumed.

Source code in xnano/components/component.py
def handle_paste(self, text: str) -> bool:
    """Optional paste handler while focused.

    Returns:
        ``True`` when the paste was consumed.
    """
    return False

ComponentRenderContext dataclass

ComponentRenderContext(
    area: "Area",
    terminal: "Terminal[StateT] | None" = None,
    state: StateT | None = None,
    component: "Component | None" = None,
)

Bases: Generic[StateT]

Render-time scope passed into component paint hooks.

Attributes:

  • area ('Area') –

    Target area for this paint.

  • terminal ('Terminal[StateT] | None') –

    Terminal handling the render, when available.

  • state (StateT | None) –

    Application state for this paint.

  • component ('Component | None') –

    Component being rendered, when known.

area instance-attribute

area: 'Area'

Area assigned to the component.

terminal class-attribute instance-attribute

terminal: 'Terminal[StateT] | None' = None

Terminal handling the render.

state class-attribute instance-attribute

state: StateT | None = None

Application state for this paint.

component class-attribute instance-attribute

component: 'Component | None' = None

Component being rendered.

Gauge dataclass

Gauge(
    *,
    style: Style | None = None,
    z: int = 0,
    visible: bool = True,
    progress: float = 0.0,
    label: str | None = None,
    foreground: ColorLike = "green",
    background: ColorLike | None = None
)

Bases: ContentBase

Render a filled progress gauge.

Example

Gauge(progress=0.75, label="75%")

Attributes:

style class-attribute instance-attribute

style: Style | None = None

Optional shared style.

z class-attribute instance-attribute

z: int = 0

Sibling-local paint order.

visible class-attribute instance-attribute

visible: bool = True

Whether this content paints.

progress class-attribute instance-attribute

progress: float = 0.0

Completion ratio from zero to one.

label class-attribute instance-attribute

label: str | None = None

Text displayed inside the gauge.

foreground class-attribute instance-attribute

foreground: ColorLike = 'green'

Filled-region color.

background class-attribute instance-attribute

background: ColorLike | None = None

Gauge background color.

Items dataclass

Items(
    *,
    style: Style | None = None,
    z: int = 0,
    visible: bool = True,
    items: tuple[str | Run | TextBlock, ...] = (),
    selected: int | None = None,
    foreground: ColorLike | None = None,
    background: ColorLike | None = None,
    highlight_color: ColorLike = "black",
    highlight_background: ColorLike = "white",
    highlight_symbol: str = "> "
)

Bases: ContentBase

Render a selectable list of text items.

Example

Items(items=("One", "Two"), selected=0)

Attributes:

style class-attribute instance-attribute

style: Style | None = None

Optional shared style.

z class-attribute instance-attribute

z: int = 0

Sibling-local paint order.

visible class-attribute instance-attribute

visible: bool = True

Whether this content paints.

items class-attribute instance-attribute

items: tuple[str | Run | TextBlock, ...] = ()

List entries.

selected class-attribute instance-attribute

selected: int | None = None

Selected entry index.

foreground class-attribute instance-attribute

foreground: ColorLike | None = None

Default foreground.

background class-attribute instance-attribute

background: ColorLike | None = None

Default background.

highlight_color class-attribute instance-attribute

highlight_color: ColorLike = 'black'

Selected-entry foreground.

highlight_background class-attribute instance-attribute

highlight_background: ColorLike = 'white'

Selected-entry background.

highlight_symbol class-attribute instance-attribute

highlight_symbol: str = '> '

Symbol prefixed to the selected entry.

LineGauge dataclass

LineGauge(
    *,
    style: Style | None = None,
    z: int = 0,
    visible: bool = True,
    progress: float = 0.0,
    label: str | None = None,
    foreground: ColorLike | None = None,
    filled_color: ColorLike | None = None,
    unfilled_color: ColorLike | None = None,
    background: ColorLike | None = None
)

Bases: ContentBase

Render progress as a single horizontal line.

Attributes:

style class-attribute instance-attribute

style: Style | None = None

Optional shared style.

z class-attribute instance-attribute

z: int = 0

Sibling-local paint order.

visible class-attribute instance-attribute

visible: bool = True

Whether this content paints.

progress class-attribute instance-attribute

progress: float = 0.0

Completion ratio from zero to one.

label class-attribute instance-attribute

label: str | None = None

Text displayed with the line.

foreground class-attribute instance-attribute

foreground: ColorLike | None = None

Default foreground color.

filled_color class-attribute instance-attribute

filled_color: ColorLike | None = None

Completed-region color.

unfilled_color class-attribute instance-attribute

unfilled_color: ColorLike | None = None

Remaining-region color.

background class-attribute instance-attribute

background: ColorLike | None = None

Line background color.

Panel dataclass

Panel(
    *,
    style: Style | None = None,
    z: int = 0,
    visible: bool = True,
    child: ContentBase,
    title: str | None = None,
    title_position: FrameTitlePosition | None = None,
    border: Border | None = None,
    border_color: ColorLike | None = None,
    border_sides: tuple[Side, ...] | None = None,
    background: ColorLike | None = None,
    padding: PaddingLike | None = None
)

Bases: ContentBase

Decorate child content with a frame and padding.

Example

Panel(child=TextBlock(text="Status"), title="Service")

Attributes:

style class-attribute instance-attribute

style: Style | None = None

Optional shared style.

z class-attribute instance-attribute

z: int = 0

Sibling-local paint order.

visible class-attribute instance-attribute

visible: bool = True

Whether this content paints.

child instance-attribute

child: ContentBase

Content inside the panel.

title class-attribute instance-attribute

title: str | None = None

Optional frame title.

title_position class-attribute instance-attribute

title_position: FrameTitlePosition | None = None

Frame title alignment.

border class-attribute instance-attribute

border: Border | None = None

Border style.

border_color class-attribute instance-attribute

border_color: ColorLike | None = None

Border foreground color.

border_sides class-attribute instance-attribute

border_sides: tuple[Side, ...] | None = None

Visible border sides.

background class-attribute instance-attribute

background: ColorLike | None = None

Panel background color.

padding class-attribute instance-attribute

padding: PaddingLike | None = None

Space between border and child.

Plot dataclass

Plot(
    *,
    style: Style | None = None,
    z: int = 0,
    visible: bool = True,
    datasets: tuple[PlotDataset, ...] = (),
    x_axis: PlotAxis | None = None,
    y_axis: PlotAxis | None = None,
    color: ColorLike | None = None,
    legend: bool = True,
    legend_position: LegendPositionLike | None = "top_right"
)

Bases: ContentBase

Render one or more numeric data series.

Example

Plot(datasets=(PlotDataset(data=((0, 1), (1, 3))),))

Attributes:

style class-attribute instance-attribute

style: Style | None = None

Optional shared style.

z class-attribute instance-attribute

z: int = 0

Sibling-local paint order.

visible class-attribute instance-attribute

visible: bool = True

Whether this content paints.

datasets class-attribute instance-attribute

datasets: tuple[PlotDataset, ...] = ()

Data series.

x_axis class-attribute instance-attribute

x_axis: PlotAxis | None = None

Horizontal axis.

y_axis class-attribute instance-attribute

y_axis: PlotAxis | None = None

Vertical axis.

color class-attribute instance-attribute

color: ColorLike | None = None

Default plot color.

legend class-attribute instance-attribute

legend: bool = True

Whether to show the legend.

legend_position class-attribute instance-attribute

legend_position: LegendPositionLike | None = 'top_right'

Legend placement.

Run dataclass

Run(
    *,
    style: Style | None = None,
    z: int = 0,
    visible: bool = True,
    text: str,
    foreground: ColorLike | None = None,
    background: ColorLike | None = None,
    modifiers: tuple[CharacterModifier, ...] = (),
    color: InitVar[Any] = _ALIAS_UNSET
)

Bases: ContentBase

One styled text run.

Example

Run(text="Ready", foreground="green", modifiers=("bold",))

Attributes:

Methods:

  • __post_init__

    Apply the deprecated constructor alias.

  • plain

    Create a plain styled run.

style class-attribute instance-attribute

style: Style | None = None

Optional shared style.

z class-attribute instance-attribute

z: int = 0

Sibling-local paint order.

visible class-attribute instance-attribute

visible: bool = True

Whether this content paints.

text instance-attribute

text: str

Text displayed by the run.

foreground class-attribute instance-attribute

foreground: ColorLike | None = None

Foreground color.

background class-attribute instance-attribute

background: ColorLike | None = None

Background color.

modifiers class-attribute instance-attribute

modifiers: tuple[CharacterModifier, ...] = ()

Character modifiers.

__post_init__

__post_init__(color: Any) -> None

Apply the deprecated constructor alias.

Source code in xnano/core/content.py
def __post_init__(self, color: Any) -> None:
    """Apply the deprecated constructor alias."""
    resolve_init_alias(self, color, old="color", new="foreground")

plain classmethod

plain(
    text: str,
    *,
    foreground: ColorLike | None = None,
    background: ColorLike | None = None,
    modifiers: Sequence[CharacterModifier] | None = None,
    style: Style | None = None,
    z: int = 0,
    visible: bool = True,
    color: ColorLike | None = None
) -> "Run"

Create a plain styled run.

Parameters:

  • text (str) –

    Text displayed by the run.

  • foreground (ColorLike | None, default: None ) –

    Foreground color.

  • background (ColorLike | None, default: None ) –

    Background color.

  • modifiers (Sequence[CharacterModifier] | None, default: None ) –

    Character modifiers.

  • style (Style | None, default: None ) –

    Optional shared style.

  • z (int, default: 0 ) –

    Sibling-local paint order.

  • visible (bool, default: True ) –

    Whether the run paints.

  • color (ColorLike | None, default: None ) –

    Deprecated alias for foreground.

Returns:

  • 'Run'

    A run containing text.

Source code in xnano/core/content.py
@classmethod
def plain(
    cls,
    text: str,
    *,
    foreground: ColorLike | None = None,
    background: ColorLike | None = None,
    modifiers: Sequence[CharacterModifier] | None = None,
    style: Style | None = None,
    z: int = 0,
    visible: bool = True,
    color: ColorLike | None = None,
) -> "Run":
    """Create a plain styled run.

    Args:
        text: Text displayed by the run.
        foreground: Foreground color.
        background: Background color.
        modifiers: Character modifiers.
        style: Optional shared style.
        z: Sibling-local paint order.
        visible: Whether the run paints.
        color: Deprecated alias for ``foreground``.

    Returns:
        A run containing ``text``.
    """
    foreground = resolve_color_alias(foreground, color, stacklevel=3)
    return cls(
        text=text,
        foreground=foreground,
        background=background,
        modifiers=tuple(modifiers or ()),
        style=style,
        z=z,
        visible=visible,
    )

ScrollbarContent dataclass

ScrollbarContent(
    *,
    style: Style | None = None,
    z: int = 0,
    visible: bool = True,
    content_length: int = 0,
    position: int = 0,
    viewport_length: int = 0,
    orientation: ScrollbarOrientationLike = "vertical_right",
    thumb_symbol: str | None = None,
    track_symbol: str | None = None,
    begin_symbol: str | None = None,
    end_symbol: str | None = None,
    color: ColorLike | None = None,
    thumb_color: ColorLike | None = None,
    track_color: ColorLike | None = None
)

Bases: ContentBase

Render scroll position and viewport proportion.

Example

Scrollbar(content_length=100, viewport_length=20, position=10)

Attributes:

style class-attribute instance-attribute

style: Style | None = None

Optional shared style.

z class-attribute instance-attribute

z: int = 0

Sibling-local paint order.

visible class-attribute instance-attribute

visible: bool = True

Whether this content paints.

content_length class-attribute instance-attribute

content_length: int = 0

Total scrollable length.

position class-attribute instance-attribute

position: int = 0

Current scroll offset.

viewport_length class-attribute instance-attribute

viewport_length: int = 0

Visible length.

orientation class-attribute instance-attribute

orientation: ScrollbarOrientationLike = 'vertical_right'

Scrollbar edge and direction.

thumb_symbol class-attribute instance-attribute

thumb_symbol: str | None = None

Custom thumb symbol.

track_symbol class-attribute instance-attribute

track_symbol: str | None = None

Custom track symbol.

begin_symbol class-attribute instance-attribute

begin_symbol: str | None = None

Symbol at the beginning.

end_symbol class-attribute instance-attribute

end_symbol: str | None = None

Symbol at the end.

color class-attribute instance-attribute

color: ColorLike | None = None

Default scrollbar color.

thumb_color class-attribute instance-attribute

thumb_color: ColorLike | None = None

Thumb color.

track_color class-attribute instance-attribute

track_color: ColorLike | None = None

Track color.

Sparkline dataclass

Sparkline(
    *,
    style: Style | None = None,
    z: int = 0,
    visible: bool = True,
    data: tuple[int, ...] = (),
    bars: tuple[SparklineBar, ...] | None = None,
    max_value: int | None = None,
    foreground: ColorLike | None = None,
    background: ColorLike | None = None,
    absent_value_color: ColorLike | None = None,
    absent_value_symbol: str | None = None
)

Bases: ContentBase

Render a compact sequence of vertical samples.

Example

Sparkline(data=(2, 5, 3, 8), foreground="cyan")

Attributes:

style class-attribute instance-attribute

style: Style | None = None

Optional shared style.

z class-attribute instance-attribute

z: int = 0

Sibling-local paint order.

visible class-attribute instance-attribute

visible: bool = True

Whether this content paints.

data class-attribute instance-attribute

data: tuple[int, ...] = ()

Sample values.

bars class-attribute instance-attribute

bars: tuple[SparklineBar, ...] | None = None

Individually styled samples.

max_value class-attribute instance-attribute

max_value: int | None = None

Explicit sample ceiling.

foreground class-attribute instance-attribute

foreground: ColorLike | None = None

Default sample color.

background class-attribute instance-attribute

background: ColorLike | None = None

Sparkline background.

absent_value_color class-attribute instance-attribute

absent_value_color: ColorLike | None = None

Color for missing or zero samples.

absent_value_symbol class-attribute instance-attribute

absent_value_symbol: str | None = None

Symbol for missing or zero samples.

Stack dataclass

Stack(
    *,
    style: Style | None = None,
    z: int = 0,
    visible: bool = True,
    children: tuple[ContentBase, ...] = (),
    direction: Direction = "vertical",
    gap: int = 0
)

Bases: ContentBase

Lay out child content in one direction.

Example

Stack(children=(TextBlock(text="One"), TextBlock(text="Two")))

Attributes:

style class-attribute instance-attribute

style: Style | None = None

Optional shared style.

z class-attribute instance-attribute

z: int = 0

Sibling-local paint order.

visible class-attribute instance-attribute

visible: bool = True

Whether this content paints.

children class-attribute instance-attribute

children: tuple[ContentBase, ...] = ()

Child content.

direction class-attribute instance-attribute

direction: Direction = 'vertical'

Layout direction.

gap class-attribute instance-attribute

gap: int = 0

Cells between children.

TableGrid dataclass

TableGrid(
    *,
    style: Style | None = None,
    z: int = 0,
    visible: bool = True,
    rows: tuple[TableRow, ...] = (),
    header: TableRow | None = None,
    footer: TableRow | None = None,
    column_widths: tuple[int | float, ...] | None = None,
    column_spacing: int = 1,
    selected_row: int | None = None,
    selected_column: int | None = None,
    highlight_color: ColorLike | None = None,
    highlight_background: ColorLike | None = None,
    highlight_symbol: str | None = None
)

Bases: ContentBase

Render tabular rows with optional selection.

Example

TableGrid(rows=(TableRow(cells=("Ada", "Engineer")),))

Attributes:

style class-attribute instance-attribute

style: Style | None = None

Optional shared style.

z class-attribute instance-attribute

z: int = 0

Sibling-local paint order.

visible class-attribute instance-attribute

visible: bool = True

Whether this content paints.

rows class-attribute instance-attribute

rows: tuple[TableRow, ...] = ()

Body rows.

header class-attribute instance-attribute

header: TableRow | None = None

Optional header row.

footer class-attribute instance-attribute

footer: TableRow | None = None

Optional footer row.

column_widths class-attribute instance-attribute

column_widths: tuple[int | float, ...] | None = None

Fixed cell widths or fractional widths.

column_spacing class-attribute instance-attribute

column_spacing: int = 1

Cells between columns.

selected_row class-attribute instance-attribute

selected_row: int | None = None

Selected body row.

selected_column class-attribute instance-attribute

selected_column: int | None = None

Selected column.

highlight_color class-attribute instance-attribute

highlight_color: ColorLike | None = None

Selection foreground.

highlight_background class-attribute instance-attribute

highlight_background: ColorLike | None = None

Selection background.

highlight_symbol class-attribute instance-attribute

highlight_symbol: str | None = None

Symbol prefixed to the selected row.

TextBlock dataclass

TextBlock(
    *,
    style: Style | None = None,
    z: int = 0,
    visible: bool = True,
    text: str = "",
    lines: tuple[tuple[Run, ...], ...] = (),
    foreground: ColorLike | None = None,
    background: ColorLike | None = None,
    modifiers: tuple[CharacterModifier, ...] = (),
    horizontal_align: Alignment | None = None,
    vertical_align: VerticalAlignment | None = None,
    wrap: bool = True,
    color: InitVar[Any] = _ALIAS_UNSET,
    align: InitVar[Any] = _ALIAS_UNSET
)

Bases: ContentBase

Wrapped plain text or lines of styled runs.

Example

TextBlock(text="Hello", horizontal_align="center")

Attributes:

Methods:

  • __post_init__

    Apply deprecated constructor aliases.

  • from_plain

    Create a block from plain text and explicit style values.

  • from_lines

    Create a block from styled line sequences.

style class-attribute instance-attribute

style: Style | None = None

Optional shared style.

z class-attribute instance-attribute

z: int = 0

Sibling-local paint order.

visible class-attribute instance-attribute

visible: bool = True

Whether this content paints.

text class-attribute instance-attribute

text: str = ''

Plain text content.

lines class-attribute instance-attribute

lines: tuple[tuple[Run, ...], ...] = ()

Styled text lines.

foreground class-attribute instance-attribute

foreground: ColorLike | None = None

Default foreground color.

background class-attribute instance-attribute

background: ColorLike | None = None

Default background color.

modifiers class-attribute instance-attribute

modifiers: tuple[CharacterModifier, ...] = ()

Default character modifiers.

horizontal_align class-attribute instance-attribute

horizontal_align: Alignment | None = None

Horizontal alignment.

vertical_align class-attribute instance-attribute

vertical_align: VerticalAlignment | None = None

Vertical alignment within the painted area.

wrap class-attribute instance-attribute

wrap: bool = True

Whether long lines wrap.

__post_init__

__post_init__(color: Any, align: Any) -> None

Apply deprecated constructor aliases.

Source code in xnano/core/content.py
def __post_init__(self, color: Any, align: Any) -> None:
    """Apply deprecated constructor aliases."""
    resolve_init_alias(self, color, old="color", new="foreground")
    resolve_init_alias(self, align, old="align", new="horizontal_align")

from_plain classmethod

from_plain(
    text: str,
    *,
    foreground: ColorLike | None = None,
    background: ColorLike | None = None,
    modifiers: Sequence[CharacterModifier] | None = None,
    horizontal_align: Alignment | None = None,
    vertical_align: VerticalAlignment | None = None,
    wrap: bool = True,
    style: Style | None = None,
    z: int = 0,
    visible: bool = True,
    color: ColorLike | None = None
) -> "TextBlock"

Create a block from plain text and explicit style values.

color is a deprecated alias for foreground.

Source code in xnano/core/content.py
@classmethod
def from_plain(
    cls,
    text: str,
    *,
    foreground: ColorLike | None = None,
    background: ColorLike | None = None,
    modifiers: Sequence[CharacterModifier] | None = None,
    horizontal_align: Alignment | None = None,
    vertical_align: VerticalAlignment | None = None,
    wrap: bool = True,
    style: Style | None = None,
    z: int = 0,
    visible: bool = True,
    color: ColorLike | None = None,
) -> "TextBlock":
    """Create a block from plain text and explicit style values.

    ``color`` is a deprecated alias for ``foreground``.
    """
    foreground = resolve_color_alias(foreground, color, stacklevel=3)
    return cls(
        text=text,
        foreground=foreground,
        background=background,
        modifiers=tuple(modifiers or ()),
        horizontal_align=horizontal_align,
        vertical_align=vertical_align,
        wrap=wrap,
        style=style,
        z=z,
        visible=visible,
    )

from_lines classmethod

from_lines(
    lines: Sequence[Sequence[Run] | Run | str],
    *,
    foreground: ColorLike | None = None,
    background: ColorLike | None = None,
    modifiers: Sequence[CharacterModifier] | None = None,
    horizontal_align: Alignment | None = None,
    vertical_align: VerticalAlignment | None = None,
    wrap: bool = True,
    style: Style | None = None,
    z: int = 0,
    visible: bool = True,
    color: ColorLike | None = None
) -> "TextBlock"

Create a block from styled line sequences.

color is a deprecated alias for foreground.

Source code in xnano/core/content.py
@classmethod
def from_lines(
    cls,
    lines: Sequence[Sequence[Run] | Run | str],
    *,
    foreground: ColorLike | None = None,
    background: ColorLike | None = None,
    modifiers: Sequence[CharacterModifier] | None = None,
    horizontal_align: Alignment | None = None,
    vertical_align: VerticalAlignment | None = None,
    wrap: bool = True,
    style: Style | None = None,
    z: int = 0,
    visible: bool = True,
    color: ColorLike | None = None,
) -> "TextBlock":
    """Create a block from styled line sequences.

    ``color`` is a deprecated alias for ``foreground``.
    """
    foreground = resolve_color_alias(foreground, color, stacklevel=3)
    normalized: list[tuple[Run, ...]] = []
    for line in lines:
        if isinstance(line, str):
            normalized.append((Run(text=line),))
        elif isinstance(line, Run):
            normalized.append((line,))
        else:
            normalized.append(tuple(line))
    return cls(
        lines=tuple(normalized),
        foreground=foreground,
        background=background,
        modifiers=tuple(modifiers or ()),
        horizontal_align=horizontal_align,
        vertical_align=vertical_align,
        wrap=wrap,
        style=style,
        z=z,
        visible=visible,
    )

Dropdown dataclass

Dropdown(
    items: Sequence[OptionItem] = (),
    query: str = "",
    filter: FilterMode = "fuzzy",
    accept: AcceptPolicy = "replace",
    searchable: bool = True,
    selected: int = 0,
    direction: OptionsDirection = "top_to_bottom",
    foreground: ColorLike | None = None,
    background: ColorLike | None = None,
    highlight_color: ColorLike = "black",
    highlight_background: ColorLike = "white",
    highlight_symbol: str = "> ",
    hovered: int | None = None,
    hover_symbol: str = "· ",
    match_color: ColorLike | None = "cyan",
    repeat_highlight_symbol: bool = False,
    focusable: bool = True,
    passthrough: Sequence[str] = (),
    open: bool = False,
    placeholder: str | Any | None = None,
    max_visible: int | None = None,
    close_on_select: bool = True,
    open_keys: Sequence[str] = ("enter", "space", "down"),
    close_keys: Sequence[str] = ("enter", "escape"),
    *,
    visible: bool = True,
    z: int = 0,
    fit_content: bool = True
)

Bases: Options

Collapsible choice list.

When closed, the dropdown shows the selected label or placeholder. When open, users can search and move through the available options.

class Form(BaseGrid):
    theme: Dropdown = Field(
        default=Dropdown(
            items=("dark", "light", "system"),
            placeholder="pick a theme",
        ),
    )

    @on_keyboard("enter")
def _choose(self, ctx: Context) -> None:
    if not self.theme.open:
        apply_theme(self.theme.value)
Example

Dropdown(items=("dark", "light"), placeholder="Choose a theme")

Attributes:

Methods:

  • component_post_init

    Initialize subclass state after dataclass fields are assigned.

  • get_frame

    Optional frame/panel chrome around composed content.

  • get_size

    Return the preferred cell size of this component.

  • before_render

    Called before rendering; returns the effective render area.

  • after_render

    Record the painted area so hover can map a pointer to a row.

  • compose_extra_small

    Compose content when the viewport is extra small (< 40 cols).

  • compose_small

    Compose content when the viewport is small (40–79 cols).

  • compose_medium

    Compose content when the viewport is medium (80–119 cols).

  • compose_large

    Compose content when the viewport is large (120–159 cols).

  • compose_extra_large

    Compose content when the viewport is extra large (>= 160 cols).

  • handle_paste

    Optional paste handler while focused.

  • select_value

    Select the visible item whose stored value equals value.

  • resolve_submission

    Reconcile typed composer text with the selection per accept.

  • move

    Move selected by delta, skipping disabled entries.

  • select

    Set selected to index within the filtered view.

  • clear_query

    Clear the filter query and reset selection to the first row.

  • handle_hover

    Mark the row under the pointer as hovered (mouse hover).

  • handle_keyboard

    Open, close, search, or move through the dropdown.

  • compose

    Compose closed label or open options content.

visible class-attribute instance-attribute

visible: bool = dataclasses.field(
    default=True, kw_only=True
)

Whether this component paints at all.

z class-attribute instance-attribute

z: int = dataclasses.field(default=0, kw_only=True)

Stacking order among sibling content.

fit_content class-attribute instance-attribute

fit_content: bool = dataclasses.field(
    default=True, kw_only=True
)

When True, paint at natural size inside a larger slot.

focused property

focused: bool

Whether this component currently holds field focus.

items class-attribute instance-attribute

items: Sequence[OptionItem] = ()

Entries to pick from (plain strings, Text, or Option).

query class-attribute instance-attribute

query: str = ''

Filter text. Edited by typing while focused when searchable; assign it from a hook to filter reactively.

filter class-attribute instance-attribute

filter: FilterMode = 'fuzzy'

How query narrows the visible items — see FilterMode.

"fuzzy"/True fuzzy-match, "prefix" prefix-match, "none"/ False show everything (for externally filtered items), or a (query, item_text) -> bool callable.

accept class-attribute instance-attribute

accept: AcceptPolicy = 'replace'

How resolve_submission reconciles typed text with the selection.

selected class-attribute instance-attribute

selected: int = 0

Selection index within the filtered view.

direction class-attribute instance-attribute

direction: OptionsDirection = 'top_to_bottom'

Visual order of option rows in the list.

foreground class-attribute instance-attribute

foreground: ColorLike | None = None

Default foreground color for unselected rows (deprecated alias: color).

background class-attribute instance-attribute

background: ColorLike | None = None

Default background color for unselected rows.

highlight_color class-attribute instance-attribute

highlight_color: ColorLike = 'black'

Foreground of the selected row.

highlight_background class-attribute instance-attribute

highlight_background: ColorLike = 'white'

Background of the selected row.

highlight_symbol class-attribute instance-attribute

highlight_symbol: str = '> '

Symbol prepended to the selected row.

hovered class-attribute instance-attribute

hovered: int | None = None

Row under the pointer (filtered-view index), or None.

Set by :meth:handle_hover when mouse events are enabled; drawn as a dim hover_symbol so it reads as distinct from the selection.

hover_symbol class-attribute instance-attribute

hover_symbol: str = '· '

Symbol prepended to the hovered row (dim), distinct from selection.

match_color class-attribute instance-attribute

match_color: ColorLike | None = 'cyan'

Emphasis color for characters matched by query.

repeat_highlight_symbol class-attribute instance-attribute

repeat_highlight_symbol: bool = False

When True, every row shows the highlight symbol.

focusable class-attribute instance-attribute

focusable: bool = True

Whether this Options participates in field focus (tab order).

passthrough class-attribute instance-attribute

passthrough: Sequence[str] = ()

Key bindings this list never captures while focused.

owns_cursor class-attribute instance-attribute

owns_cursor: bool = dataclasses.field(
    default=True, init=False
)

Whether selection replaces the hardware caret.

filtered property

filtered: tuple[int, ...]

Indices into items currently visible, in display order.

Use this with visible_items to inspect the current filtered view.

visible_items property

visible_items: tuple[str, ...]

Plain text of the currently visible items, in display order.

value property

value: Any | None

Stored value of the selected item, or None when empty.

selected_item property

selected_item: OptionItem | None

The selected item object, or None when the view is empty.

selected_value property

selected_value: Any | None

Stable stored value of the selection (alias of value).

Use with select_value to preserve a selection across item rebuilds by value/id rather than by fragile filtered index.

selected_label property

selected_label: str

Plain display text of the selected item, or "" when empty.

searchable class-attribute instance-attribute

searchable: bool = True

Whether typing while open edits query.

A dropdown is a search box when expanded, so unlike a plain Options list it defaults to True; set False for a pick-only dropdown.

open class-attribute instance-attribute

open: bool = False

Whether the options list is expanded.

placeholder class-attribute instance-attribute

placeholder: str | Any | None = None

Closed-row text when nothing is selected (string or Text).

max_visible class-attribute instance-attribute

max_visible: int | None = None

Cap on open-list rows around the selection; None shows all.

close_on_select class-attribute instance-attribute

close_on_select: bool = True

Close after enter accepts the current selection.

open_keys class-attribute instance-attribute

open_keys: Sequence[str] = ('enter', 'space', 'down')

Bindings that open a closed dropdown.

close_keys class-attribute instance-attribute

close_keys: Sequence[str] = ('enter', 'escape')

Bindings that close an open dropdown.

component_post_init

component_post_init() -> None

Initialize subclass state after dataclass fields are assigned.

Override this method instead of __post_init__.

Source code in xnano/components/component.py
def component_post_init(self) -> None:
    """Initialize subclass state after dataclass fields are assigned.

    Override this method instead of ``__post_init__``.
    """
    return None

get_frame

get_frame() -> Any | None

Optional frame/panel chrome around composed content.

Source code in xnano/components/component.py
def get_frame(self) -> Any | None:
    """Optional frame/panel chrome around composed content."""
    return None

get_size

get_size(ctx: ComponentRenderContext[StateT]) -> Size

Return the preferred cell size of this component.

Source code in xnano/components/component.py
def get_size(self, ctx: ComponentRenderContext[StateT]) -> Size:
    """Return the preferred cell size of this component."""
    return Size(width=0, height=0)

before_render

before_render(
    ctx: ComponentRenderContext[StateT], area: "Area"
) -> "Area"

Called before rendering; returns the effective render area.

Source code in xnano/components/component.py
def before_render(
    self,
    ctx: ComponentRenderContext[StateT],
    area: "Area",
) -> "Area":
    """Called before rendering; returns the effective render area."""
    return area

after_render

after_render(
    ctx: "ComponentRenderContext", area: Any
) -> None

Record the painted area so hover can map a pointer to a row.

Source code in xnano/components/options.py
def after_render(self, ctx: "ComponentRenderContext", area: Any) -> None:
    """Record the painted area so hover can map a pointer to a row."""
    self._content_area = area

compose_extra_small

compose_extra_small(
    ctx: ComponentRenderContext[StateT],
) -> Content | None

Compose content when the viewport is extra small (< 40 cols).

Optional responsive counterpart to :meth:compose. When overridden, it is used instead of compose while the window is in this size tier. Overriding any compose_* variant opts the component into breakpoint dispatch; a component that overrides none pays no per-frame cost.

Source code in xnano/components/component.py
@responsive_noop
def compose_extra_small(
    self, ctx: ComponentRenderContext[StateT]
) -> Content | None:
    """Compose content when the viewport is extra small (< 40 cols).

    Optional responsive counterpart to :meth:`compose`. When
    overridden, it is used *instead of* ``compose`` while the window
    is in this size tier. Overriding any ``compose_*`` variant opts the
    component into breakpoint dispatch; a component that overrides none
    pays no per-frame cost.
    """
    return None

compose_small

compose_small(
    ctx: ComponentRenderContext[StateT],
) -> Content | None

Compose content when the viewport is small (40–79 cols).

See :meth:compose_extra_small.

Source code in xnano/components/component.py
@responsive_noop
def compose_small(
    self, ctx: ComponentRenderContext[StateT]
) -> Content | None:
    """Compose content when the viewport is small (40–79 cols).

    See :meth:`compose_extra_small`.
    """
    return None

compose_medium

compose_medium(
    ctx: ComponentRenderContext[StateT],
) -> Content | None

Compose content when the viewport is medium (80–119 cols).

See :meth:compose_extra_small.

Source code in xnano/components/component.py
@responsive_noop
def compose_medium(
    self, ctx: ComponentRenderContext[StateT]
) -> Content | None:
    """Compose content when the viewport is medium (80–119 cols).

    See :meth:`compose_extra_small`.
    """
    return None

compose_large

compose_large(
    ctx: ComponentRenderContext[StateT],
) -> Content | None

Compose content when the viewport is large (120–159 cols).

See :meth:compose_extra_small.

Source code in xnano/components/component.py
@responsive_noop
def compose_large(
    self, ctx: ComponentRenderContext[StateT]
) -> Content | None:
    """Compose content when the viewport is large (120–159 cols).

    See :meth:`compose_extra_small`.
    """
    return None

compose_extra_large

compose_extra_large(
    ctx: ComponentRenderContext[StateT],
) -> Content | None

Compose content when the viewport is extra large (>= 160 cols).

See :meth:compose_extra_small.

Source code in xnano/components/component.py
@responsive_noop
def compose_extra_large(
    self, ctx: ComponentRenderContext[StateT]
) -> Content | None:
    """Compose content when the viewport is extra large (>= 160 cols).

    See :meth:`compose_extra_small`.
    """
    return None

handle_paste

handle_paste(text: str) -> bool

Optional paste handler while focused.

Returns:

  • bool

    True when the paste was consumed.

Source code in xnano/components/component.py
def handle_paste(self, text: str) -> bool:
    """Optional paste handler while focused.

    Returns:
        ``True`` when the paste was consumed.
    """
    return False

select_value

select_value(value: Any) -> bool

Select the visible item whose stored value equals value.

Returns True when a match is found. The stable way to keep a selection pinned across item rebuilds: store the value, rebuild items, then re-select by value.

Source code in xnano/components/options.py
def select_value(self, value: Any) -> bool:
    """Select the visible item whose stored value equals ``value``.

    Returns ``True`` when a match is found. The stable way to keep a
    selection pinned across item rebuilds: store the value, rebuild
    ``items``, then re-select by value.
    """
    for index, (item_index, _) in enumerate(self._filtered()):
        if _item_value(self.items[item_index]) == value:
            self.selected = index
            return True
    return False

resolve_submission

resolve_submission(typed: str) -> str

Reconcile typed composer text with the selection per accept.

Removes the userland "did they Tab-complete this row or type past it?" logic. See AcceptPolicy for the per-policy behavior.

Source code in xnano/components/options.py
def resolve_submission(self, typed: str) -> str:
    """Reconcile typed composer text with the selection per ``accept``.

    Removes the userland "did they Tab-complete this row or type past
    it?" logic. See ``AcceptPolicy`` for the per-policy behavior.
    """
    typed = typed or ""
    selected = self.selected_label
    if self.accept == "extend" or not selected:
        return typed
    if self.accept == "replace":
        return selected
    # if_prefix_only
    typed_stripped = typed.strip()
    selected_stripped = selected.strip()
    if not typed_stripped:
        return selected
    typed_lower = typed_stripped.lower()
    selected_lower = selected_stripped.lower()
    if typed_lower == selected_lower or typed_lower.startswith(
        selected_lower + " "
    ):
        return typed
    if selected_lower.startswith(typed_lower):
        return selected
    return selected

move

move(delta: int) -> None

Move selected by delta, skipping disabled entries.

Movement stays within the currently filtered choices.

Parameters:

  • delta (int) –

    Steps to move; negative moves toward the start.

Source code in xnano/components/options.py
def move(self, delta: int) -> None:
    """Move ``selected`` by ``delta``, skipping disabled entries.

    Movement stays within the currently filtered choices.

    Args:
        delta: Steps to move; negative moves toward the start.
    """
    visible = self._filtered()
    visible_count = len(visible)
    if visible_count == 0:
        self.selected = 0
        return
    current = max(0, min(self.selected, visible_count - 1))
    if delta == 0:
        self.selected = current
        return
    step = 1 if delta > 0 else -1
    remaining = abs(delta)
    index = current
    while remaining > 0:
        next_index = index + step
        if next_index < 0 or next_index >= visible_count:
            break
        index = next_index
        item_index = visible[index][0]
        if not _item_disabled(self.items[item_index]):
            remaining -= 1
    self.selected = index
    # If we landed on a disabled entry (all remaining disabled),
    # snap back to the nearest enabled neighbor.
    self._ensure_enabled_selection()

select

select(index: int) -> None

Set selected to index within the filtered view.

Disabled entries are rejected; the selection is left unchanged when index points at a disabled item. Out-of-range indices are clamped.

Parameters:

  • index (int) –

    Target index in the filtered view.

Source code in xnano/components/options.py
def select(self, index: int) -> None:
    """Set ``selected`` to ``index`` within the filtered view.

    Disabled entries are rejected; the selection is left unchanged
    when ``index`` points at a disabled item. Out-of-range indices
    are clamped.

    Args:
        index: Target index in the filtered view.
    """
    visible = self._filtered()
    if not visible:
        self.selected = 0
        return
    clamped = max(0, min(index, len(visible) - 1))
    item_index = visible[clamped][0]
    if _item_disabled(self.items[item_index]):
        return
    self.selected = clamped

clear_query

clear_query() -> None

Clear the filter query and reset selection to the first row.

Source code in xnano/components/options.py
def clear_query(self) -> None:
    """Clear the filter query and reset selection to the first row."""
    self.query = ""
    self.selected = 0
    self._ensure_enabled_selection()

handle_hover

handle_hover(x: int, y: int) -> bool

Mark the row under the pointer as hovered (mouse hover).

Hover is a distinct indicator from the selection: the hovered row is set here and drawn with a dim hover_symbol. Moving off the rows clears it. Returns whether the hovered row changed.

Source code in xnano/components/options.py
def handle_hover(self, x: int, y: int) -> bool:
    """Mark the row under the pointer as hovered (mouse hover).

    Hover is a *distinct* indicator from the selection: the hovered row
    is set here and drawn with a dim ``hover_symbol``. Moving off the
    rows clears it. Returns whether the hovered row changed.
    """
    del x  # rows span the full width; only the row (y) matters
    area = self._content_area
    if area is None:
        return False
    pairs = self._filtered()
    # The query row (searchable) sits above the items; skip it.
    top = area.y + (1 if self.searchable else 0)
    row = y - top
    visible_count = min(
        len(pairs) - self._visible_start,
        max(0, area.height - (1 if self.searchable else 0)),
    )
    if self.direction == "bottom_to_top" and visible_count:
        row = (visible_count - 1) - row
    hovered = (
        self._visible_start + row
        if pairs and 0 <= row < visible_count
        else None
    )
    if hovered != self.hovered:
        self.hovered = hovered
        return True
    return False

handle_keyboard

handle_keyboard(keyboard: 'KeyboardEventData') -> bool

Open, close, search, or move through the dropdown.

Opening keys expand a closed dropdown. Escape closes it without changing the selection. Enter accepts the selected value and closes it when close_on_select is enabled.

Parameters:

  • keyboard ('KeyboardEventData') –

    The keyboard event payload.

Returns:

  • bool

    True when the event was consumed.

Source code in xnano/components/dropdown.py
def handle_keyboard(self, keyboard: "KeyboardEventData") -> bool:
    """Open, close, search, or move through the dropdown.

    Opening keys expand a closed dropdown. Escape closes it without
    changing the selection. Enter accepts the selected value and closes
    it when ``close_on_select`` is enabled.

    Args:
        keyboard: The keyboard event payload.

    Returns:
        ``True`` when the event was consumed.
    """
    kind = keyboard.kind
    if kind is not None and kind not in ("press", "repeat"):
        return False
    if self.passthrough and keyboard.matches(*self.passthrough):
        return False

    if not self.open:
        if self.open_keys and keyboard.matches(*self.open_keys):
            self.open = True
            self._ensure_enabled_selection()
            return True
        return False

    # Open: escape always dismisses without changing selection.
    if keyboard.matches("escape", "esc"):
        self.open = False
        return True

    if keyboard.matches("enter"):
        # Accept current selection; close only when configured.
        self._ensure_enabled_selection()
        if self.close_on_select:
            self.open = False
        # Bubble so application hooks can read ``value``.
        return False

    if self.close_keys and keyboard.matches(*self.close_keys):
        # Non-enter close keys (e.g. a custom binding) dismiss.
        if not keyboard.matches("enter"):
            self.open = False
            return True

    # While open, reuse Options movement / filter handling.
    return super().handle_keyboard(keyboard)

compose

compose(ctx: 'ComponentRenderContext') -> Any

Compose closed label or open options content.

Parameters:

  • ctx ('ComponentRenderContext') –

    Render-time scope for this paint.

Returns:

  • Closed ( Any ) –

    a single TextBlock row. Open: the same content

  • Any

    tree Options would produce, optionally windowed.

Source code in xnano/components/dropdown.py
def compose(self, ctx: "ComponentRenderContext") -> Any:
    """Compose closed label or open options content.

    Args:
        ctx: Render-time scope for this paint.

    Returns:
        Closed: a single ``TextBlock`` row. Open: the same content
        tree ``Options`` would produce, optionally windowed.
    """
    from xnano.core.content import Stack

    if not self.open:
        return self._compose_closed_row()

    window = self._windowed_visible()
    # Remap selected into the window for Items highlight, then
    # restore — selection state stays global on the component.
    global_selected = self.selected
    local = self._window_selected_offset(window)
    if local is not None:
        self.selected = local
    try:
        items_content = self._compose_items(ctx, visible=window)
    finally:
        self.selected = global_selected

    if not self.searchable:
        return items_content
    return Stack(
        children=(self._compose_query_row(), items_content),
        direction="vertical",
        z=self.z,
        visible=self.visible,
    )

Image dataclass

Image(
    source: ImageSource = "",
    fit: ImageFit = "crop",
    loop: bool = True,
    speed: float = 1.0,
    background: tuple[int, int, int] = (0, 0, 0),
    horizontal_pixels_per_cell: HorizontalPixelsPerCell = 1,
    correct_terminal_aspect: bool = False,
    playing: bool = True,
    position_ms: float | None = None,
    *,
    visible: bool = True,
    z: int = 0,
    fit_content: bool = False
)

Bases: Component

A native-resolution terminal image or real-time GIF component.

Pass an Image to a runtime render for a zero-delay still/first frame. Animated formats play according to their source frame timings when playing is True.

crop is intentionally the default fit: source pixels are never resized. The source and viewport centers align, with oversized content cropped and undersized content padded around the center.

Example

Image(source="logo.png", fit="contain")

Attributes:

Methods:

  • get_frame

    Optional frame/panel chrome around composed content.

  • before_render

    Called before rendering; returns the effective render area.

  • after_render

    Called after rendering for optional post-paint work.

  • compose_extra_small

    Compose content when the viewport is extra small (< 40 cols).

  • compose_small

    Compose content when the viewport is small (40–79 cols).

  • compose_medium

    Compose content when the viewport is medium (80–119 cols).

  • compose_large

    Compose content when the viewport is large (120–159 cols).

  • compose_extra_large

    Compose content when the viewport is extra large (>= 160 cols).

  • handle_keyboard

    Optional keyboard handler while focused.

  • handle_paste

    Optional paste handler while focused.

  • component_post_init

    Validate configuration and decode the initial source.

  • play

    Resume animation from the current playback position.

  • pause

    Freeze animation at the current playback position.

  • restart

    Restart animation timing at the next render.

  • seek

    Move playback to a source timestamp without delaying a render.

  • get_frame_index

    Return the source frame active at an elapsed playback time.

  • get_size

    Return the native terminal cell dimensions of the source.

  • compose

    Compose the current timed frame for the target terminal area.

visible class-attribute instance-attribute

visible: bool = dataclasses.field(
    default=True, kw_only=True
)

Whether this component paints at all.

z class-attribute instance-attribute

z: int = dataclasses.field(default=0, kw_only=True)

Stacking order among sibling content.

focused property

focused: bool

Whether this component currently holds field focus.

source class-attribute instance-attribute

source: ImageSource = ''

Encoded image path, bytes, stream, or decoded image data.

fit class-attribute instance-attribute

fit: ImageFit = 'crop'

Native crop, contain, cover, stretch, or adaptive placement.

loop class-attribute instance-attribute

loop: bool = True

Whether an animation restarts after its final frame.

speed class-attribute instance-attribute

speed: float = 1.0

Playback-rate multiplier.

background class-attribute instance-attribute

background: tuple[int, int, int] = (0, 0, 0)

RGB color behind transparency and centered padding.

horizontal_pixels_per_cell class-attribute instance-attribute

horizontal_pixels_per_cell: HorizontalPixelsPerCell = 1

Adjacent source pixels sampled into each terminal cell.

correct_terminal_aspect class-attribute instance-attribute

correct_terminal_aspect: bool = False

Whether 2x2 sampling preserves physical terminal proportions.

playing class-attribute instance-attribute

playing: bool = True

Whether wall-clock time advances the animation.

position_ms class-attribute instance-attribute

position_ms: float | None = None

Explicit playback position override in milliseconds.

fit_content class-attribute instance-attribute

fit_content: bool = dataclasses.field(
    default=False, kw_only=True
)

Whether layout should use the image's natural cell size.

frame_count property

frame_count: int

Number of decoded still or animation frames.

duration_ms property

duration_ms: int

Total duration of one animation cycle in milliseconds.

finished property

finished: bool

Whether non-looping playback has reached its source duration.

get_frame

get_frame() -> Any | None

Optional frame/panel chrome around composed content.

Source code in xnano/components/component.py
def get_frame(self) -> Any | None:
    """Optional frame/panel chrome around composed content."""
    return None

before_render

before_render(
    ctx: ComponentRenderContext[StateT], area: "Area"
) -> "Area"

Called before rendering; returns the effective render area.

Source code in xnano/components/component.py
def before_render(
    self,
    ctx: ComponentRenderContext[StateT],
    area: "Area",
) -> "Area":
    """Called before rendering; returns the effective render area."""
    return area

after_render

after_render(
    ctx: ComponentRenderContext[StateT], area: "Area"
) -> None

Called after rendering for optional post-paint work.

Source code in xnano/components/component.py
def after_render(
    self,
    ctx: ComponentRenderContext[StateT],
    area: "Area",
) -> None:
    """Called after rendering for optional post-paint work."""
    return None

compose_extra_small

compose_extra_small(
    ctx: ComponentRenderContext[StateT],
) -> Content | None

Compose content when the viewport is extra small (< 40 cols).

Optional responsive counterpart to :meth:compose. When overridden, it is used instead of compose while the window is in this size tier. Overriding any compose_* variant opts the component into breakpoint dispatch; a component that overrides none pays no per-frame cost.

Source code in xnano/components/component.py
@responsive_noop
def compose_extra_small(
    self, ctx: ComponentRenderContext[StateT]
) -> Content | None:
    """Compose content when the viewport is extra small (< 40 cols).

    Optional responsive counterpart to :meth:`compose`. When
    overridden, it is used *instead of* ``compose`` while the window
    is in this size tier. Overriding any ``compose_*`` variant opts the
    component into breakpoint dispatch; a component that overrides none
    pays no per-frame cost.
    """
    return None

compose_small

compose_small(
    ctx: ComponentRenderContext[StateT],
) -> Content | None

Compose content when the viewport is small (40–79 cols).

See :meth:compose_extra_small.

Source code in xnano/components/component.py
@responsive_noop
def compose_small(
    self, ctx: ComponentRenderContext[StateT]
) -> Content | None:
    """Compose content when the viewport is small (40–79 cols).

    See :meth:`compose_extra_small`.
    """
    return None

compose_medium

compose_medium(
    ctx: ComponentRenderContext[StateT],
) -> Content | None

Compose content when the viewport is medium (80–119 cols).

See :meth:compose_extra_small.

Source code in xnano/components/component.py
@responsive_noop
def compose_medium(
    self, ctx: ComponentRenderContext[StateT]
) -> Content | None:
    """Compose content when the viewport is medium (80–119 cols).

    See :meth:`compose_extra_small`.
    """
    return None

compose_large

compose_large(
    ctx: ComponentRenderContext[StateT],
) -> Content | None

Compose content when the viewport is large (120–159 cols).

See :meth:compose_extra_small.

Source code in xnano/components/component.py
@responsive_noop
def compose_large(
    self, ctx: ComponentRenderContext[StateT]
) -> Content | None:
    """Compose content when the viewport is large (120–159 cols).

    See :meth:`compose_extra_small`.
    """
    return None

compose_extra_large

compose_extra_large(
    ctx: ComponentRenderContext[StateT],
) -> Content | None

Compose content when the viewport is extra large (>= 160 cols).

See :meth:compose_extra_small.

Source code in xnano/components/component.py
@responsive_noop
def compose_extra_large(
    self, ctx: ComponentRenderContext[StateT]
) -> Content | None:
    """Compose content when the viewport is extra large (>= 160 cols).

    See :meth:`compose_extra_small`.
    """
    return None

handle_keyboard

handle_keyboard(keyboard: 'KeyboardEventData') -> bool

Optional keyboard handler while focused.

Returns:

  • bool

    True when the event was consumed.

Source code in xnano/components/component.py
def handle_keyboard(self, keyboard: "KeyboardEventData") -> bool:
    """Optional keyboard handler while focused.

    Returns:
        ``True`` when the event was consumed.
    """
    return False

handle_paste

handle_paste(text: str) -> bool

Optional paste handler while focused.

Returns:

  • bool

    True when the paste was consumed.

Source code in xnano/components/component.py
def handle_paste(self, text: str) -> bool:
    """Optional paste handler while focused.

    Returns:
        ``True`` when the paste was consumed.
    """
    return False

component_post_init

component_post_init() -> None

Validate configuration and decode the initial source.

Source code in xnano/components/image.py
def component_post_init(self) -> None:
    """Validate configuration and decode the initial source."""
    self._validate_configuration()
    self._decode_source()

play

play() -> None

Resume animation from the current playback position.

Source code in xnano/components/image.py
def play(self) -> None:
    """Resume animation from the current playback position."""
    if self.playing:
        return
    self.playing = True
    # Preserve paused position across resume.
    self._started_at_ns = time.monotonic_ns() - round(
        self._paused_elapsed_ms * 1_000_000
    )

pause

pause() -> None

Freeze animation at the current playback position.

Source code in xnano/components/image.py
def pause(self) -> None:
    """Freeze animation at the current playback position."""
    if not self.playing:
        return
    self._paused_elapsed_ms = self._get_elapsed_ms()
    self.playing = False

restart

restart() -> None

Restart animation timing at the next render.

Source code in xnano/components/image.py
def restart(self) -> None:
    """Restart animation timing at the next render."""
    self._started_at_ns = None
    self._paused_elapsed_ms = 0.0
    self.position_ms = None

seek

seek(position_ms: float) -> None

Move playback to a source timestamp without delaying a render.

Parameters:

  • position_ms (float) –

    Source animation timestamp in milliseconds.

Source code in xnano/components/image.py
def seek(self, position_ms: float) -> None:
    """Move playback to a source timestamp without delaying a render.

    Args:
        position_ms: Source animation timestamp in milliseconds.
    """
    wall_elapsed_ms = max(0.0, position_ms) / self.speed
    self._paused_elapsed_ms = wall_elapsed_ms
    self.position_ms = None
    if self.playing:
        self._started_at_ns = time.monotonic_ns() - round(
            wall_elapsed_ms * 1_000_000
        )
    else:
        self._started_at_ns = None

get_frame_index

get_frame_index(elapsed_ms: float) -> int

Return the source frame active at an elapsed playback time.

Parameters:

  • elapsed_ms (float) –

    Playback time in milliseconds.

Returns:

  • int

    The zero-based active frame index.

Source code in xnano/components/image.py
def get_frame_index(self, elapsed_ms: float) -> int:
    """Return the source frame active at an elapsed playback time.

    Args:
        elapsed_ms: Playback time in milliseconds.

    Returns:
        The zero-based active frame index.
    """
    self._ensure_decoded()
    if len(self._frames) == 1:
        return 0
    position = max(0, int(elapsed_ms * self.speed))
    if self.loop:
        position %= self._duration_ms
    else:
        position = min(position, self._duration_ms - 1)
    for frame_index, frame_end in enumerate(self._frame_ends_ms):
        if position < frame_end:
            return frame_index
    return len(self._frames) - 1

get_size

get_size(ctx: ComponentRenderContext) -> Size

Return the native terminal cell dimensions of the source.

Source code in xnano/components/image.py
def get_size(self, ctx: ComponentRenderContext) -> Size:
    """Return the native terminal cell dimensions of the source."""
    self._ensure_decoded()
    frame = self._frames[0]
    if self.correct_terminal_aspect:
        width = frame.width
    else:
        width = math.ceil(frame.width / self.horizontal_pixels_per_cell)
    return Size(
        width=width,
        height=math.ceil(frame.height / 2),
    )

compose

Compose the current timed frame for the target terminal area.

Source code in xnano/components/image.py
def compose(self, ctx: ComponentRenderContext) -> CellCanvas:
    """Compose the current timed frame for the target terminal area."""
    self._ensure_decoded()
    elapsed_ms = self._get_elapsed_ms()
    if self.playing:
        self._paused_elapsed_ms = elapsed_ms
    frame_index = self.get_frame_index(elapsed_ms)
    source = self._frames[frame_index]
    if self.correct_terminal_aspect:
        native_width = source.width
    else:
        native_width = math.ceil(
            source.width / self.horizontal_pixels_per_cell
        )
    width = max(1, ctx.area.width or native_width)
    height = max(1, ctx.area.height or math.ceil(source.height / 2))
    cache_key = (
        frame_index,
        width,
        height,
        self.fit,
        self.horizontal_pixels_per_cell,
        self.correct_terminal_aspect,
    )
    if cache_key == self._cached_key and self._cached_canvas is not None:
        return self._cached_canvas
    fitted_width = width
    if not self.correct_terminal_aspect:
        fitted_width *= self.horizontal_pixels_per_cell
    fitted = _fit_frame(
        source,
        fitted_width,
        height * 2,
        self.fit,
        self.background,
    )
    if (
        self.correct_terminal_aspect
        and self.horizontal_pixels_per_cell == 2
    ):
        fitted = _resize_frame(
            fitted,
            fitted.width * 2,
            fitted.height,
        )
    canvas = _get_frame_as_canvas(
        fitted,
        self.horizontal_pixels_per_cell,
    )
    result = CellCanvas(
        width=canvas.width,
        height=canvas.height,
        rows=canvas.rows,
        z=self.z,
        visible=self.visible,
    )
    self._cached_key = cache_key
    self._cached_canvas = result
    return result

ImageData dataclass

ImageData(
    width: int, height: int, frames: tuple[ImageFrame, ...]
)

Decoded image frames independent of Pillow and the render engine.

Attributes:

Methods:

  • from_bytes

    Decode xnano's compact, Pillow-free animation container.

width instance-attribute

width: int

Native pixel width shared by every frame.

height instance-attribute

height: int

Native pixel height shared by every frame.

frames instance-attribute

frames: tuple[ImageFrame, ...]

RGB frames in playback order.

from_bytes classmethod

from_bytes(data: bytes) -> ImageData

Decode xnano's compact, Pillow-free animation container.

Parameters:

  • data (bytes) –

    Bytes produced by scripts/precompute_demo_image.py.

Returns:

  • ImageData

    Decoded full-resolution RGB image data.

Source code in xnano/components/image.py
@classmethod
def from_bytes(cls, data: bytes) -> ImageData:
    """Decode xnano's compact, Pillow-free animation container.

    Args:
        data: Bytes produced by ``scripts/precompute_demo_image.py``.

    Returns:
        Decoded full-resolution RGB image data.
    """
    if data[:4] != b"XNI1":
        raise ValueError("Image data does not have an XNI1 header.")
    try:
        width, height, frame_count = struct.unpack_from(">HHH", data, 4)
        offset = 10
        frames: list[ImageFrame] = []
        pixel_count = width * height
        for _ in range(frame_count):
            duration_ms, color_count, compressed_size = struct.unpack_from(
                ">HHI", data, offset
            )
            offset += 8
            palette_size = color_count * 3
            palette = data[offset : offset + palette_size]
            offset += palette_size
            compressed = data[offset : offset + compressed_size]
            offset += compressed_size
            indexes = zlib.decompress(compressed)
            if len(indexes) != pixel_count:
                raise ValueError("Image frame has an invalid pixel count.")
            pixels = bytearray(pixel_count * 3)
            for pixel_index, color_index in enumerate(indexes):
                palette_offset = color_index * 3
                pixel_offset = pixel_index * 3
                pixels[pixel_offset : pixel_offset + 3] = palette[
                    palette_offset : palette_offset + 3
                ]
            frames.append(ImageFrame(bytes(pixels), max(10, duration_ms)))
    except (struct.error, zlib.error) as error:
        raise ValueError("Image data is truncated or corrupt.") from error
    if offset != len(data):
        raise ValueError("Image data contains unexpected trailing bytes.")
    return cls(width=width, height=height, frames=tuple(frames))

ImageFrame dataclass

ImageFrame(pixels: bytes, duration_ms: int = 100)

One native-resolution RGB frame and its display duration.

Attributes:

pixels instance-attribute

pixels: bytes

Packed row-major RGB bytes.

duration_ms class-attribute instance-attribute

duration_ms: int = 100

Display duration in milliseconds.

Input dataclass

Input(
    content: str | Text | list[str | Text] = "",
    foreground: ColorLike | None = None,
    background: ColorLike | None = None,
    modifiers: tuple[CharacterModifier, ...] = (),
    horizontal_align: Alignment | None = None,
    vertical_align: VerticalAlignment | None = None,
    wrap: bool = True,
    input: bool = False,
    placeholder: str | Text | None = None,
    cursor: int | None = None,
    multiline: bool = False,
    rows: int | None = None,
    ansi: bool = False,
    markdown: bool = False,
    language: str | None = None,
    passthrough: Sequence[str] = (),
    mask: str | None = None,
    max_length: int | None = None,
    read_only: bool = False,
    tab_size: int = 4,
    focusable: bool = False,
    fill: bool = False,
    submit_keys: Sequence[str] = ("enter",),
    auto_height: bool = False,
    min_rows: int = 1,
    max_rows: int | None = None,
    *,
    visible: bool = True,
    z: int = 0,
    fit_content: bool = True
)

Bases: Text

Editable text field.

Input is single-line by default. Set multiline=True for an editor that supports line breaks, selection, and navigation. Set auto_height=True for a composer that grows as the text soft-wraps, bounded by min_rows and max_rows — pair it with a Field(height="fit") slot so layout consumes the reported height.

Example

Input(placeholder="Search", submit_keys=("enter",))

Attributes:

  • submit_keys (Sequence[str]) –

    Keys reserved for submit hooks instead of text editing.

  • auto_height (bool) –

    Grow the reported height to fit soft-wrapped content.

  • min_rows (int) –

    Minimum reported height when auto_height is set.

  • max_rows (int | None) –

    Maximum reported height when auto_height is set.

Methods:

  • get_frame

    Optional frame/panel chrome around composed content.

  • before_render

    Called before rendering; returns the effective render area.

  • after_render

    Record the multi-line editor caret so the terminal cursor tracks it.

  • compose

    Compose interface-neutral content for this Text.

  • compose_extra_small

    Compose content when the viewport is extra small (< 40 cols).

  • compose_small

    Compose content when the viewport is small (40–79 cols).

  • compose_medium

    Compose content when the viewport is medium (80–119 cols).

  • compose_large

    Compose content when the viewport is large (120–159 cols).

  • compose_extra_large

    Compose content when the viewport is extra large (>= 160 cols).

  • handle_keyboard

    Edit this text when it has focus.

  • handle_paste

    Insert pasted text at the caret of a multi-line editor.

  • get_terminal_node

    Return composed content for terminal compatibility.

  • component_post_init

    Force input mode, then run Text editor setup.

  • get_size

    Report the preferred cell size, growing with content when asked.

visible class-attribute instance-attribute

visible: bool = dataclasses.field(
    default=True, kw_only=True
)

Whether this component paints at all.

z class-attribute instance-attribute

z: int = dataclasses.field(default=0, kw_only=True)

Stacking order among sibling content.

fit_content class-attribute instance-attribute

fit_content: bool = dataclasses.field(
    default=True, kw_only=True
)

When True, paint at natural size inside a larger slot.

focused property

focused: bool

Whether this component currently holds field focus.

content class-attribute instance-attribute

content: str | Text | list[str | Text] = dataclasses.field(
    default=""
)

Plain string, nested Text, or a list of either.

foreground class-attribute instance-attribute

foreground: ColorLike | None = None

Foreground color (deprecated alias: color).

background class-attribute instance-attribute

background: ColorLike | None = None

Background color.

modifiers class-attribute instance-attribute

modifiers: tuple[CharacterModifier, ...] = ()

Character modifiers such as bold or underline.

horizontal_align class-attribute instance-attribute

horizontal_align: Alignment | None = None

Horizontal alignment at the paragraph level.

vertical_align class-attribute instance-attribute

vertical_align: VerticalAlignment | None = None

Vertical alignment within the painted area.

wrap class-attribute instance-attribute

wrap: bool = True

Whether long lines may wrap.

input class-attribute instance-attribute

input: bool = False

When True on a leaf, the component is an editable field.

placeholder class-attribute instance-attribute

placeholder: str | Text | None = None

Shown when input is empty and unfocused.

cursor class-attribute instance-attribute

cursor: int | None = None

Caret index for single-line input; None means end of string.

multiline class-attribute instance-attribute

multiline: bool = False

When True with input, editing uses CoreTextEditor.

rows class-attribute instance-attribute

rows: int | None = None

Preferred visible height (lines) for a multiline input.

ansi class-attribute instance-attribute

ansi: bool = False

Parse ANSI SGR sequences in leaf content.

markdown class-attribute instance-attribute

markdown: bool = False

Parse leaf content as markdown.

language class-attribute instance-attribute

language: str | None = None

Pygments lexer name for syntax highlighting only.

passthrough class-attribute instance-attribute

passthrough: Sequence[str] = ()

Key bindings this input never captures while focused.

mask class-attribute instance-attribute

mask: str | None = None

Display-only mask character(s); real value is preserved.

max_length class-attribute instance-attribute

max_length: int | None = None

Maximum plain-string length; longer input is clamped.

read_only class-attribute instance-attribute

read_only: bool = False

Reject edits while remaining focusable.

tab_size class-attribute instance-attribute

tab_size: int = 4

Tab width applied to multiline tab insertion and paste.

focusable class-attribute instance-attribute

focusable: bool = False

Whether this component participates in field focus.

fill class-attribute instance-attribute

fill: bool = False

Whether background fills the whole slot rather than only the text glyphs. When True short lines still paint the background across the full cell width (no manual right-padding required).

owns_cursor property

owns_cursor: bool

Whether this Text paints its own caret (multi-line editor).

cursor_position property

cursor_position: tuple[int, int] | None

Absolute caret cell for the terminal cursor, set during paint.

Reported only for a focused multi-line editor (a single-line input paints its own caret inline). None otherwise, which hides the hardware cursor.

value property writable

value: str

Canonical plain-string content for leaf and input modes.

submit_keys class-attribute instance-attribute

submit_keys: Sequence[str] = ('enter',)

Read-only convenience for hook matching; not consumed here.

auto_height class-attribute instance-attribute

auto_height: bool = False

Report a preferred height that grows with soft-wrapped content.

Works with a Field(height="fit") slot: the input measures how many rows its value occupies at the available width and reports that height, clamped to [min_rows, max_rows] — no manual grid_set_field loop.

min_rows class-attribute instance-attribute

min_rows: int = 1

Smallest reported height (rows) while auto_height is set.

max_rows class-attribute instance-attribute

max_rows: int | None = None

Largest reported height (rows) while auto_height is set, or None for unbounded growth.

get_frame

get_frame() -> Any | None

Optional frame/panel chrome around composed content.

Source code in xnano/components/component.py
def get_frame(self) -> Any | None:
    """Optional frame/panel chrome around composed content."""
    return None

before_render

before_render(
    ctx: ComponentRenderContext[StateT], area: "Area"
) -> "Area"

Called before rendering; returns the effective render area.

Source code in xnano/components/component.py
def before_render(
    self,
    ctx: ComponentRenderContext[StateT],
    area: "Area",
) -> "Area":
    """Called before rendering; returns the effective render area."""
    return area

after_render

after_render(
    ctx: "ComponentRenderContext[Any]", area: "Area"
) -> None

Record the multi-line editor caret so the terminal cursor tracks it.

Source code in xnano/components/text.py
def after_render(
    self,
    ctx: "ComponentRenderContext[Any]",
    area: "Area",
) -> None:
    """Record the multi-line editor caret so the terminal cursor tracks it."""
    if not (self._input_focused and self._editor is not None):
        self._cursor_position = None
        return
    row, column = self._editor.cursor()
    # ponytail: no soft-wrap/scroll accounting; clamps to the slot, which
    # is exact for a note-sized editor. Add offsets if the body scrolls.
    x = area.x + max(0, min(column, max(0, area.width - 1)))
    y = area.y + max(0, min(row, max(0, area.height - 1)))
    self._cursor_position = (x, y)

compose

compose(
    ctx: ComponentRenderContext[Any],
) -> TextBlock | Native | Panel | None

Compose interface-neutral content for this Text.

Parameters:

Returns:

Source code in xnano/components/text.py
def compose(
    self, ctx: ComponentRenderContext[Any]
) -> TextBlock | Native | Panel | None:
    """Compose interface-neutral content for this Text.

    Args:
        ctx: Render-time scope for this paint.

    Returns:
        A ``TextBlock``, ``Native`` editor payload, or nested content.
        When ``fill`` is set the block is wrapped in a background
        ``Panel`` so the color spans the full slot.
    """
    content = self._compose_content(ctx)
    if (
        self.fill
        and self.background is not None
        and isinstance(content, TextBlock)
    ):
        return Panel(child=content, background=self.background)
    return content

compose_extra_small

compose_extra_small(
    ctx: ComponentRenderContext[StateT],
) -> Content | None

Compose content when the viewport is extra small (< 40 cols).

Optional responsive counterpart to :meth:compose. When overridden, it is used instead of compose while the window is in this size tier. Overriding any compose_* variant opts the component into breakpoint dispatch; a component that overrides none pays no per-frame cost.

Source code in xnano/components/component.py
@responsive_noop
def compose_extra_small(
    self, ctx: ComponentRenderContext[StateT]
) -> Content | None:
    """Compose content when the viewport is extra small (< 40 cols).

    Optional responsive counterpart to :meth:`compose`. When
    overridden, it is used *instead of* ``compose`` while the window
    is in this size tier. Overriding any ``compose_*`` variant opts the
    component into breakpoint dispatch; a component that overrides none
    pays no per-frame cost.
    """
    return None

compose_small

compose_small(
    ctx: ComponentRenderContext[StateT],
) -> Content | None

Compose content when the viewport is small (40–79 cols).

See :meth:compose_extra_small.

Source code in xnano/components/component.py
@responsive_noop
def compose_small(
    self, ctx: ComponentRenderContext[StateT]
) -> Content | None:
    """Compose content when the viewport is small (40–79 cols).

    See :meth:`compose_extra_small`.
    """
    return None

compose_medium

compose_medium(
    ctx: ComponentRenderContext[StateT],
) -> Content | None

Compose content when the viewport is medium (80–119 cols).

See :meth:compose_extra_small.

Source code in xnano/components/component.py
@responsive_noop
def compose_medium(
    self, ctx: ComponentRenderContext[StateT]
) -> Content | None:
    """Compose content when the viewport is medium (80–119 cols).

    See :meth:`compose_extra_small`.
    """
    return None

compose_large

compose_large(
    ctx: ComponentRenderContext[StateT],
) -> Content | None

Compose content when the viewport is large (120–159 cols).

See :meth:compose_extra_small.

Source code in xnano/components/component.py
@responsive_noop
def compose_large(
    self, ctx: ComponentRenderContext[StateT]
) -> Content | None:
    """Compose content when the viewport is large (120–159 cols).

    See :meth:`compose_extra_small`.
    """
    return None

compose_extra_large

compose_extra_large(
    ctx: ComponentRenderContext[StateT],
) -> Content | None

Compose content when the viewport is extra large (>= 160 cols).

See :meth:compose_extra_small.

Source code in xnano/components/component.py
@responsive_noop
def compose_extra_large(
    self, ctx: ComponentRenderContext[StateT]
) -> Content | None:
    """Compose content when the viewport is extra large (>= 160 cols).

    See :meth:`compose_extra_small`.
    """
    return None

handle_keyboard

handle_keyboard(keyboard: 'KeyboardEventData') -> bool

Edit this text when it has focus.

Passthrough bindings remain available to hooks. Read-only inputs reject edits, and max_length limits inserted content.

Parameters:

  • keyboard ('KeyboardEventData') –

    The keyboard event payload.

Returns:

  • bool

    True when the key was consumed as text editing.

Source code in xnano/components/text.py
def handle_keyboard(self, keyboard: "KeyboardEventData") -> bool:
    """Edit this text when it has focus.

    Passthrough bindings remain available to hooks. Read-only inputs reject
    edits, and ``max_length`` limits inserted content.

    Args:
        keyboard: The keyboard event payload.

    Returns:
        ``True`` when the key was consumed as text editing.
    """
    if self.passthrough and keyboard.matches(*self.passthrough):
        return False
    if not self.input:
        return False

    if self._editor is not None:
        return self._handle_editor_keyboard(keyboard)
    return self._handle_single_line_keyboard(keyboard)

handle_paste

handle_paste(text: str) -> bool

Insert pasted text at the caret of a multi-line editor.

Parameters:

  • text (str) –

    The pasted clipboard text.

Returns:

  • bool

    True when the paste was consumed by the native editor.

Source code in xnano/components/text.py
def handle_paste(self, text: str) -> bool:
    """Insert pasted text at the caret of a multi-line editor.

    Args:
        text: The pasted clipboard text.

    Returns:
        ``True`` when the paste was consumed by the native editor.
    """
    if self._editor is None:
        return False
    if self.read_only:
        return True
    text = self._clamp_text(text)
    if self.max_length is not None:
        remaining = self.max_length - len(self._editor.text())
        if remaining <= 0:
            return True
        text = text[:remaining]
    self._editor.insert_text(text)
    object.__setattr__(self, "content", self._editor.text())
    return True

get_terminal_node

get_terminal_node(
    ctx: ComponentRenderContext[Any],
) -> TextBlock | Native | Panel | None

Return composed content for terminal compatibility.

Parameters:

Returns:

Source code in xnano/components/text.py
def get_terminal_node(
    self, ctx: ComponentRenderContext[Any]
) -> TextBlock | Native | Panel | None:
    """Return composed content for terminal compatibility.

    Args:
        ctx: Render-time scope for this paint.

    Returns:
        The same interface-neutral content as ``compose``.
    """
    return self.compose(ctx)

component_post_init

component_post_init() -> None

Force input mode, then run Text editor setup.

Source code in xnano/components/input.py
def component_post_init(self) -> None:
    """Force input mode, then run ``Text`` editor setup."""
    self.input = True
    super().component_post_init()

get_size

get_size(ctx: 'ComponentRenderContext') -> Size

Report the preferred cell size, growing with content when asked.

Source code in xnano/components/input.py
def get_size(self, ctx: "ComponentRenderContext") -> Size:
    """Report the preferred cell size, growing with content when asked."""
    text = self.value
    width = ctx.area.width
    if self.auto_height:
        rows = self._wrapped_row_count(text, width)
        rows = max(self.min_rows, rows)
        if self.max_rows is not None:
            rows = min(self.max_rows, rows)
    else:
        rows = self.rows if self.rows is not None else text.count("\n") + 1
    preferred_width = width or max(
        (len(line) for line in text.split("\n")), default=0
    )
    return Size(width=preferred_width, height=rows)
Link(
    content: str | Text | list[str | Text] = "",
    foreground: ColorLike | None = "blue",
    background: ColorLike | None = None,
    modifiers: tuple[CharacterModifier, ...] = (),
    horizontal_align: Alignment | None = None,
    vertical_align: VerticalAlignment | None = None,
    wrap: bool = True,
    input: bool = False,
    placeholder: str | Text | None = None,
    cursor: int | None = None,
    multiline: bool = False,
    rows: int | None = None,
    ansi: bool = False,
    markdown: bool = False,
    language: str | None = None,
    passthrough: Sequence[str] = (),
    mask: str | None = None,
    max_length: int | None = None,
    read_only: bool = False,
    tab_size: int = 4,
    focusable: bool = True,
    fill: bool = False,
    url: str = "",
    underline: bool = True,
    focused_color: ColorLike | None = None,
    visited: bool = False,
    *,
    visible: bool = True,
    z: int = 0,
    fit_content: bool = True
)

Bases: Text

Focusable hyperlink label.

Links are underlined and blue by default. Handle an activation key in a grid hook to open, copy, or otherwise use url.

Example

Link(content="Documentation", url="https://example.com/docs")

Attributes:

Methods:

  • get_frame

    Optional frame/panel chrome around composed content.

  • get_size

    Return the preferred cell size of this component.

  • before_render

    Called before rendering; returns the effective render area.

  • after_render

    Record the multi-line editor caret so the terminal cursor tracks it.

  • compose_extra_small

    Compose content when the viewport is extra small (< 40 cols).

  • compose_small

    Compose content when the viewport is small (40–79 cols).

  • compose_medium

    Compose content when the viewport is medium (80–119 cols).

  • compose_large

    Compose content when the viewport is large (120–159 cols).

  • compose_extra_large

    Compose content when the viewport is extra large (>= 160 cols).

  • handle_paste

    Insert pasted text at the caret of a multi-line editor.

  • get_terminal_node

    Return composed content for terminal compatibility.

  • component_post_init

    Validate modes without forcing input=True.

  • compose

    Compose underlined, focus-aware link content.

  • handle_keyboard

    Leave activation keys for application hooks.

visible class-attribute instance-attribute

visible: bool = dataclasses.field(
    default=True, kw_only=True
)

Whether this component paints at all.

z class-attribute instance-attribute

z: int = dataclasses.field(default=0, kw_only=True)

Stacking order among sibling content.

fit_content class-attribute instance-attribute

fit_content: bool = dataclasses.field(
    default=True, kw_only=True
)

When True, paint at natural size inside a larger slot.

focused property

focused: bool

Whether this component currently holds field focus.

content class-attribute instance-attribute

content: str | Text | list[str | Text] = dataclasses.field(
    default=""
)

Plain string, nested Text, or a list of either.

background class-attribute instance-attribute

background: ColorLike | None = None

Background color.

modifiers class-attribute instance-attribute

modifiers: tuple[CharacterModifier, ...] = ()

Character modifiers such as bold or underline.

horizontal_align class-attribute instance-attribute

horizontal_align: Alignment | None = None

Horizontal alignment at the paragraph level.

vertical_align class-attribute instance-attribute

vertical_align: VerticalAlignment | None = None

Vertical alignment within the painted area.

wrap class-attribute instance-attribute

wrap: bool = True

Whether long lines may wrap.

input class-attribute instance-attribute

input: bool = False

When True on a leaf, the component is an editable field.

placeholder class-attribute instance-attribute

placeholder: str | Text | None = None

Shown when input is empty and unfocused.

cursor class-attribute instance-attribute

cursor: int | None = None

Caret index for single-line input; None means end of string.

multiline class-attribute instance-attribute

multiline: bool = False

When True with input, editing uses CoreTextEditor.

rows class-attribute instance-attribute

rows: int | None = None

Preferred visible height (lines) for a multiline input.

ansi class-attribute instance-attribute

ansi: bool = False

Parse ANSI SGR sequences in leaf content.

markdown class-attribute instance-attribute

markdown: bool = False

Parse leaf content as markdown.

language class-attribute instance-attribute

language: str | None = None

Pygments lexer name for syntax highlighting only.

passthrough class-attribute instance-attribute

passthrough: Sequence[str] = ()

Key bindings this input never captures while focused.

mask class-attribute instance-attribute

mask: str | None = None

Display-only mask character(s); real value is preserved.

max_length class-attribute instance-attribute

max_length: int | None = None

Maximum plain-string length; longer input is clamped.

read_only class-attribute instance-attribute

read_only: bool = False

Reject edits while remaining focusable.

tab_size class-attribute instance-attribute

tab_size: int = 4

Tab width applied to multiline tab insertion and paste.

fill class-attribute instance-attribute

fill: bool = False

Whether background fills the whole slot rather than only the text glyphs. When True short lines still paint the background across the full cell width (no manual right-padding required).

owns_cursor property

owns_cursor: bool

Whether this Text paints its own caret (multi-line editor).

cursor_position property

cursor_position: tuple[int, int] | None

Absolute caret cell for the terminal cursor, set during paint.

Reported only for a focused multi-line editor (a single-line input paints its own caret inline). None otherwise, which hides the hardware cursor.

url class-attribute instance-attribute

url: str = ''

Destination address exposed to hooks; never auto-opened.

underline class-attribute instance-attribute

underline: bool = True

When True, compose with the underline modifier.

foreground class-attribute instance-attribute

foreground: ColorLike | None = 'blue'

Default link foreground color.

focused_color class-attribute instance-attribute

focused_color: ColorLike | None = None

Foreground override while this link holds focus.

visited class-attribute instance-attribute

visited: bool = False

Application-maintained visited flag.

focusable class-attribute instance-attribute

focusable: bool = True

Links participate in field focus by default.

value property writable

value: str

Plain label text, falling back to url when empty.

get_frame

get_frame() -> Any | None

Optional frame/panel chrome around composed content.

Source code in xnano/components/component.py
def get_frame(self) -> Any | None:
    """Optional frame/panel chrome around composed content."""
    return None

get_size

get_size(ctx: ComponentRenderContext[StateT]) -> Size

Return the preferred cell size of this component.

Source code in xnano/components/component.py
def get_size(self, ctx: ComponentRenderContext[StateT]) -> Size:
    """Return the preferred cell size of this component."""
    return Size(width=0, height=0)

before_render

before_render(
    ctx: ComponentRenderContext[StateT], area: "Area"
) -> "Area"

Called before rendering; returns the effective render area.

Source code in xnano/components/component.py
def before_render(
    self,
    ctx: ComponentRenderContext[StateT],
    area: "Area",
) -> "Area":
    """Called before rendering; returns the effective render area."""
    return area

after_render

after_render(
    ctx: "ComponentRenderContext[Any]", area: "Area"
) -> None

Record the multi-line editor caret so the terminal cursor tracks it.

Source code in xnano/components/text.py
def after_render(
    self,
    ctx: "ComponentRenderContext[Any]",
    area: "Area",
) -> None:
    """Record the multi-line editor caret so the terminal cursor tracks it."""
    if not (self._input_focused and self._editor is not None):
        self._cursor_position = None
        return
    row, column = self._editor.cursor()
    # ponytail: no soft-wrap/scroll accounting; clamps to the slot, which
    # is exact for a note-sized editor. Add offsets if the body scrolls.
    x = area.x + max(0, min(column, max(0, area.width - 1)))
    y = area.y + max(0, min(row, max(0, area.height - 1)))
    self._cursor_position = (x, y)

compose_extra_small

compose_extra_small(
    ctx: ComponentRenderContext[StateT],
) -> Content | None

Compose content when the viewport is extra small (< 40 cols).

Optional responsive counterpart to :meth:compose. When overridden, it is used instead of compose while the window is in this size tier. Overriding any compose_* variant opts the component into breakpoint dispatch; a component that overrides none pays no per-frame cost.

Source code in xnano/components/component.py
@responsive_noop
def compose_extra_small(
    self, ctx: ComponentRenderContext[StateT]
) -> Content | None:
    """Compose content when the viewport is extra small (< 40 cols).

    Optional responsive counterpart to :meth:`compose`. When
    overridden, it is used *instead of* ``compose`` while the window
    is in this size tier. Overriding any ``compose_*`` variant opts the
    component into breakpoint dispatch; a component that overrides none
    pays no per-frame cost.
    """
    return None

compose_small

compose_small(
    ctx: ComponentRenderContext[StateT],
) -> Content | None

Compose content when the viewport is small (40–79 cols).

See :meth:compose_extra_small.

Source code in xnano/components/component.py
@responsive_noop
def compose_small(
    self, ctx: ComponentRenderContext[StateT]
) -> Content | None:
    """Compose content when the viewport is small (40–79 cols).

    See :meth:`compose_extra_small`.
    """
    return None

compose_medium

compose_medium(
    ctx: ComponentRenderContext[StateT],
) -> Content | None

Compose content when the viewport is medium (80–119 cols).

See :meth:compose_extra_small.

Source code in xnano/components/component.py
@responsive_noop
def compose_medium(
    self, ctx: ComponentRenderContext[StateT]
) -> Content | None:
    """Compose content when the viewport is medium (80–119 cols).

    See :meth:`compose_extra_small`.
    """
    return None

compose_large

compose_large(
    ctx: ComponentRenderContext[StateT],
) -> Content | None

Compose content when the viewport is large (120–159 cols).

See :meth:compose_extra_small.

Source code in xnano/components/component.py
@responsive_noop
def compose_large(
    self, ctx: ComponentRenderContext[StateT]
) -> Content | None:
    """Compose content when the viewport is large (120–159 cols).

    See :meth:`compose_extra_small`.
    """
    return None

compose_extra_large

compose_extra_large(
    ctx: ComponentRenderContext[StateT],
) -> Content | None

Compose content when the viewport is extra large (>= 160 cols).

See :meth:compose_extra_small.

Source code in xnano/components/component.py
@responsive_noop
def compose_extra_large(
    self, ctx: ComponentRenderContext[StateT]
) -> Content | None:
    """Compose content when the viewport is extra large (>= 160 cols).

    See :meth:`compose_extra_small`.
    """
    return None

handle_paste

handle_paste(text: str) -> bool

Insert pasted text at the caret of a multi-line editor.

Parameters:

  • text (str) –

    The pasted clipboard text.

Returns:

  • bool

    True when the paste was consumed by the native editor.

Source code in xnano/components/text.py
def handle_paste(self, text: str) -> bool:
    """Insert pasted text at the caret of a multi-line editor.

    Args:
        text: The pasted clipboard text.

    Returns:
        ``True`` when the paste was consumed by the native editor.
    """
    if self._editor is None:
        return False
    if self.read_only:
        return True
    text = self._clamp_text(text)
    if self.max_length is not None:
        remaining = self.max_length - len(self._editor.text())
        if remaining <= 0:
            return True
        text = text[:remaining]
    self._editor.insert_text(text)
    object.__setattr__(self, "content", self._editor.text())
    return True

get_terminal_node

get_terminal_node(
    ctx: ComponentRenderContext[Any],
) -> TextBlock | Native | Panel | None

Return composed content for terminal compatibility.

Parameters:

Returns:

Source code in xnano/components/text.py
def get_terminal_node(
    self, ctx: ComponentRenderContext[Any]
) -> TextBlock | Native | Panel | None:
    """Return composed content for terminal compatibility.

    Args:
        ctx: Render-time scope for this paint.

    Returns:
        The same interface-neutral content as ``compose``.
    """
    return self.compose(ctx)

component_post_init

component_post_init() -> None

Validate modes without forcing input=True.

Source code in xnano/components/link.py
def component_post_init(self) -> None:
    """Validate modes without forcing ``input=True``."""
    super().component_post_init()

compose

compose(
    ctx: ComponentRenderContext[Any],
) -> TextBlock | Any

Compose underlined, focus-aware link content.

Parameters:

Returns:

  • TextBlock | Any

    Styled TextBlock (or nested content) for this link.

Source code in xnano/components/link.py
def compose(self, ctx: ComponentRenderContext[Any]) -> TextBlock | Any:
    """Compose underlined, focus-aware link content.

    Args:
        ctx: Render-time scope for this paint.

    Returns:
        Styled ``TextBlock`` (or nested content) for this link.
    """
    modifiers = tuple(self.modifiers)
    if self.underline and "underline" not in modifiers:
        modifiers = modifiers + ("underline",)

    color = self.foreground
    if self.focused and self.focused_color is not None:
        color = self.focused_color
    elif self.visited and self.focused_color is None:
        # Mild visited cue when no focused override is set.
        if color == "blue":
            color = "magenta"

    # Temporarily apply compose styles without mutating live state
    # beyond the frame: build a plain block from the label.
    if isinstance(self.content, str):
        label = self.content if self.content else self.url
        return TextBlock.from_plain(
            label,
            foreground=color,
            background=self.background,
            modifiers=modifiers,
            horizontal_align=self.horizontal_align,
            vertical_align=self.vertical_align,
            wrap=self.wrap,
            z=self.z,
            visible=self.visible,
        )

    # Nested content: style via a short-lived attribute swap.
    previous_modifiers = self.modifiers
    previous_color = self.foreground
    object.__setattr__(self, "modifiers", modifiers)
    object.__setattr__(self, "foreground", color)
    try:
        return Text.compose(self, ctx)
    finally:
        object.__setattr__(self, "modifiers", previous_modifiers)
        object.__setattr__(self, "foreground", previous_color)

handle_keyboard

handle_keyboard(keyboard: 'KeyboardEventData') -> bool

Leave activation keys for application hooks.

Parameters:

  • keyboard ('KeyboardEventData') –

    The keyboard event payload.

Returns:

  • bool

    Always False so enter/space and other keys bubble.

Source code in xnano/components/link.py
def handle_keyboard(self, keyboard: "KeyboardEventData") -> bool:
    """Leave activation keys for application hooks.

    Args:
        keyboard: The keyboard event payload.

    Returns:
        Always ``False`` so enter/space and other keys bubble.
    """
    if self.passthrough and keyboard.matches(*self.passthrough):
        return False
    # Never consume activation or editing keys on a link.
    return False

Loader dataclass

Loader(
    value: float | None = None,
    total: float | None = None,
    style: LoaderStyle = "spinner",
    label: LoaderLabel = None,
    symbols: LoaderSymbols = "dots",
    interval: int = 80,
    foreground: "ColorLike" = "green",
    background: "ColorLike | None" = None,
    filled_color: "ColorLike | None" = None,
    unfilled_color: "ColorLike | None" = None,
    running: bool = True,
    *,
    visible: bool = True,
    z: int = 0,
    fit_content: bool = False
)

Bases: Component

Determinate progress indicator or indeterminate spinner.

Leave value as None to show a spinner. Set value to a ratio, or pair it with total, to show measurable progress.

Example

Loader(value=42, total=100, label=True)

Attributes:

  • value (float | None) –

    Current amount, ratio, or None for indeterminate.

  • total (float | None) –

    When set, ratio is value / total.

  • style (LoaderStyle) –

    "spinner", "bar", or "line".

  • label (LoaderLabel) –

    Overlay text, auto percentage, nested Text, or hidden.

  • symbols (LoaderSymbols) –

    Spinner frames or a named preset.

  • interval (int) –

    Milliseconds between spinner frames.

  • foreground ('ColorLike') –

    Primary foreground / filled color (deprecated alias: color).

  • background ('ColorLike | None') –

    Widget background color.

  • filled_color ('ColorLike | None') –

    Line style filled-portion color.

  • unfilled_color ('ColorLike | None') –

    Line style unfilled-portion color.

  • running (bool) –

    Whether spinner animation advances.

Methods:

  • get_frame

    Optional frame/panel chrome around composed content.

  • get_size

    Return the preferred cell size of this component.

  • before_render

    Called before rendering; returns the effective render area.

  • after_render

    Called after rendering for optional post-paint work.

  • compose_extra_small

    Compose content when the viewport is extra small (< 40 cols).

  • compose_small

    Compose content when the viewport is small (40–79 cols).

  • compose_medium

    Compose content when the viewport is medium (80–119 cols).

  • compose_large

    Compose content when the viewport is large (120–159 cols).

  • compose_extra_large

    Compose content when the viewport is extra large (>= 160 cols).

  • handle_keyboard

    Optional keyboard handler while focused.

  • handle_paste

    Optional paste handler while focused.

  • component_post_init

    Resolve spinner frames and start the animation epoch.

  • restart

    Reset the spinner epoch to the current monotonic time.

  • current_frame

    Return the active spinner glyph, advanced by the framework clock.

  • inline_text

    Return the spinner glyph plus its label for inline embedding.

  • compose

    Compose spinner text or gauge content for this loader.

visible class-attribute instance-attribute

visible: bool = dataclasses.field(
    default=True, kw_only=True
)

Whether this component paints at all.

z class-attribute instance-attribute

z: int = dataclasses.field(default=0, kw_only=True)

Stacking order among sibling content.

focused property

focused: bool

Whether this component currently holds field focus.

value class-attribute instance-attribute

value: float | None = None

Current amount, ratio, or None for indeterminate work.

total class-attribute instance-attribute

total: float | None = None

When set, the ratio is value / total.

style class-attribute instance-attribute

style: LoaderStyle = 'spinner'

"spinner", block "bar", or thin "line" gauge.

label class-attribute instance-attribute

label: LoaderLabel = None

Overlay text. None auto-derives a percentage when determinate.

symbols class-attribute instance-attribute

symbols: LoaderSymbols = 'dots'

Spinner frames or a named preset.

interval class-attribute instance-attribute

interval: int = 80

Milliseconds between spinner frames.

foreground class-attribute instance-attribute

foreground: 'ColorLike' = 'green'

Primary foreground / filled color (deprecated alias: color).

background class-attribute instance-attribute

background: 'ColorLike | None' = None

Widget background color.

filled_color class-attribute instance-attribute

filled_color: 'ColorLike | None' = None

Line style filled-portion color (defaults to foreground).

unfilled_color class-attribute instance-attribute

unfilled_color: 'ColorLike | None' = None

Line style unfilled-portion color.

running class-attribute instance-attribute

running: bool = True

Whether spinner animation advances with wall time.

fit_content class-attribute instance-attribute

fit_content: bool = dataclasses.field(
    default=False, kw_only=True
)

Whether layout should use the loader's natural size.

resolved_symbols property

resolved_symbols: tuple[str, ...]

Resolved spinner frame ladder.

ratio property

ratio: float

Return the completion ratio, clamped to 0.01.0.

Indeterminate loaders report 0.0.

finished property

finished: bool

Whether determinate progress has reached completion.

get_frame

get_frame() -> Any | None

Optional frame/panel chrome around composed content.

Source code in xnano/components/component.py
def get_frame(self) -> Any | None:
    """Optional frame/panel chrome around composed content."""
    return None

get_size

get_size(ctx: ComponentRenderContext[StateT]) -> Size

Return the preferred cell size of this component.

Source code in xnano/components/component.py
def get_size(self, ctx: ComponentRenderContext[StateT]) -> Size:
    """Return the preferred cell size of this component."""
    return Size(width=0, height=0)

before_render

before_render(
    ctx: ComponentRenderContext[StateT], area: "Area"
) -> "Area"

Called before rendering; returns the effective render area.

Source code in xnano/components/component.py
def before_render(
    self,
    ctx: ComponentRenderContext[StateT],
    area: "Area",
) -> "Area":
    """Called before rendering; returns the effective render area."""
    return area

after_render

after_render(
    ctx: ComponentRenderContext[StateT], area: "Area"
) -> None

Called after rendering for optional post-paint work.

Source code in xnano/components/component.py
def after_render(
    self,
    ctx: ComponentRenderContext[StateT],
    area: "Area",
) -> None:
    """Called after rendering for optional post-paint work."""
    return None

compose_extra_small

compose_extra_small(
    ctx: ComponentRenderContext[StateT],
) -> Content | None

Compose content when the viewport is extra small (< 40 cols).

Optional responsive counterpart to :meth:compose. When overridden, it is used instead of compose while the window is in this size tier. Overriding any compose_* variant opts the component into breakpoint dispatch; a component that overrides none pays no per-frame cost.

Source code in xnano/components/component.py
@responsive_noop
def compose_extra_small(
    self, ctx: ComponentRenderContext[StateT]
) -> Content | None:
    """Compose content when the viewport is extra small (< 40 cols).

    Optional responsive counterpart to :meth:`compose`. When
    overridden, it is used *instead of* ``compose`` while the window
    is in this size tier. Overriding any ``compose_*`` variant opts the
    component into breakpoint dispatch; a component that overrides none
    pays no per-frame cost.
    """
    return None

compose_small

compose_small(
    ctx: ComponentRenderContext[StateT],
) -> Content | None

Compose content when the viewport is small (40–79 cols).

See :meth:compose_extra_small.

Source code in xnano/components/component.py
@responsive_noop
def compose_small(
    self, ctx: ComponentRenderContext[StateT]
) -> Content | None:
    """Compose content when the viewport is small (40–79 cols).

    See :meth:`compose_extra_small`.
    """
    return None

compose_medium

compose_medium(
    ctx: ComponentRenderContext[StateT],
) -> Content | None

Compose content when the viewport is medium (80–119 cols).

See :meth:compose_extra_small.

Source code in xnano/components/component.py
@responsive_noop
def compose_medium(
    self, ctx: ComponentRenderContext[StateT]
) -> Content | None:
    """Compose content when the viewport is medium (80–119 cols).

    See :meth:`compose_extra_small`.
    """
    return None

compose_large

compose_large(
    ctx: ComponentRenderContext[StateT],
) -> Content | None

Compose content when the viewport is large (120–159 cols).

See :meth:compose_extra_small.

Source code in xnano/components/component.py
@responsive_noop
def compose_large(
    self, ctx: ComponentRenderContext[StateT]
) -> Content | None:
    """Compose content when the viewport is large (120–159 cols).

    See :meth:`compose_extra_small`.
    """
    return None

compose_extra_large

compose_extra_large(
    ctx: ComponentRenderContext[StateT],
) -> Content | None

Compose content when the viewport is extra large (>= 160 cols).

See :meth:compose_extra_small.

Source code in xnano/components/component.py
@responsive_noop
def compose_extra_large(
    self, ctx: ComponentRenderContext[StateT]
) -> Content | None:
    """Compose content when the viewport is extra large (>= 160 cols).

    See :meth:`compose_extra_small`.
    """
    return None

handle_keyboard

handle_keyboard(keyboard: 'KeyboardEventData') -> bool

Optional keyboard handler while focused.

Returns:

  • bool

    True when the event was consumed.

Source code in xnano/components/component.py
def handle_keyboard(self, keyboard: "KeyboardEventData") -> bool:
    """Optional keyboard handler while focused.

    Returns:
        ``True`` when the event was consumed.
    """
    return False

handle_paste

handle_paste(text: str) -> bool

Optional paste handler while focused.

Returns:

  • bool

    True when the paste was consumed.

Source code in xnano/components/component.py
def handle_paste(self, text: str) -> bool:
    """Optional paste handler while focused.

    Returns:
        ``True`` when the paste was consumed.
    """
    return False

component_post_init

component_post_init() -> None

Resolve spinner frames and start the animation epoch.

Source code in xnano/components/loader.py
def component_post_init(self) -> None:
    """Resolve spinner frames and start the animation epoch."""
    if self.style not in ("spinner", "bar", "line"):
        raise ValueError(f"Unsupported loader style: {self.style!r}")
    if self.interval <= 0:
        raise ValueError("Loader interval must be greater than zero.")
    self._resolved_symbols = resolve_loader_symbols(self.symbols)
    self._epoch_ns = time.monotonic_ns()
    self._frozen_frame = 0

restart

restart() -> None

Reset the spinner epoch to the current monotonic time.

Source code in xnano/components/loader.py
def restart(self) -> None:
    """Reset the spinner epoch to the current monotonic time."""
    self._epoch_ns = time.monotonic_ns()
    self._frozen_frame = 0

current_frame

current_frame() -> str

Return the active spinner glyph, advanced by the framework clock.

The inline path: embed this in any Text/flow content to animate a spinner mid-line without a manual per-tick frame counter — e.g. Text(content=f"{loader.current_frame()} generating…").

Source code in xnano/components/loader.py
def current_frame(self) -> str:
    """Return the active spinner glyph, advanced by the framework clock.

    The inline path: embed this in any ``Text``/flow content to animate a
    spinner mid-line without a manual per-tick frame counter — e.g.
    ``Text(content=f"{loader.current_frame()} generating…")``.
    """
    return self._resolved_symbols[self._spinner_frame_index()]

inline_text

inline_text() -> str

Return the spinner glyph plus its label for inline embedding.

Source code in xnano/components/loader.py
def inline_text(self) -> str:
    """Return the spinner glyph plus its label for inline embedding."""
    label = self._resolve_label_text()
    frame = self.current_frame()
    return frame if not label else f"{frame} {label}"

compose

compose(ctx: 'ComponentRenderContext')

Compose spinner text or gauge content for this loader.

Returns:

  • Interface-neutral content for this loader.

Source code in xnano/components/loader.py
def compose(self, ctx: "ComponentRenderContext"):
    """Compose spinner text or gauge content for this loader.

    Returns:
        Interface-neutral content for this loader.
    """
    if self.style == "spinner":
        return self._compose_spinner()
    if self.value is None:
        # Indeterminate bar/line: pulse the active spinner frame as
        # the gauge label while progress stays at zero.
        frame = self._resolved_symbols[self._spinner_frame_index()]
        label = self._resolve_label_text()
        pulse_label = frame if label is None else f"{frame} {label}"
        if self.style == "line":
            return LineGauge(
                progress=0.0,
                label=pulse_label,
                foreground=self.foreground,
                filled_color=self.filled_color or self.foreground,
                unfilled_color=self.unfilled_color,
                background=self.background,
                z=self.z,
                visible=self.visible,
            )
        return Gauge(
            progress=0.0,
            label=pulse_label,
            foreground=self.foreground,
            background=self.background,
            z=self.z,
            visible=self.visible,
        )
    return self._compose_determinate()

Markdown dataclass

Markdown(
    content: str | Text | list[str | Text] = "",
    foreground: ColorLike | None = None,
    background: ColorLike | None = None,
    modifiers: tuple[CharacterModifier, ...] = (),
    horizontal_align: Alignment | None = None,
    vertical_align: VerticalAlignment | None = None,
    wrap: bool = True,
    input: bool = False,
    placeholder: str | Text | None = None,
    cursor: int | None = None,
    multiline: bool = False,
    rows: int | None = None,
    ansi: bool = False,
    markdown: bool = False,
    language: str | None = None,
    passthrough: Sequence[str] = (),
    mask: str | None = None,
    max_length: int | None = None,
    read_only: bool = False,
    tab_size: int = 4,
    focusable: bool = False,
    fill: bool = False,
    base_path: Path | None = None,
    images: bool = True,
    links: bool = True,
    *,
    visible: bool = True,
    z: int = 0,
    fit_content: bool = True
)

Bases: Text

Display Markdown with terminal-friendly styling.

Content remains live and supports the same color, alignment, and wrapping options as Text.

Example

Markdown(content="# Status\n\nAll systems operational.")

Attributes:

  • base_path (Path | None) –

    Root for resolving relative image and link targets.

  • images (bool) –

    When True, allow image constructs when supported.

  • links (bool) –

    When True, allow link constructs when supported.

Methods:

  • get_frame

    Optional frame/panel chrome around composed content.

  • get_size

    Return the preferred cell size of this component.

  • before_render

    Called before rendering; returns the effective render area.

  • after_render

    Record the multi-line editor caret so the terminal cursor tracks it.

  • compose

    Compose interface-neutral content for this Text.

  • compose_extra_small

    Compose content when the viewport is extra small (< 40 cols).

  • compose_small

    Compose content when the viewport is small (40–79 cols).

  • compose_medium

    Compose content when the viewport is medium (80–119 cols).

  • compose_large

    Compose content when the viewport is large (120–159 cols).

  • compose_extra_large

    Compose content when the viewport is extra large (>= 160 cols).

  • handle_keyboard

    Edit this text when it has focus.

  • handle_paste

    Insert pasted text at the caret of a multi-line editor.

  • get_terminal_node

    Return composed content for terminal compatibility.

  • component_post_init

    Force markdown mode, then run Text setup.

visible class-attribute instance-attribute

visible: bool = dataclasses.field(
    default=True, kw_only=True
)

Whether this component paints at all.

z class-attribute instance-attribute

z: int = dataclasses.field(default=0, kw_only=True)

Stacking order among sibling content.

fit_content class-attribute instance-attribute

fit_content: bool = dataclasses.field(
    default=True, kw_only=True
)

When True, paint at natural size inside a larger slot.

focused property

focused: bool

Whether this component currently holds field focus.

content class-attribute instance-attribute

content: str | Text | list[str | Text] = dataclasses.field(
    default=""
)

Plain string, nested Text, or a list of either.

foreground class-attribute instance-attribute

foreground: ColorLike | None = None

Foreground color (deprecated alias: color).

background class-attribute instance-attribute

background: ColorLike | None = None

Background color.

modifiers class-attribute instance-attribute

modifiers: tuple[CharacterModifier, ...] = ()

Character modifiers such as bold or underline.

horizontal_align class-attribute instance-attribute

horizontal_align: Alignment | None = None

Horizontal alignment at the paragraph level.

vertical_align class-attribute instance-attribute

vertical_align: VerticalAlignment | None = None

Vertical alignment within the painted area.

wrap class-attribute instance-attribute

wrap: bool = True

Whether long lines may wrap.

input class-attribute instance-attribute

input: bool = False

When True on a leaf, the component is an editable field.

placeholder class-attribute instance-attribute

placeholder: str | Text | None = None

Shown when input is empty and unfocused.

cursor class-attribute instance-attribute

cursor: int | None = None

Caret index for single-line input; None means end of string.

multiline class-attribute instance-attribute

multiline: bool = False

When True with input, editing uses CoreTextEditor.

rows class-attribute instance-attribute

rows: int | None = None

Preferred visible height (lines) for a multiline input.

ansi class-attribute instance-attribute

ansi: bool = False

Parse ANSI SGR sequences in leaf content.

markdown class-attribute instance-attribute

markdown: bool = False

Parse leaf content as markdown.

language class-attribute instance-attribute

language: str | None = None

Pygments lexer name for syntax highlighting only.

passthrough class-attribute instance-attribute

passthrough: Sequence[str] = ()

Key bindings this input never captures while focused.

mask class-attribute instance-attribute

mask: str | None = None

Display-only mask character(s); real value is preserved.

max_length class-attribute instance-attribute

max_length: int | None = None

Maximum plain-string length; longer input is clamped.

read_only class-attribute instance-attribute

read_only: bool = False

Reject edits while remaining focusable.

tab_size class-attribute instance-attribute

tab_size: int = 4

Tab width applied to multiline tab insertion and paste.

focusable class-attribute instance-attribute

focusable: bool = False

Whether this component participates in field focus.

fill class-attribute instance-attribute

fill: bool = False

Whether background fills the whole slot rather than only the text glyphs. When True short lines still paint the background across the full cell width (no manual right-padding required).

owns_cursor property

owns_cursor: bool

Whether this Text paints its own caret (multi-line editor).

cursor_position property

cursor_position: tuple[int, int] | None

Absolute caret cell for the terminal cursor, set during paint.

Reported only for a focused multi-line editor (a single-line input paints its own caret inline). None otherwise, which hides the hardware cursor.

value property writable

value: str

Canonical plain-string content for leaf and input modes.

base_path class-attribute instance-attribute

base_path: Path | None = None

Root path for resolving relative image and link targets.

images class-attribute instance-attribute

images: bool = True

Whether image constructs may be emitted when supported.

links: bool = True

Whether link constructs may be emitted when supported.

get_frame

get_frame() -> Any | None

Optional frame/panel chrome around composed content.

Source code in xnano/components/component.py
def get_frame(self) -> Any | None:
    """Optional frame/panel chrome around composed content."""
    return None

get_size

get_size(ctx: ComponentRenderContext[StateT]) -> Size

Return the preferred cell size of this component.

Source code in xnano/components/component.py
def get_size(self, ctx: ComponentRenderContext[StateT]) -> Size:
    """Return the preferred cell size of this component."""
    return Size(width=0, height=0)

before_render

before_render(
    ctx: ComponentRenderContext[StateT], area: "Area"
) -> "Area"

Called before rendering; returns the effective render area.

Source code in xnano/components/component.py
def before_render(
    self,
    ctx: ComponentRenderContext[StateT],
    area: "Area",
) -> "Area":
    """Called before rendering; returns the effective render area."""
    return area

after_render

after_render(
    ctx: "ComponentRenderContext[Any]", area: "Area"
) -> None

Record the multi-line editor caret so the terminal cursor tracks it.

Source code in xnano/components/text.py
def after_render(
    self,
    ctx: "ComponentRenderContext[Any]",
    area: "Area",
) -> None:
    """Record the multi-line editor caret so the terminal cursor tracks it."""
    if not (self._input_focused and self._editor is not None):
        self._cursor_position = None
        return
    row, column = self._editor.cursor()
    # ponytail: no soft-wrap/scroll accounting; clamps to the slot, which
    # is exact for a note-sized editor. Add offsets if the body scrolls.
    x = area.x + max(0, min(column, max(0, area.width - 1)))
    y = area.y + max(0, min(row, max(0, area.height - 1)))
    self._cursor_position = (x, y)

compose

compose(
    ctx: ComponentRenderContext[Any],
) -> TextBlock | Native | Panel | None

Compose interface-neutral content for this Text.

Parameters:

Returns:

Source code in xnano/components/text.py
def compose(
    self, ctx: ComponentRenderContext[Any]
) -> TextBlock | Native | Panel | None:
    """Compose interface-neutral content for this Text.

    Args:
        ctx: Render-time scope for this paint.

    Returns:
        A ``TextBlock``, ``Native`` editor payload, or nested content.
        When ``fill`` is set the block is wrapped in a background
        ``Panel`` so the color spans the full slot.
    """
    content = self._compose_content(ctx)
    if (
        self.fill
        and self.background is not None
        and isinstance(content, TextBlock)
    ):
        return Panel(child=content, background=self.background)
    return content

compose_extra_small

compose_extra_small(
    ctx: ComponentRenderContext[StateT],
) -> Content | None

Compose content when the viewport is extra small (< 40 cols).

Optional responsive counterpart to :meth:compose. When overridden, it is used instead of compose while the window is in this size tier. Overriding any compose_* variant opts the component into breakpoint dispatch; a component that overrides none pays no per-frame cost.

Source code in xnano/components/component.py
@responsive_noop
def compose_extra_small(
    self, ctx: ComponentRenderContext[StateT]
) -> Content | None:
    """Compose content when the viewport is extra small (< 40 cols).

    Optional responsive counterpart to :meth:`compose`. When
    overridden, it is used *instead of* ``compose`` while the window
    is in this size tier. Overriding any ``compose_*`` variant opts the
    component into breakpoint dispatch; a component that overrides none
    pays no per-frame cost.
    """
    return None

compose_small

compose_small(
    ctx: ComponentRenderContext[StateT],
) -> Content | None

Compose content when the viewport is small (40–79 cols).

See :meth:compose_extra_small.

Source code in xnano/components/component.py
@responsive_noop
def compose_small(
    self, ctx: ComponentRenderContext[StateT]
) -> Content | None:
    """Compose content when the viewport is small (40–79 cols).

    See :meth:`compose_extra_small`.
    """
    return None

compose_medium

compose_medium(
    ctx: ComponentRenderContext[StateT],
) -> Content | None

Compose content when the viewport is medium (80–119 cols).

See :meth:compose_extra_small.

Source code in xnano/components/component.py
@responsive_noop
def compose_medium(
    self, ctx: ComponentRenderContext[StateT]
) -> Content | None:
    """Compose content when the viewport is medium (80–119 cols).

    See :meth:`compose_extra_small`.
    """
    return None

compose_large

compose_large(
    ctx: ComponentRenderContext[StateT],
) -> Content | None

Compose content when the viewport is large (120–159 cols).

See :meth:compose_extra_small.

Source code in xnano/components/component.py
@responsive_noop
def compose_large(
    self, ctx: ComponentRenderContext[StateT]
) -> Content | None:
    """Compose content when the viewport is large (120–159 cols).

    See :meth:`compose_extra_small`.
    """
    return None

compose_extra_large

compose_extra_large(
    ctx: ComponentRenderContext[StateT],
) -> Content | None

Compose content when the viewport is extra large (>= 160 cols).

See :meth:compose_extra_small.

Source code in xnano/components/component.py
@responsive_noop
def compose_extra_large(
    self, ctx: ComponentRenderContext[StateT]
) -> Content | None:
    """Compose content when the viewport is extra large (>= 160 cols).

    See :meth:`compose_extra_small`.
    """
    return None

handle_keyboard

handle_keyboard(keyboard: 'KeyboardEventData') -> bool

Edit this text when it has focus.

Passthrough bindings remain available to hooks. Read-only inputs reject edits, and max_length limits inserted content.

Parameters:

  • keyboard ('KeyboardEventData') –

    The keyboard event payload.

Returns:

  • bool

    True when the key was consumed as text editing.

Source code in xnano/components/text.py
def handle_keyboard(self, keyboard: "KeyboardEventData") -> bool:
    """Edit this text when it has focus.

    Passthrough bindings remain available to hooks. Read-only inputs reject
    edits, and ``max_length`` limits inserted content.

    Args:
        keyboard: The keyboard event payload.

    Returns:
        ``True`` when the key was consumed as text editing.
    """
    if self.passthrough and keyboard.matches(*self.passthrough):
        return False
    if not self.input:
        return False

    if self._editor is not None:
        return self._handle_editor_keyboard(keyboard)
    return self._handle_single_line_keyboard(keyboard)

handle_paste

handle_paste(text: str) -> bool

Insert pasted text at the caret of a multi-line editor.

Parameters:

  • text (str) –

    The pasted clipboard text.

Returns:

  • bool

    True when the paste was consumed by the native editor.

Source code in xnano/components/text.py
def handle_paste(self, text: str) -> bool:
    """Insert pasted text at the caret of a multi-line editor.

    Args:
        text: The pasted clipboard text.

    Returns:
        ``True`` when the paste was consumed by the native editor.
    """
    if self._editor is None:
        return False
    if self.read_only:
        return True
    text = self._clamp_text(text)
    if self.max_length is not None:
        remaining = self.max_length - len(self._editor.text())
        if remaining <= 0:
            return True
        text = text[:remaining]
    self._editor.insert_text(text)
    object.__setattr__(self, "content", self._editor.text())
    return True

get_terminal_node

get_terminal_node(
    ctx: ComponentRenderContext[Any],
) -> TextBlock | Native | Panel | None

Return composed content for terminal compatibility.

Parameters:

Returns:

Source code in xnano/components/text.py
def get_terminal_node(
    self, ctx: ComponentRenderContext[Any]
) -> TextBlock | Native | Panel | None:
    """Return composed content for terminal compatibility.

    Args:
        ctx: Render-time scope for this paint.

    Returns:
        The same interface-neutral content as ``compose``.
    """
    return self.compose(ctx)

component_post_init

component_post_init() -> None

Force markdown mode, then run Text setup.

Source code in xnano/components/markdown.py
def component_post_init(self) -> None:
    """Force markdown mode, then run ``Text`` setup."""
    self.markdown = True
    super().component_post_init()

Option dataclass

Option(
    label: str | Any,
    value: Any = None,
    disabled: bool = False,
)

A labeled choice with optional distinct value and disabled flag.

Pass plain strings or Text values for simple choices. Use Option when the stored value differs from its label or the choice is disabled.

Attributes:

  • label (str | Any) –

    Display text (plain string or styled Text leaf).

  • value (Any) –

    Stored value; defaults to the plain label text when None.

  • disabled (bool) –

    When True, movement skips this entry and it cannot become the active selection via select.

Methods:

  • get_label_text

    Return the plain-text form of label.

  • get_value

    Return the stored value, falling back to the plain label.

label instance-attribute

label: str | Any

Display text (plain string or styled Text leaf).

value class-attribute instance-attribute

value: Any = None

Stored value; defaults to the plain label text when None.

disabled class-attribute instance-attribute

disabled: bool = False

When True, movement skips this entry.

get_label_text

get_label_text() -> str

Return the plain-text form of label.

Source code in xnano/components/options.py
def get_label_text(self) -> str:
    """Return the plain-text form of ``label``."""
    label = self.label
    if isinstance(label, str):
        return label
    value = getattr(label, "value", None)
    if isinstance(value, str):
        return value
    content = getattr(label, "content", None)
    if isinstance(content, str):
        return content
    return str(label)

get_value

get_value() -> Any

Return the stored value, falling back to the plain label.

Source code in xnano/components/options.py
def get_value(self) -> Any:
    """Return the stored value, falling back to the plain label."""
    if self.value is not None:
        return self.value
    return self.get_label_text()

Options dataclass

Options(
    items: Sequence[OptionItem] = (),
    query: str = "",
    filter: FilterMode = "fuzzy",
    accept: AcceptPolicy = "replace",
    searchable: bool = False,
    selected: int = 0,
    direction: OptionsDirection = "top_to_bottom",
    foreground: ColorLike | None = None,
    background: ColorLike | None = None,
    highlight_color: ColorLike = "black",
    highlight_background: ColorLike = "white",
    highlight_symbol: str = "> ",
    hovered: int | None = None,
    hover_symbol: str = "· ",
    match_color: ColorLike | None = "cyan",
    repeat_highlight_symbol: bool = False,
    focusable: bool = True,
    passthrough: Sequence[str] = (),
    *,
    visible: bool = True,
    z: int = 0,
    fit_content: bool = True
)

Bases: Component

Always-visible, selectable choice list.

Up/down move the selection (skipping disabled entries) and value reads the selected item:

class Picker(BaseGrid):
    themes: Options = Field(
        default=Options(items=THEME_NAMES),
    )

    @on_keyboard("enter")
    def _choose(self, ctx: Context) -> None:
        apply_theme(self.themes.value)

A plain list is not a search box: typing-to-filter is opt-in. Set searchable=True to turn the list into a fuzzy filter where printable keys edit query and matched characters are emphasized in match_color — a focused searchable list intentionally consumes those keys, so leave it off for a list that only browses, or the keys never reach your command hooks. Either way you can drive query reactively from another field.

Example

Options(items=("small", "medium", "large"), selected=1)

Attributes:

Methods:

  • component_post_init

    Initialize subclass state after dataclass fields are assigned.

  • get_frame

    Optional frame/panel chrome around composed content.

  • get_size

    Return the preferred cell size of this component.

  • before_render

    Called before rendering; returns the effective render area.

  • compose_extra_small

    Compose content when the viewport is extra small (< 40 cols).

  • compose_small

    Compose content when the viewport is small (40–79 cols).

  • compose_medium

    Compose content when the viewport is medium (80–119 cols).

  • compose_large

    Compose content when the viewport is large (120–159 cols).

  • compose_extra_large

    Compose content when the viewport is extra large (>= 160 cols).

  • handle_paste

    Optional paste handler while focused.

  • select_value

    Select the visible item whose stored value equals value.

  • resolve_submission

    Reconcile typed composer text with the selection per accept.

  • move

    Move selected by delta, skipping disabled entries.

  • select

    Set selected to index within the filtered view.

  • clear_query

    Clear the filter query and reset selection to the first row.

  • handle_keyboard

    Apply a keyboard event while this Options is focused.

  • compose

    Compose interface-neutral Content for this Options.

  • after_render

    Record the painted area so hover can map a pointer to a row.

  • handle_hover

    Mark the row under the pointer as hovered (mouse hover).

visible class-attribute instance-attribute

visible: bool = dataclasses.field(
    default=True, kw_only=True
)

Whether this component paints at all.

z class-attribute instance-attribute

z: int = dataclasses.field(default=0, kw_only=True)

Stacking order among sibling content.

fit_content class-attribute instance-attribute

fit_content: bool = dataclasses.field(
    default=True, kw_only=True
)

When True, paint at natural size inside a larger slot.

focused property

focused: bool

Whether this component currently holds field focus.

items class-attribute instance-attribute

items: Sequence[OptionItem] = ()

Entries to pick from (plain strings, Text, or Option).

query class-attribute instance-attribute

query: str = ''

Filter text. Edited by typing while focused when searchable; assign it from a hook to filter reactively.

filter class-attribute instance-attribute

filter: FilterMode = 'fuzzy'

How query narrows the visible items — see FilterMode.

"fuzzy"/True fuzzy-match, "prefix" prefix-match, "none"/ False show everything (for externally filtered items), or a (query, item_text) -> bool callable.

accept class-attribute instance-attribute

accept: AcceptPolicy = 'replace'

How resolve_submission reconciles typed text with the selection.

searchable class-attribute instance-attribute

searchable: bool = False

Whether typing while focused edits query directly.

Off by default: a plain Options browses with the arrow keys and lets every other key reach app hooks. Turn it on to make the list a fuzzy search box that consumes printable keys while focused.

selected class-attribute instance-attribute

selected: int = 0

Selection index within the filtered view.

direction class-attribute instance-attribute

direction: OptionsDirection = 'top_to_bottom'

Visual order of option rows in the list.

foreground class-attribute instance-attribute

foreground: ColorLike | None = None

Default foreground color for unselected rows (deprecated alias: color).

background class-attribute instance-attribute

background: ColorLike | None = None

Default background color for unselected rows.

highlight_color class-attribute instance-attribute

highlight_color: ColorLike = 'black'

Foreground of the selected row.

highlight_background class-attribute instance-attribute

highlight_background: ColorLike = 'white'

Background of the selected row.

highlight_symbol class-attribute instance-attribute

highlight_symbol: str = '> '

Symbol prepended to the selected row.

hovered class-attribute instance-attribute

hovered: int | None = None

Row under the pointer (filtered-view index), or None.

Set by :meth:handle_hover when mouse events are enabled; drawn as a dim hover_symbol so it reads as distinct from the selection.

hover_symbol class-attribute instance-attribute

hover_symbol: str = '· '

Symbol prepended to the hovered row (dim), distinct from selection.

match_color class-attribute instance-attribute

match_color: ColorLike | None = 'cyan'

Emphasis color for characters matched by query.

repeat_highlight_symbol class-attribute instance-attribute

repeat_highlight_symbol: bool = False

When True, every row shows the highlight symbol.

focusable class-attribute instance-attribute

focusable: bool = True

Whether this Options participates in field focus (tab order).

passthrough class-attribute instance-attribute

passthrough: Sequence[str] = ()

Key bindings this list never captures while focused.

owns_cursor class-attribute instance-attribute

owns_cursor: bool = dataclasses.field(
    default=True, init=False
)

Whether selection replaces the hardware caret.

filtered property

filtered: tuple[int, ...]

Indices into items currently visible, in display order.

Use this with visible_items to inspect the current filtered view.

visible_items property

visible_items: tuple[str, ...]

Plain text of the currently visible items, in display order.

value property

value: Any | None

Stored value of the selected item, or None when empty.

selected_item property

selected_item: OptionItem | None

The selected item object, or None when the view is empty.

selected_value property

selected_value: Any | None

Stable stored value of the selection (alias of value).

Use with select_value to preserve a selection across item rebuilds by value/id rather than by fragile filtered index.

selected_label property

selected_label: str

Plain display text of the selected item, or "" when empty.

component_post_init

component_post_init() -> None

Initialize subclass state after dataclass fields are assigned.

Override this method instead of __post_init__.

Source code in xnano/components/component.py
def component_post_init(self) -> None:
    """Initialize subclass state after dataclass fields are assigned.

    Override this method instead of ``__post_init__``.
    """
    return None

get_frame

get_frame() -> Any | None

Optional frame/panel chrome around composed content.

Source code in xnano/components/component.py
def get_frame(self) -> Any | None:
    """Optional frame/panel chrome around composed content."""
    return None

get_size

get_size(ctx: ComponentRenderContext[StateT]) -> Size

Return the preferred cell size of this component.

Source code in xnano/components/component.py
def get_size(self, ctx: ComponentRenderContext[StateT]) -> Size:
    """Return the preferred cell size of this component."""
    return Size(width=0, height=0)

before_render

before_render(
    ctx: ComponentRenderContext[StateT], area: "Area"
) -> "Area"

Called before rendering; returns the effective render area.

Source code in xnano/components/component.py
def before_render(
    self,
    ctx: ComponentRenderContext[StateT],
    area: "Area",
) -> "Area":
    """Called before rendering; returns the effective render area."""
    return area

compose_extra_small

compose_extra_small(
    ctx: ComponentRenderContext[StateT],
) -> Content | None

Compose content when the viewport is extra small (< 40 cols).

Optional responsive counterpart to :meth:compose. When overridden, it is used instead of compose while the window is in this size tier. Overriding any compose_* variant opts the component into breakpoint dispatch; a component that overrides none pays no per-frame cost.

Source code in xnano/components/component.py
@responsive_noop
def compose_extra_small(
    self, ctx: ComponentRenderContext[StateT]
) -> Content | None:
    """Compose content when the viewport is extra small (< 40 cols).

    Optional responsive counterpart to :meth:`compose`. When
    overridden, it is used *instead of* ``compose`` while the window
    is in this size tier. Overriding any ``compose_*`` variant opts the
    component into breakpoint dispatch; a component that overrides none
    pays no per-frame cost.
    """
    return None

compose_small

compose_small(
    ctx: ComponentRenderContext[StateT],
) -> Content | None

Compose content when the viewport is small (40–79 cols).

See :meth:compose_extra_small.

Source code in xnano/components/component.py
@responsive_noop
def compose_small(
    self, ctx: ComponentRenderContext[StateT]
) -> Content | None:
    """Compose content when the viewport is small (40–79 cols).

    See :meth:`compose_extra_small`.
    """
    return None

compose_medium

compose_medium(
    ctx: ComponentRenderContext[StateT],
) -> Content | None

Compose content when the viewport is medium (80–119 cols).

See :meth:compose_extra_small.

Source code in xnano/components/component.py
@responsive_noop
def compose_medium(
    self, ctx: ComponentRenderContext[StateT]
) -> Content | None:
    """Compose content when the viewport is medium (80–119 cols).

    See :meth:`compose_extra_small`.
    """
    return None

compose_large

compose_large(
    ctx: ComponentRenderContext[StateT],
) -> Content | None

Compose content when the viewport is large (120–159 cols).

See :meth:compose_extra_small.

Source code in xnano/components/component.py
@responsive_noop
def compose_large(
    self, ctx: ComponentRenderContext[StateT]
) -> Content | None:
    """Compose content when the viewport is large (120–159 cols).

    See :meth:`compose_extra_small`.
    """
    return None

compose_extra_large

compose_extra_large(
    ctx: ComponentRenderContext[StateT],
) -> Content | None

Compose content when the viewport is extra large (>= 160 cols).

See :meth:compose_extra_small.

Source code in xnano/components/component.py
@responsive_noop
def compose_extra_large(
    self, ctx: ComponentRenderContext[StateT]
) -> Content | None:
    """Compose content when the viewport is extra large (>= 160 cols).

    See :meth:`compose_extra_small`.
    """
    return None

handle_paste

handle_paste(text: str) -> bool

Optional paste handler while focused.

Returns:

  • bool

    True when the paste was consumed.

Source code in xnano/components/component.py
def handle_paste(self, text: str) -> bool:
    """Optional paste handler while focused.

    Returns:
        ``True`` when the paste was consumed.
    """
    return False

select_value

select_value(value: Any) -> bool

Select the visible item whose stored value equals value.

Returns True when a match is found. The stable way to keep a selection pinned across item rebuilds: store the value, rebuild items, then re-select by value.

Source code in xnano/components/options.py
def select_value(self, value: Any) -> bool:
    """Select the visible item whose stored value equals ``value``.

    Returns ``True`` when a match is found. The stable way to keep a
    selection pinned across item rebuilds: store the value, rebuild
    ``items``, then re-select by value.
    """
    for index, (item_index, _) in enumerate(self._filtered()):
        if _item_value(self.items[item_index]) == value:
            self.selected = index
            return True
    return False

resolve_submission

resolve_submission(typed: str) -> str

Reconcile typed composer text with the selection per accept.

Removes the userland "did they Tab-complete this row or type past it?" logic. See AcceptPolicy for the per-policy behavior.

Source code in xnano/components/options.py
def resolve_submission(self, typed: str) -> str:
    """Reconcile typed composer text with the selection per ``accept``.

    Removes the userland "did they Tab-complete this row or type past
    it?" logic. See ``AcceptPolicy`` for the per-policy behavior.
    """
    typed = typed or ""
    selected = self.selected_label
    if self.accept == "extend" or not selected:
        return typed
    if self.accept == "replace":
        return selected
    # if_prefix_only
    typed_stripped = typed.strip()
    selected_stripped = selected.strip()
    if not typed_stripped:
        return selected
    typed_lower = typed_stripped.lower()
    selected_lower = selected_stripped.lower()
    if typed_lower == selected_lower or typed_lower.startswith(
        selected_lower + " "
    ):
        return typed
    if selected_lower.startswith(typed_lower):
        return selected
    return selected

move

move(delta: int) -> None

Move selected by delta, skipping disabled entries.

Movement stays within the currently filtered choices.

Parameters:

  • delta (int) –

    Steps to move; negative moves toward the start.

Source code in xnano/components/options.py
def move(self, delta: int) -> None:
    """Move ``selected`` by ``delta``, skipping disabled entries.

    Movement stays within the currently filtered choices.

    Args:
        delta: Steps to move; negative moves toward the start.
    """
    visible = self._filtered()
    visible_count = len(visible)
    if visible_count == 0:
        self.selected = 0
        return
    current = max(0, min(self.selected, visible_count - 1))
    if delta == 0:
        self.selected = current
        return
    step = 1 if delta > 0 else -1
    remaining = abs(delta)
    index = current
    while remaining > 0:
        next_index = index + step
        if next_index < 0 or next_index >= visible_count:
            break
        index = next_index
        item_index = visible[index][0]
        if not _item_disabled(self.items[item_index]):
            remaining -= 1
    self.selected = index
    # If we landed on a disabled entry (all remaining disabled),
    # snap back to the nearest enabled neighbor.
    self._ensure_enabled_selection()

select

select(index: int) -> None

Set selected to index within the filtered view.

Disabled entries are rejected; the selection is left unchanged when index points at a disabled item. Out-of-range indices are clamped.

Parameters:

  • index (int) –

    Target index in the filtered view.

Source code in xnano/components/options.py
def select(self, index: int) -> None:
    """Set ``selected`` to ``index`` within the filtered view.

    Disabled entries are rejected; the selection is left unchanged
    when ``index`` points at a disabled item. Out-of-range indices
    are clamped.

    Args:
        index: Target index in the filtered view.
    """
    visible = self._filtered()
    if not visible:
        self.selected = 0
        return
    clamped = max(0, min(index, len(visible) - 1))
    item_index = visible[clamped][0]
    if _item_disabled(self.items[item_index]):
        return
    self.selected = clamped

clear_query

clear_query() -> None

Clear the filter query and reset selection to the first row.

Source code in xnano/components/options.py
def clear_query(self) -> None:
    """Clear the filter query and reset selection to the first row."""
    self.query = ""
    self.selected = 0
    self._ensure_enabled_selection()

handle_keyboard

handle_keyboard(keyboard: 'KeyboardEventData') -> bool

Apply a keyboard event while this Options is focused.

Up/down move the selection (skipping disabled); Home/End jump to the first/last enabled entry. Printable characters and backspace edit query when searchable. Enter, tab, escape, and passthrough bindings are never consumed so hooks and focus navigation see them.

Parameters:

  • keyboard ('KeyboardEventData') –

    The keyboard event payload.

Returns:

  • bool

    True when the event was consumed.

Source code in xnano/components/options.py
def handle_keyboard(self, keyboard: "KeyboardEventData") -> bool:
    """Apply a keyboard event while this Options is focused.

    Up/down move the selection (skipping disabled); Home/End jump
    to the first/last enabled entry. Printable characters and
    backspace edit ``query`` when ``searchable``. Enter, tab,
    escape, and ``passthrough`` bindings are never consumed so hooks
    and focus navigation see them.

    Args:
        keyboard: The keyboard event payload.

    Returns:
        ``True`` when the event was consumed.
    """
    kind = keyboard.kind
    if kind is not None and kind not in ("press", "repeat"):
        return False
    if self.passthrough and keyboard.matches(*self.passthrough):
        return False
    if keyboard.matches("enter", "tab", "escape", "esc"):
        return False

    visible_count = len(self._filtered())
    if keyboard.matches("up"):
        self.move(-1)
        return True
    if keyboard.matches("down"):
        self.move(1)
        return True
    if keyboard.matches("home"):
        self.selected = self._first_enabled_index()
        return True
    if keyboard.matches("end"):
        self.selected = self._last_enabled_index() if visible_count else 0
        return True
    if not self.searchable:
        return False
    if keyboard.matches("backspace"):
        self.query = self.query[:-1]
        self.selected = 0
        self._ensure_enabled_selection()
        return True
    character = keyboard.character
    modifiers = keyboard.modifiers
    if (
        character is not None
        and len(character) == 1
        and character.isprintable()
        and character not in ("\n", "\r", "\t")
        # ctrl/alt chords are app shortcuts, not search text.
        and "ctrl" not in modifiers
        and "alt" not in modifiers
    ):
        self.query = self.query + character
        self.selected = 0
        self._ensure_enabled_selection()
        return True
    return False

compose

compose(ctx: 'ComponentRenderContext') -> Any

Compose interface-neutral Content for this Options.

Parameters:

  • ctx ('ComponentRenderContext') –

    Render-time scope for this paint.

Returns:

  • Any

    An Items list, or a vertical Stack with a query row

  • Any

    above the list when searchable.

Source code in xnano/components/options.py
def compose(self, ctx: "ComponentRenderContext") -> Any:
    """Compose interface-neutral Content for this Options.

    Args:
        ctx: Render-time scope for this paint.

    Returns:
        An ``Items`` list, or a vertical ``Stack`` with a query row
        above the list when ``searchable``.
    """
    from xnano.core.content import Stack

    visible = self._filtered()
    available = ctx.area.height - (1 if self.searchable else 0)
    if available > 0 and len(visible) > available:
        selected = max(0, min(self.selected, len(visible) - 1))
        start = min(
            max(selected - available // 2, 0),
            len(visible) - available,
        )
        window = visible[start : start + available]
        self._visible_start = start
        hovered = self.hovered
        self.selected = selected - start
        if hovered is not None:
            self.hovered = hovered - start
        try:
            items_content = self._compose_items(ctx, visible=window)
        finally:
            self.selected = selected
            self.hovered = hovered
    else:
        self._visible_start = 0
        items_content = self._compose_items(ctx, visible=visible)
    if not self.searchable:
        return items_content
    return Stack(
        children=(self._compose_query_row(), items_content),
        direction="vertical",
        z=self.z,
        visible=self.visible,
    )

after_render

after_render(
    ctx: "ComponentRenderContext", area: Any
) -> None

Record the painted area so hover can map a pointer to a row.

Source code in xnano/components/options.py
def after_render(self, ctx: "ComponentRenderContext", area: Any) -> None:
    """Record the painted area so hover can map a pointer to a row."""
    self._content_area = area

handle_hover

handle_hover(x: int, y: int) -> bool

Mark the row under the pointer as hovered (mouse hover).

Hover is a distinct indicator from the selection: the hovered row is set here and drawn with a dim hover_symbol. Moving off the rows clears it. Returns whether the hovered row changed.

Source code in xnano/components/options.py
def handle_hover(self, x: int, y: int) -> bool:
    """Mark the row under the pointer as hovered (mouse hover).

    Hover is a *distinct* indicator from the selection: the hovered row
    is set here and drawn with a dim ``hover_symbol``. Moving off the
    rows clears it. Returns whether the hovered row changed.
    """
    del x  # rows span the full width; only the row (y) matters
    area = self._content_area
    if area is None:
        return False
    pairs = self._filtered()
    # The query row (searchable) sits above the items; skip it.
    top = area.y + (1 if self.searchable else 0)
    row = y - top
    visible_count = min(
        len(pairs) - self._visible_start,
        max(0, area.height - (1 if self.searchable else 0)),
    )
    if self.direction == "bottom_to_top" and visible_count:
        row = (visible_count - 1) - row
    hovered = (
        self._visible_start + row
        if pairs and 0 <= row < visible_count
        else None
    )
    if hovered != self.hovered:
        self.hovered = hovered
        return True
    return False

Scrollbar dataclass

Scrollbar(
    content_length: int = 0,
    position: int = 0,
    viewport_length: int = 0,
    orientation: ScrollbarOrientationLike = "vertical_right",
    thumb: str | None = None,
    track: str | None = None,
    begin: str | None = None,
    end: str | None = None,
    color: "ColorLike | None" = None,
    thumb_color: "ColorLike | None" = None,
    track_color: "ColorLike | None" = None,
    begin_color: "ColorLike | None" = None,
    end_color: "ColorLike | None" = None,
    *,
    visible: bool = True,
    z: int = 0,
    fit_content: bool = True
)

Bases: Component

Display the visible range of scrollable content.

Lengths and position are clamped on every frame, so live updates cannot move the thumb outside the track.

Example

Scrollbar(content_length=200, viewport_length=25, position=50)

Attributes:

Methods:

  • get_frame

    Optional frame/panel chrome around composed content.

  • get_size

    Return the preferred cell size of this component.

  • before_render

    Called before rendering; returns the effective render area.

  • after_render

    Called after rendering for optional post-paint work.

  • compose_extra_small

    Compose content when the viewport is extra small (< 40 cols).

  • compose_small

    Compose content when the viewport is small (40–79 cols).

  • compose_medium

    Compose content when the viewport is medium (80–119 cols).

  • compose_large

    Compose content when the viewport is large (120–159 cols).

  • compose_extra_large

    Compose content when the viewport is extra large (>= 160 cols).

  • handle_keyboard

    Optional keyboard handler while focused.

  • handle_paste

    Optional paste handler while focused.

  • component_post_init

    Clamp initial lengths and position.

  • from_scroll_handle

    Build a scrollbar bound to a field scroll handle.

  • compose

    Compose scrollbar content with a native paint fallback.

visible class-attribute instance-attribute

visible: bool = dataclasses.field(
    default=True, kw_only=True
)

Whether this component paints at all.

z class-attribute instance-attribute

z: int = dataclasses.field(default=0, kw_only=True)

Stacking order among sibling content.

fit_content class-attribute instance-attribute

fit_content: bool = dataclasses.field(
    default=True, kw_only=True
)

When True, paint at natural size inside a larger slot.

focused property

focused: bool

Whether this component currently holds field focus.

content_length class-attribute instance-attribute

content_length: int = 0

Total scrollable content size.

position class-attribute instance-attribute

position: int = 0

Current scroll offset within content_length.

viewport_length class-attribute instance-attribute

viewport_length: int = 0

Visible window size; drives thumb proportion.

orientation class-attribute instance-attribute

orientation: ScrollbarOrientationLike = 'vertical_right'

Which edge the scrollbar is drawn on.

thumb class-attribute instance-attribute

thumb: str | None = None

Optional thumb glyph.

track class-attribute instance-attribute

track: str | None = None

Optional track glyph.

begin class-attribute instance-attribute

begin: str | None = None

Arrow symbol at the start; None omits it.

end class-attribute instance-attribute

end: str | None = None

Arrow symbol at the end; None omits it.

color class-attribute instance-attribute

color: 'ColorLike | None' = None

Overall track and thumb style.

thumb_color class-attribute instance-attribute

thumb_color: 'ColorLike | None' = None

Thumb foreground color.

track_color class-attribute instance-attribute

track_color: 'ColorLike | None' = None

Track foreground color.

begin_color class-attribute instance-attribute

begin_color: 'ColorLike | None' = None

Begin-arrow color.

end_color class-attribute instance-attribute

end_color: 'ColorLike | None' = None

End-arrow color.

get_frame

get_frame() -> Any | None

Optional frame/panel chrome around composed content.

Source code in xnano/components/component.py
def get_frame(self) -> Any | None:
    """Optional frame/panel chrome around composed content."""
    return None

get_size

get_size(ctx: ComponentRenderContext[StateT]) -> Size

Return the preferred cell size of this component.

Source code in xnano/components/component.py
def get_size(self, ctx: ComponentRenderContext[StateT]) -> Size:
    """Return the preferred cell size of this component."""
    return Size(width=0, height=0)

before_render

before_render(
    ctx: ComponentRenderContext[StateT], area: "Area"
) -> "Area"

Called before rendering; returns the effective render area.

Source code in xnano/components/component.py
def before_render(
    self,
    ctx: ComponentRenderContext[StateT],
    area: "Area",
) -> "Area":
    """Called before rendering; returns the effective render area."""
    return area

after_render

after_render(
    ctx: ComponentRenderContext[StateT], area: "Area"
) -> None

Called after rendering for optional post-paint work.

Source code in xnano/components/component.py
def after_render(
    self,
    ctx: ComponentRenderContext[StateT],
    area: "Area",
) -> None:
    """Called after rendering for optional post-paint work."""
    return None

compose_extra_small

compose_extra_small(
    ctx: ComponentRenderContext[StateT],
) -> Content | None

Compose content when the viewport is extra small (< 40 cols).

Optional responsive counterpart to :meth:compose. When overridden, it is used instead of compose while the window is in this size tier. Overriding any compose_* variant opts the component into breakpoint dispatch; a component that overrides none pays no per-frame cost.

Source code in xnano/components/component.py
@responsive_noop
def compose_extra_small(
    self, ctx: ComponentRenderContext[StateT]
) -> Content | None:
    """Compose content when the viewport is extra small (< 40 cols).

    Optional responsive counterpart to :meth:`compose`. When
    overridden, it is used *instead of* ``compose`` while the window
    is in this size tier. Overriding any ``compose_*`` variant opts the
    component into breakpoint dispatch; a component that overrides none
    pays no per-frame cost.
    """
    return None

compose_small

compose_small(
    ctx: ComponentRenderContext[StateT],
) -> Content | None

Compose content when the viewport is small (40–79 cols).

See :meth:compose_extra_small.

Source code in xnano/components/component.py
@responsive_noop
def compose_small(
    self, ctx: ComponentRenderContext[StateT]
) -> Content | None:
    """Compose content when the viewport is small (40–79 cols).

    See :meth:`compose_extra_small`.
    """
    return None

compose_medium

compose_medium(
    ctx: ComponentRenderContext[StateT],
) -> Content | None

Compose content when the viewport is medium (80–119 cols).

See :meth:compose_extra_small.

Source code in xnano/components/component.py
@responsive_noop
def compose_medium(
    self, ctx: ComponentRenderContext[StateT]
) -> Content | None:
    """Compose content when the viewport is medium (80–119 cols).

    See :meth:`compose_extra_small`.
    """
    return None

compose_large

compose_large(
    ctx: ComponentRenderContext[StateT],
) -> Content | None

Compose content when the viewport is large (120–159 cols).

See :meth:compose_extra_small.

Source code in xnano/components/component.py
@responsive_noop
def compose_large(
    self, ctx: ComponentRenderContext[StateT]
) -> Content | None:
    """Compose content when the viewport is large (120–159 cols).

    See :meth:`compose_extra_small`.
    """
    return None

compose_extra_large

compose_extra_large(
    ctx: ComponentRenderContext[StateT],
) -> Content | None

Compose content when the viewport is extra large (>= 160 cols).

See :meth:compose_extra_small.

Source code in xnano/components/component.py
@responsive_noop
def compose_extra_large(
    self, ctx: ComponentRenderContext[StateT]
) -> Content | None:
    """Compose content when the viewport is extra large (>= 160 cols).

    See :meth:`compose_extra_small`.
    """
    return None

handle_keyboard

handle_keyboard(keyboard: 'KeyboardEventData') -> bool

Optional keyboard handler while focused.

Returns:

  • bool

    True when the event was consumed.

Source code in xnano/components/component.py
def handle_keyboard(self, keyboard: "KeyboardEventData") -> bool:
    """Optional keyboard handler while focused.

    Returns:
        ``True`` when the event was consumed.
    """
    return False

handle_paste

handle_paste(text: str) -> bool

Optional paste handler while focused.

Returns:

  • bool

    True when the paste was consumed.

Source code in xnano/components/component.py
def handle_paste(self, text: str) -> bool:
    """Optional paste handler while focused.

    Returns:
        ``True`` when the paste was consumed.
    """
    return False

component_post_init

component_post_init() -> None

Clamp initial lengths and position.

Source code in xnano/components/scrollbar.py
def component_post_init(self) -> None:
    """Clamp initial lengths and position."""
    self._clamp_state()

from_scroll_handle classmethod

from_scroll_handle(
    handle: "ScrollHandle",
    *,
    content_length: int | None = None,
    viewport_length: int | None = None,
    orientation: ScrollbarOrientationLike = "vertical_right",
    thumb: str | None = None,
    track: str | None = None,
    begin: str | None = None,
    end: str | None = None,
    color: "ColorLike | None" = None,
    thumb_color: "ColorLike | None" = None,
    track_color: "ColorLike | None" = None,
    begin_color: "ColorLike | None" = None,
    end_color: "ColorLike | None" = None,
    visible: bool = True,
    z: int = 0,
    fit_content: bool = True
) -> "Scrollbar"

Build a scrollbar bound to a field scroll handle.

The handle owns offset / follow mode. Callers (or the field controller) should pass the measured content and viewport lengths; the component never reaches into grid private dictionaries.

Parameters:

  • handle ('ScrollHandle') –

    Resolved ScrollHandle for a scrolled field.

  • content_length (int | None, default: None ) –

    Total scrollable content size when known.

  • viewport_length (int | None, default: None ) –

    Visible window size when known.

  • orientation (ScrollbarOrientationLike, default: 'vertical_right' ) –

    Scrollbar edge placement.

  • thumb (str | None, default: None ) –

    Optional thumb glyph.

  • track (str | None, default: None ) –

    Optional track glyph.

  • begin (str | None, default: None ) –

    Optional leading glyph.

  • end (str | None, default: None ) –

    Optional trailing glyph.

  • color ('ColorLike | None', default: None ) –

    Overall foreground color.

  • thumb_color ('ColorLike | None', default: None ) –

    Thumb foreground color.

  • track_color ('ColorLike | None', default: None ) –

    Track foreground color.

  • begin_color ('ColorLike | None', default: None ) –

    Leading glyph color.

  • end_color ('ColorLike | None', default: None ) –

    Trailing glyph color.

  • visible (bool, default: True ) –

    Whether the scrollbar paints.

  • z (int, default: 0 ) –

    Paint order relative to siblings.

  • fit_content (bool, default: True ) –

    Whether layout uses the natural scrollbar size.

Returns:

  • 'Scrollbar'

    A Scrollbar that reads position from handle.

Source code in xnano/components/scrollbar.py
@classmethod
def from_scroll_handle(
    cls,
    handle: "ScrollHandle",
    *,
    content_length: int | None = None,
    viewport_length: int | None = None,
    orientation: ScrollbarOrientationLike = "vertical_right",
    thumb: str | None = None,
    track: str | None = None,
    begin: str | None = None,
    end: str | None = None,
    color: "ColorLike | None" = None,
    thumb_color: "ColorLike | None" = None,
    track_color: "ColorLike | None" = None,
    begin_color: "ColorLike | None" = None,
    end_color: "ColorLike | None" = None,
    visible: bool = True,
    z: int = 0,
    fit_content: bool = True,
) -> "Scrollbar":
    """Build a scrollbar bound to a field scroll handle.

    The handle owns offset / follow mode. Callers (or the field
    controller) should pass the measured content and viewport lengths;
    the component never reaches into grid private dictionaries.

    Args:
        handle: Resolved ``ScrollHandle`` for a scrolled field.
        content_length: Total scrollable content size when known.
        viewport_length: Visible window size when known.
        orientation: Scrollbar edge placement.
        thumb: Optional thumb glyph.
        track: Optional track glyph.
        begin: Optional leading glyph.
        end: Optional trailing glyph.
        color: Overall foreground color.
        thumb_color: Thumb foreground color.
        track_color: Track foreground color.
        begin_color: Leading glyph color.
        end_color: Trailing glyph color.
        visible: Whether the scrollbar paints.
        z: Paint order relative to siblings.
        fit_content: Whether layout uses the natural scrollbar size.

    Returns:
        A ``Scrollbar`` that reads ``position`` from ``handle``.
    """
    instance = cls(
        content_length=content_length or 0,
        position=int(getattr(handle, "offset", 0) or 0),
        viewport_length=viewport_length or 0,
        orientation=orientation,
        thumb=thumb,
        track=track,
        begin=begin,
        end=end,
        color=color,
        thumb_color=thumb_color,
        track_color=track_color,
        begin_color=begin_color,
        end_color=end_color,
        visible=visible,
        z=z,
        fit_content=fit_content,
    )
    instance._scroll_handle = handle
    instance._handle_content_length = content_length
    instance._handle_viewport_length = viewport_length
    return instance

compose

compose(ctx: 'ComponentRenderContext')

Compose scrollbar content with a native paint fallback.

Returns:

  • Interface-neutral content for this scrollbar.

Source code in xnano/components/scrollbar.py
def compose(self, ctx: "ComponentRenderContext"):
    """Compose scrollbar content with a native paint fallback.

    Returns:
        Interface-neutral content for this scrollbar.
    """
    return self._compose_content()

Column dataclass

Column(
    header: str | None = None,
    accessor: Callable[[Any], Any] | None = None,
    format: FormatResolver = None,
    foreground: ColorResolver = None,
    background: ColorResolver = None,
    horizontal_align: "Alignment | None" = None,
    width: int | float | None = None,
)

Bases: ComponentDescriptor

Describe one table column.

Attributes:

  • header (str | None) –

    Header displayed for the column.

  • accessor (Callable[[Any], Any] | None) –

    Row attribute, mapping key, or value callback.

  • format (FormatResolver) –

    Optional format string or value callback.

  • foreground (ColorResolver) –

    Cell foreground color.

  • background (ColorResolver) –

    Cell background color.

  • horizontal_align ('Alignment | None') –

    Cell alignment.

  • width (int | float | None) –

    Fixed or proportional column width.

Methods:

name class-attribute instance-attribute

name: str = dataclasses.field(default='', init=False)

Attribute name assigned by the component class.

header class-attribute instance-attribute

header: str | None = None

Displayed column header.

accessor class-attribute instance-attribute

accessor: Callable[[Any], Any] | None = None

Custom row value accessor.

format class-attribute instance-attribute

format: FormatResolver = None

Value formatter.

foreground class-attribute instance-attribute

foreground: ColorResolver = None

Foreground color or resolver.

background class-attribute instance-attribute

background: ColorResolver = None

Background color or resolver.

horizontal_align class-attribute instance-attribute

horizontal_align: 'Alignment | None' = None

Cell text alignment.

width class-attribute instance-attribute

width: int | float | None = None

Fixed width or fractional width.

resolve_header

resolve_header() -> str

Return the displayed header.

Source code in xnano/components/schema.py
def resolve_header(self) -> str:
    """Return the displayed header."""
    return self.header or self.name.replace("_", " ").title()

resolve_value

resolve_value(row: Any) -> Any

Read this column's value from a row.

Source code in xnano/components/schema.py
def resolve_value(self, row: Any) -> Any:
    """Read this column's value from a row."""
    if self.accessor is not None:
        return self.accessor(row)
    if isinstance(row, dict):
        return row.get(self.name)
    return getattr(row, self.name, None)

resolve_text

resolve_text(value: Any) -> str

Format a value for display.

Source code in xnano/components/schema.py
def resolve_text(self, value: Any) -> str:
    """Format a value for display."""
    if value is None:
        return ""
    if self.format is None:
        return str(value)
    if isinstance(self.format, str):
        return self.format.format(value)
    return self.format(value)

resolve_color

resolve_color(value: Any) -> Any

Resolve the foreground color for a value.

Source code in xnano/components/schema.py
def resolve_color(self, value: Any) -> Any:
    """Resolve the foreground color for a value."""
    resolver = self.foreground
    return resolver(value) if callable(resolver) else resolver

resolve_background

resolve_background(value: Any) -> Any

Resolve the background color for a value.

Source code in xnano/components/schema.py
def resolve_background(self, value: Any) -> Any:
    """Resolve the background color for a value."""
    return (
        self.background(value)
        if callable(self.background)
        else self.background
    )

Table dataclass

Table(
    data: list[Any] = list(),
    columns: ColumnsArg = None,
    selected: int | None = None,
    show_header: bool = True,
    column_spacing: int = 1,
    highlight_color: "ColorLike | None" = None,
    highlight_background: "ColorLike | None" = None,
    highlight_symbol: str | None = None,
    focusable: bool = False,
    passthrough: Sequence[str] = (),
    sort: str | None = None,
    sort_direction: SortDirection = "ascending",
    *,
    visible: bool = True,
    z: int = 0,
    fit_content: bool = True
)

Bases: Component

Declarative data table.

Feed it a list of rows — dicts, dataclasses, or arbitrary objects — and the columns are derived for you. Selection is a single attribute. For full control, subclass and declare Column descriptors.

Example

Table(data=({"name": "Ada", "role": "Engineer"},))

Attributes:

Methods:

  • component_post_init

    Initialize subclass state after dataclass fields are assigned.

  • get_frame

    Optional frame/panel chrome around composed content.

  • get_size

    Return the preferred cell size of this component.

  • before_render

    Called before rendering; returns the effective render area.

  • after_render

    Called after rendering for optional post-paint work.

  • compose_extra_small

    Compose content when the viewport is extra small (< 40 cols).

  • compose_small

    Compose content when the viewport is small (40–79 cols).

  • compose_medium

    Compose content when the viewport is medium (80–119 cols).

  • compose_large

    Compose content when the viewport is large (120–159 cols).

  • compose_extra_large

    Compose content when the viewport is extra large (>= 160 cols).

  • handle_paste

    Optional paste handler while focused.

  • move

    Move the selection by delta rows in display order.

  • handle_keyboard

    Navigate selection when focusable is enabled.

  • compose

    Compose TableGrid content with a native TableNode paint fallback.

visible class-attribute instance-attribute

visible: bool = dataclasses.field(
    default=True, kw_only=True
)

Whether this component paints at all.

z class-attribute instance-attribute

z: int = dataclasses.field(default=0, kw_only=True)

Stacking order among sibling content.

fit_content class-attribute instance-attribute

fit_content: bool = dataclasses.field(
    default=True, kw_only=True
)

When True, paint at natural size inside a larger slot.

focused property

focused: bool

Whether this component currently holds field focus.

data class-attribute instance-attribute

data: list[Any] = dataclasses.field(default_factory=list)

The rows — dicts, dataclasses, or objects with attributes.

columns class-attribute instance-attribute

columns: ColumnsArg = None

Optional column overrides for the data-driven path.

selected class-attribute instance-attribute

selected: int | None = None

Highlighted row index in display order.

show_header class-attribute instance-attribute

show_header: bool = True

Whether to render the derived header row.

column_spacing class-attribute instance-attribute

column_spacing: int = 1

Space between columns in terminal columns.

highlight_color class-attribute instance-attribute

highlight_color: 'ColorLike | None' = None

Selection foreground color.

highlight_background class-attribute instance-attribute

highlight_background: 'ColorLike | None' = None

Selection background color.

highlight_symbol class-attribute instance-attribute

highlight_symbol: str | None = None

Glyph prepended to the selected row.

focusable class-attribute instance-attribute

focusable: bool = False

Whether keyboard navigation is enabled.

passthrough class-attribute instance-attribute

passthrough: Sequence[str] = ()

Key bindings that bubble without being consumed.

sort class-attribute instance-attribute

sort: str | None = None

Optional column name used for a derived sort index.

sort_direction class-attribute instance-attribute

sort_direction: SortDirection = 'ascending'

Ascending or descending sort order.

selected_row property

selected_row: Any | None

Return the source row currently selected, if any.

value property

value: Any | None

Alias for selected_row.

component_post_init

component_post_init() -> None

Initialize subclass state after dataclass fields are assigned.

Override this method instead of __post_init__.

Source code in xnano/components/component.py
def component_post_init(self) -> None:
    """Initialize subclass state after dataclass fields are assigned.

    Override this method instead of ``__post_init__``.
    """
    return None

get_frame

get_frame() -> Any | None

Optional frame/panel chrome around composed content.

Source code in xnano/components/component.py
def get_frame(self) -> Any | None:
    """Optional frame/panel chrome around composed content."""
    return None

get_size

get_size(ctx: ComponentRenderContext[StateT]) -> Size

Return the preferred cell size of this component.

Source code in xnano/components/component.py
def get_size(self, ctx: ComponentRenderContext[StateT]) -> Size:
    """Return the preferred cell size of this component."""
    return Size(width=0, height=0)

before_render

before_render(
    ctx: ComponentRenderContext[StateT], area: "Area"
) -> "Area"

Called before rendering; returns the effective render area.

Source code in xnano/components/component.py
def before_render(
    self,
    ctx: ComponentRenderContext[StateT],
    area: "Area",
) -> "Area":
    """Called before rendering; returns the effective render area."""
    return area

after_render

after_render(
    ctx: ComponentRenderContext[StateT], area: "Area"
) -> None

Called after rendering for optional post-paint work.

Source code in xnano/components/component.py
def after_render(
    self,
    ctx: ComponentRenderContext[StateT],
    area: "Area",
) -> None:
    """Called after rendering for optional post-paint work."""
    return None

compose_extra_small

compose_extra_small(
    ctx: ComponentRenderContext[StateT],
) -> Content | None

Compose content when the viewport is extra small (< 40 cols).

Optional responsive counterpart to :meth:compose. When overridden, it is used instead of compose while the window is in this size tier. Overriding any compose_* variant opts the component into breakpoint dispatch; a component that overrides none pays no per-frame cost.

Source code in xnano/components/component.py
@responsive_noop
def compose_extra_small(
    self, ctx: ComponentRenderContext[StateT]
) -> Content | None:
    """Compose content when the viewport is extra small (< 40 cols).

    Optional responsive counterpart to :meth:`compose`. When
    overridden, it is used *instead of* ``compose`` while the window
    is in this size tier. Overriding any ``compose_*`` variant opts the
    component into breakpoint dispatch; a component that overrides none
    pays no per-frame cost.
    """
    return None

compose_small

compose_small(
    ctx: ComponentRenderContext[StateT],
) -> Content | None

Compose content when the viewport is small (40–79 cols).

See :meth:compose_extra_small.

Source code in xnano/components/component.py
@responsive_noop
def compose_small(
    self, ctx: ComponentRenderContext[StateT]
) -> Content | None:
    """Compose content when the viewport is small (40–79 cols).

    See :meth:`compose_extra_small`.
    """
    return None

compose_medium

compose_medium(
    ctx: ComponentRenderContext[StateT],
) -> Content | None

Compose content when the viewport is medium (80–119 cols).

See :meth:compose_extra_small.

Source code in xnano/components/component.py
@responsive_noop
def compose_medium(
    self, ctx: ComponentRenderContext[StateT]
) -> Content | None:
    """Compose content when the viewport is medium (80–119 cols).

    See :meth:`compose_extra_small`.
    """
    return None

compose_large

compose_large(
    ctx: ComponentRenderContext[StateT],
) -> Content | None

Compose content when the viewport is large (120–159 cols).

See :meth:compose_extra_small.

Source code in xnano/components/component.py
@responsive_noop
def compose_large(
    self, ctx: ComponentRenderContext[StateT]
) -> Content | None:
    """Compose content when the viewport is large (120–159 cols).

    See :meth:`compose_extra_small`.
    """
    return None

compose_extra_large

compose_extra_large(
    ctx: ComponentRenderContext[StateT],
) -> Content | None

Compose content when the viewport is extra large (>= 160 cols).

See :meth:compose_extra_small.

Source code in xnano/components/component.py
@responsive_noop
def compose_extra_large(
    self, ctx: ComponentRenderContext[StateT]
) -> Content | None:
    """Compose content when the viewport is extra large (>= 160 cols).

    See :meth:`compose_extra_small`.
    """
    return None

handle_paste

handle_paste(text: str) -> bool

Optional paste handler while focused.

Returns:

  • bool

    True when the paste was consumed.

Source code in xnano/components/component.py
def handle_paste(self, text: str) -> bool:
    """Optional paste handler while focused.

    Returns:
        ``True`` when the paste was consumed.
    """
    return False

move

move(delta: int) -> int | None

Move the selection by delta rows in display order.

Parameters:

  • delta (int) –

    Positive moves down; negative moves up.

Returns:

  • int | None

    The new selected index, or None when there is no data.

Source code in xnano/components/table.py
def move(self, delta: int) -> int | None:
    """Move the selection by ``delta`` rows in display order.

    Args:
        delta: Positive moves down; negative moves up.

    Returns:
        The new selected index, or ``None`` when there is no data.
    """
    count = len(self.data)
    if count == 0:
        self.selected = None
        return None
    current = 0 if self.selected is None else self.selected
    self.selected = max(0, min(count - 1, current + delta))
    return self.selected

handle_keyboard

handle_keyboard(keyboard: 'KeyboardEventData') -> bool

Navigate selection when focusable is enabled.

Returns:

  • bool

    True when the event was consumed.

Source code in xnano/components/table.py
def handle_keyboard(self, keyboard: "KeyboardEventData") -> bool:
    """Navigate selection when ``focusable`` is enabled.

    Returns:
        ``True`` when the event was consumed.
    """
    if not self.focusable:
        return False
    for binding in self.passthrough:
        if keyboard.matches(binding):
            return False
    if keyboard.matches("up", "k"):
        self.move(-1)
        return True
    if keyboard.matches("down", "j"):
        self.move(1)
        return True
    if keyboard.matches("home"):
        if self.data:
            self.selected = 0
        return True
    if keyboard.matches("end"):
        if self.data:
            self.selected = len(self.data) - 1
        return True
    if keyboard.matches("pageup"):
        self.move(-10)
        return True
    if keyboard.matches("pagedown"):
        self.move(10)
        return True
    return False

compose

compose(ctx: 'ComponentRenderContext')

Compose TableGrid content with a native TableNode paint fallback.

Returns:

  • Interface-neutral content for this table.

Source code in xnano/components/table.py
def compose(self, ctx: "ComponentRenderContext"):
    """Compose TableGrid content with a native TableNode paint fallback.

    Returns:
        Interface-neutral content for this table.
    """
    return self._compose_table_grid()

Text dataclass

Text(
    content: str | Text | list[str | Text] = "",
    foreground: ColorLike | None = None,
    background: ColorLike | None = None,
    modifiers: tuple[CharacterModifier, ...] = (),
    horizontal_align: Alignment | None = None,
    vertical_align: VerticalAlignment | None = None,
    wrap: bool = True,
    input: bool = False,
    placeholder: str | Text | None = None,
    cursor: int | None = None,
    multiline: bool = False,
    rows: int | None = None,
    ansi: bool = False,
    markdown: bool = False,
    language: str | None = None,
    passthrough: Sequence[str] = (),
    mask: str | None = None,
    max_length: int | None = None,
    read_only: bool = False,
    tab_size: int = 4,
    focusable: bool = False,
    fill: bool = False,
    *,
    visible: bool = True,
    z: int = 0,
    fit_content: bool = True
)

Bases: Component

Display styled, marked-up, highlighted, or editable text.

Nest Text values for independently styled spans. Enable ANSI, Markdown, or syntax highlighting for formatted content, or set input to make a leaf editable.

Example

Text(content="Ready", color="green", modifiers=("bold",))

Attributes:

Methods:

  • get_frame

    Optional frame/panel chrome around composed content.

  • get_size

    Return the preferred cell size of this component.

  • before_render

    Called before rendering; returns the effective render area.

  • compose_extra_small

    Compose content when the viewport is extra small (< 40 cols).

  • compose_small

    Compose content when the viewport is small (40–79 cols).

  • compose_medium

    Compose content when the viewport is medium (80–119 cols).

  • compose_large

    Compose content when the viewport is large (120–159 cols).

  • compose_extra_large

    Compose content when the viewport is extra large (>= 160 cols).

  • component_post_init

    Validate modes and initialize the native editor when needed.

  • after_render

    Record the multi-line editor caret so the terminal cursor tracks it.

  • handle_paste

    Insert pasted text at the caret of a multi-line editor.

  • compose

    Compose interface-neutral content for this Text.

  • handle_keyboard

    Edit this text when it has focus.

  • get_terminal_node

    Return composed content for terminal compatibility.

visible class-attribute instance-attribute

visible: bool = dataclasses.field(
    default=True, kw_only=True
)

Whether this component paints at all.

z class-attribute instance-attribute

z: int = dataclasses.field(default=0, kw_only=True)

Stacking order among sibling content.

fit_content class-attribute instance-attribute

fit_content: bool = dataclasses.field(
    default=True, kw_only=True
)

When True, paint at natural size inside a larger slot.

focused property

focused: bool

Whether this component currently holds field focus.

content class-attribute instance-attribute

content: str | Text | list[str | Text] = dataclasses.field(
    default=""
)

Plain string, nested Text, or a list of either.

foreground class-attribute instance-attribute

foreground: ColorLike | None = None

Foreground color (deprecated alias: color).

background class-attribute instance-attribute

background: ColorLike | None = None

Background color.

modifiers class-attribute instance-attribute

modifiers: tuple[CharacterModifier, ...] = ()

Character modifiers such as bold or underline.

horizontal_align class-attribute instance-attribute

horizontal_align: Alignment | None = None

Horizontal alignment at the paragraph level.

vertical_align class-attribute instance-attribute

vertical_align: VerticalAlignment | None = None

Vertical alignment within the painted area.

wrap class-attribute instance-attribute

wrap: bool = True

Whether long lines may wrap.

input class-attribute instance-attribute

input: bool = False

When True on a leaf, the component is an editable field.

placeholder class-attribute instance-attribute

placeholder: str | Text | None = None

Shown when input is empty and unfocused.

cursor class-attribute instance-attribute

cursor: int | None = None

Caret index for single-line input; None means end of string.

multiline class-attribute instance-attribute

multiline: bool = False

When True with input, editing uses CoreTextEditor.

rows class-attribute instance-attribute

rows: int | None = None

Preferred visible height (lines) for a multiline input.

ansi class-attribute instance-attribute

ansi: bool = False

Parse ANSI SGR sequences in leaf content.

markdown class-attribute instance-attribute

markdown: bool = False

Parse leaf content as markdown.

language class-attribute instance-attribute

language: str | None = None

Pygments lexer name for syntax highlighting only.

passthrough class-attribute instance-attribute

passthrough: Sequence[str] = ()

Key bindings this input never captures while focused.

mask class-attribute instance-attribute

mask: str | None = None

Display-only mask character(s); real value is preserved.

max_length class-attribute instance-attribute

max_length: int | None = None

Maximum plain-string length; longer input is clamped.

read_only class-attribute instance-attribute

read_only: bool = False

Reject edits while remaining focusable.

tab_size class-attribute instance-attribute

tab_size: int = 4

Tab width applied to multiline tab insertion and paste.

focusable class-attribute instance-attribute

focusable: bool = False

Whether this component participates in field focus.

fill class-attribute instance-attribute

fill: bool = False

Whether background fills the whole slot rather than only the text glyphs. When True short lines still paint the background across the full cell width (no manual right-padding required).

owns_cursor property

owns_cursor: bool

Whether this Text paints its own caret (multi-line editor).

cursor_position property

cursor_position: tuple[int, int] | None

Absolute caret cell for the terminal cursor, set during paint.

Reported only for a focused multi-line editor (a single-line input paints its own caret inline). None otherwise, which hides the hardware cursor.

value property writable

value: str

Canonical plain-string content for leaf and input modes.

get_frame

get_frame() -> Any | None

Optional frame/panel chrome around composed content.

Source code in xnano/components/component.py
def get_frame(self) -> Any | None:
    """Optional frame/panel chrome around composed content."""
    return None

get_size

get_size(ctx: ComponentRenderContext[StateT]) -> Size

Return the preferred cell size of this component.

Source code in xnano/components/component.py
def get_size(self, ctx: ComponentRenderContext[StateT]) -> Size:
    """Return the preferred cell size of this component."""
    return Size(width=0, height=0)

before_render

before_render(
    ctx: ComponentRenderContext[StateT], area: "Area"
) -> "Area"

Called before rendering; returns the effective render area.

Source code in xnano/components/component.py
def before_render(
    self,
    ctx: ComponentRenderContext[StateT],
    area: "Area",
) -> "Area":
    """Called before rendering; returns the effective render area."""
    return area

compose_extra_small

compose_extra_small(
    ctx: ComponentRenderContext[StateT],
) -> Content | None

Compose content when the viewport is extra small (< 40 cols).

Optional responsive counterpart to :meth:compose. When overridden, it is used instead of compose while the window is in this size tier. Overriding any compose_* variant opts the component into breakpoint dispatch; a component that overrides none pays no per-frame cost.

Source code in xnano/components/component.py
@responsive_noop
def compose_extra_small(
    self, ctx: ComponentRenderContext[StateT]
) -> Content | None:
    """Compose content when the viewport is extra small (< 40 cols).

    Optional responsive counterpart to :meth:`compose`. When
    overridden, it is used *instead of* ``compose`` while the window
    is in this size tier. Overriding any ``compose_*`` variant opts the
    component into breakpoint dispatch; a component that overrides none
    pays no per-frame cost.
    """
    return None

compose_small

compose_small(
    ctx: ComponentRenderContext[StateT],
) -> Content | None

Compose content when the viewport is small (40–79 cols).

See :meth:compose_extra_small.

Source code in xnano/components/component.py
@responsive_noop
def compose_small(
    self, ctx: ComponentRenderContext[StateT]
) -> Content | None:
    """Compose content when the viewport is small (40–79 cols).

    See :meth:`compose_extra_small`.
    """
    return None

compose_medium

compose_medium(
    ctx: ComponentRenderContext[StateT],
) -> Content | None

Compose content when the viewport is medium (80–119 cols).

See :meth:compose_extra_small.

Source code in xnano/components/component.py
@responsive_noop
def compose_medium(
    self, ctx: ComponentRenderContext[StateT]
) -> Content | None:
    """Compose content when the viewport is medium (80–119 cols).

    See :meth:`compose_extra_small`.
    """
    return None

compose_large

compose_large(
    ctx: ComponentRenderContext[StateT],
) -> Content | None

Compose content when the viewport is large (120–159 cols).

See :meth:compose_extra_small.

Source code in xnano/components/component.py
@responsive_noop
def compose_large(
    self, ctx: ComponentRenderContext[StateT]
) -> Content | None:
    """Compose content when the viewport is large (120–159 cols).

    See :meth:`compose_extra_small`.
    """
    return None

compose_extra_large

compose_extra_large(
    ctx: ComponentRenderContext[StateT],
) -> Content | None

Compose content when the viewport is extra large (>= 160 cols).

See :meth:compose_extra_small.

Source code in xnano/components/component.py
@responsive_noop
def compose_extra_large(
    self, ctx: ComponentRenderContext[StateT]
) -> Content | None:
    """Compose content when the viewport is extra large (>= 160 cols).

    See :meth:`compose_extra_small`.
    """
    return None

component_post_init

component_post_init() -> None

Validate modes and initialize the native editor when needed.

Source code in xnano/components/text.py
def component_post_init(self) -> None:
    """Validate modes and initialize the native editor when needed."""
    self._validate_display_modes()
    if self.input:
        self.focusable = True
    self._sync_editor_state()

after_render

after_render(
    ctx: "ComponentRenderContext[Any]", area: "Area"
) -> None

Record the multi-line editor caret so the terminal cursor tracks it.

Source code in xnano/components/text.py
def after_render(
    self,
    ctx: "ComponentRenderContext[Any]",
    area: "Area",
) -> None:
    """Record the multi-line editor caret so the terminal cursor tracks it."""
    if not (self._input_focused and self._editor is not None):
        self._cursor_position = None
        return
    row, column = self._editor.cursor()
    # ponytail: no soft-wrap/scroll accounting; clamps to the slot, which
    # is exact for a note-sized editor. Add offsets if the body scrolls.
    x = area.x + max(0, min(column, max(0, area.width - 1)))
    y = area.y + max(0, min(row, max(0, area.height - 1)))
    self._cursor_position = (x, y)

handle_paste

handle_paste(text: str) -> bool

Insert pasted text at the caret of a multi-line editor.

Parameters:

  • text (str) –

    The pasted clipboard text.

Returns:

  • bool

    True when the paste was consumed by the native editor.

Source code in xnano/components/text.py
def handle_paste(self, text: str) -> bool:
    """Insert pasted text at the caret of a multi-line editor.

    Args:
        text: The pasted clipboard text.

    Returns:
        ``True`` when the paste was consumed by the native editor.
    """
    if self._editor is None:
        return False
    if self.read_only:
        return True
    text = self._clamp_text(text)
    if self.max_length is not None:
        remaining = self.max_length - len(self._editor.text())
        if remaining <= 0:
            return True
        text = text[:remaining]
    self._editor.insert_text(text)
    object.__setattr__(self, "content", self._editor.text())
    return True

compose

compose(
    ctx: ComponentRenderContext[Any],
) -> TextBlock | Native | Panel | None

Compose interface-neutral content for this Text.

Parameters:

Returns:

Source code in xnano/components/text.py
def compose(
    self, ctx: ComponentRenderContext[Any]
) -> TextBlock | Native | Panel | None:
    """Compose interface-neutral content for this Text.

    Args:
        ctx: Render-time scope for this paint.

    Returns:
        A ``TextBlock``, ``Native`` editor payload, or nested content.
        When ``fill`` is set the block is wrapped in a background
        ``Panel`` so the color spans the full slot.
    """
    content = self._compose_content(ctx)
    if (
        self.fill
        and self.background is not None
        and isinstance(content, TextBlock)
    ):
        return Panel(child=content, background=self.background)
    return content

handle_keyboard

handle_keyboard(keyboard: 'KeyboardEventData') -> bool

Edit this text when it has focus.

Passthrough bindings remain available to hooks. Read-only inputs reject edits, and max_length limits inserted content.

Parameters:

  • keyboard ('KeyboardEventData') –

    The keyboard event payload.

Returns:

  • bool

    True when the key was consumed as text editing.

Source code in xnano/components/text.py
def handle_keyboard(self, keyboard: "KeyboardEventData") -> bool:
    """Edit this text when it has focus.

    Passthrough bindings remain available to hooks. Read-only inputs reject
    edits, and ``max_length`` limits inserted content.

    Args:
        keyboard: The keyboard event payload.

    Returns:
        ``True`` when the key was consumed as text editing.
    """
    if self.passthrough and keyboard.matches(*self.passthrough):
        return False
    if not self.input:
        return False

    if self._editor is not None:
        return self._handle_editor_keyboard(keyboard)
    return self._handle_single_line_keyboard(keyboard)

get_terminal_node

get_terminal_node(
    ctx: ComponentRenderContext[Any],
) -> TextBlock | Native | Panel | None

Return composed content for terminal compatibility.

Parameters:

Returns:

Source code in xnano/components/text.py
def get_terminal_node(
    self, ctx: ComponentRenderContext[Any]
) -> TextBlock | Native | Panel | None:
    """Return composed content for terminal compatibility.

    Args:
        ctx: Render-time scope for this paint.

    Returns:
        The same interface-neutral content as ``compose``.
    """
    return self.compose(ctx)