Skip to content

xnano

xnano

xnano


Build terminal and web interfaces with grids, fields, components, hooks, actions, and runtimes.

Modules:

Classes:

  • Action

    Declarative event trigger.

  • Command

    A command or subcommand group.

  • Component

    Base class for custom xnano components.

  • Context

    Values and controls available inside an event hook.

  • Frame

    Immutable snapshot of one painted native frame.

  • Runtime

    Drive one application through an xnano_core session.

  • BaseGrid

    Declarative layout container for a terminal-based UI.

  • GridSettings

    Rendering, layout, and frame settings for a BaseGrid subclass.

  • Style

    Style derived from Tailwind utility classes.

  • Terminal

    Paint and interact with an application in a terminal.

  • Web

    Serve an application in a browser from an offscreen runtime.

Functions:

  • Field

    Create a new grid field info instance.

  • on_action

    Bind a prebuilt Action as a hook trigger.

  • on_click

    Register a click handler for a grid layout field, or a group.

  • on_clipboard

    Register a hook fired on clipboard paste events.

  • on_event

    Register an event hook that triggers on every detected event.

  • on_field

    Fire the decorated handler based on this grid's own field values.

  • on_focus

    Register a focus hook for the terminal window, a field, or a group.

  • on_keyboard

    Register a keyboard event hook.

  • on_mouse

    Register a mouse event hook.

  • on_poll

    Register a poll hook that fires on idle event waits or every frame.

  • on_resize

    Register a hook fired on terminal resize events.

  • on_state

    Fire the decorated handler based on the application state.

  • on_tick

    Register a tick hook, optionally at a fixed interval.

  • render

    Display renderables as print-like terminal output.

Action dataclass

Action()

Bases: ABC

Declarative event trigger.

Examples:

save = Action.keyboard("ctrl+s")

@on_action(save)
def save_document(self) -> None:
    ...

terminal.actions.perform(save)

Methods:

  • keyboard

    Match one or more keyboard bindings.

  • mouse

    Match a mouse event.

  • click

    Match a button press on a field.

  • focus

    Match a focus transition.

  • clipboard

    Match pasted text.

  • tick

    Match a clock tick.

  • resize

    Match a resize event.

  • request

    Match an HTTP request.

  • matches

    Return whether event satisfies this action.

keyboard classmethod

keyboard(
    *bindings: KeyboardBinding,
    kind: KeyboardEventKind | None = None
) -> "KeyboardAction"

Match one or more keyboard bindings.

Source code in xnano/actions.py
@classmethod
def keyboard(
    cls,
    *bindings: KeyboardBinding,
    kind: KeyboardEventKind | None = None,
) -> "KeyboardAction":
    """Match one or more keyboard bindings."""
    return KeyboardAction(bindings=bindings, kind=kind)

mouse classmethod

mouse(
    *buttons: MouseButton,
    kind: MouseEventKind | None = None
) -> "MouseAction"

Match a mouse event.

Source code in xnano/actions.py
@classmethod
def mouse(
    cls,
    *buttons: MouseButton,
    kind: MouseEventKind | None = None,
) -> "MouseAction":
    """Match a mouse event."""
    return MouseAction(buttons=buttons, kind=kind)

click classmethod

click(
    field: str | None = None, button: MouseButton = "left"
) -> "ClickAction"

Match a button press on a field.

Source code in xnano/actions.py
@classmethod
def click(
    cls,
    field: str | None = None,
    button: MouseButton = "left",
) -> "ClickAction":
    """Match a button press on a field."""
    return ClickAction(field=field, button=button)

focus classmethod

focus(
    field: str | None = None,
    kind: FocusEventKind | None = None,
) -> "FocusAction"

Match a focus transition.

Source code in xnano/actions.py
@classmethod
def focus(
    cls,
    field: str | None = None,
    kind: FocusEventKind | None = None,
) -> "FocusAction":
    """Match a focus transition."""
    return FocusAction(field=field, kind=kind)

clipboard classmethod

clipboard(text: str | None = None) -> 'ClipboardAction'

Match pasted text.

Source code in xnano/actions.py
@classmethod
def clipboard(cls, text: str | None = None) -> "ClipboardAction":
    """Match pasted text."""
    return ClipboardAction(text=text)

tick classmethod

tick(interval_ms: int = 0) -> 'TickAction'

Match a clock tick.

Source code in xnano/actions.py
@classmethod
def tick(cls, interval_ms: int = 0) -> "TickAction":
    """Match a clock tick."""
    return TickAction(interval_ms=interval_ms)

resize classmethod

resize(
    width: int | None = None, height: int | None = None
) -> "ResizeAction"

Match a resize event.

Source code in xnano/actions.py
@classmethod
def resize(
    cls,
    width: int | None = None,
    height: int | None = None,
) -> "ResizeAction":
    """Match a resize event."""
    return ResizeAction(width=width, height=height)

request classmethod

request(method: str, path: str = '/') -> 'RequestAction'

Match an HTTP request.

Source code in xnano/actions.py
@classmethod
def request(cls, method: str, path: str = "/") -> "RequestAction":
    """Match an HTTP request."""
    return RequestAction(method=method.upper(), path=path)

matches abstractmethod

matches(event: Any) -> bool

Return whether event satisfies this action.

Source code in xnano/actions.py
@abc.abstractmethod
def matches(self, event: Any) -> bool:
    """Return whether ``event`` satisfies this action."""

Command dataclass

Command(
    name: str | None = None,
    description: str | None = None,
    strict: bool = False,
    show_help: bool = True,
    help: bool = True,
)

A command or subcommand group.

Attributes:

Example

command = Command(name="hello") @command ... def greet(name: str) -> str: ... return f"Hello, {name}" command.run(["Ada"]) 'Hello, Ada'

Methods:

  • option

    Attach option names and help text to a command parameter.

  • command

    Decorator to register a subcommand.

  • register_callback

    Register the main callback for this command.

  • add_subcommand

    Add a subcommand programmatically.

  • parse_arguments

    Parse arguments without exiting the process.

  • run

    Parse arguments and run the command, exiting on errors/help.

  • get_help

    Return plain help text for this command.

  • __call__

    Register a callback or run with sys.argv.

name class-attribute instance-attribute

name: str | None = None

Command name shown in usage.

description class-attribute instance-attribute

description: str | None = None

Command summary shown in help.

strict class-attribute instance-attribute

strict: bool = False

Whether annotations are validated strictly.

show_help class-attribute instance-attribute

show_help: bool = True

Whether -h and --help are enabled.

help class-attribute instance-attribute

help: bool = True

Compatibility alias for show_help.

UNSET class-attribute instance-attribute

UNSET: Any = dataclasses.field(
    default=UNSET, init=False, repr=False
)

Sentinel used for parameters without defaults.

parameters property

parameters: list[_Parameter]

Resolved parameters for help rendering.

subcommands property

subcommands: dict[str, 'Command']

Registered subcommands.

option staticmethod

option(
    name_or_flags: str | list[str],
    *,
    default: Any = None,
    help: str | None = None,
    is_flag: bool | None = None
) -> Callable[[Callable[..., Any]], Callable[..., Any]]

Attach option names and help text to a command parameter.

Source code in xnano/cli/command.py
@staticmethod
def option(
    name_or_flags: str | list[str],
    *,
    default: Any = None,
    help: str | None = None,
    is_flag: bool | None = None,
) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
    """Attach option names and help text to a command parameter."""

    def decorator(func: Callable[..., Any]) -> Callable[..., Any]:
        options = getattr(func, "_cli_options", None)
        if options is None:
            options = []
            setattr(func, "_cli_options", options)
        if isinstance(name_or_flags, str):
            flags = [name_or_flags]
        else:
            flags = list(name_or_flags)
        options.append(
            {
                "flags": flags,
                "default": default,
                "help": help,
                "is_flag": is_flag,
            }
        )
        return func

    return decorator

command

command(
    name: str | None = None,
    *,
    description: str | None = None
) -> Callable[[Callable[..., Any]], Callable[..., Any]]

Decorator to register a subcommand.

Source code in xnano/cli/command.py
def command(
    self,
    name: str | None = None,
    *,
    description: str | None = None,
) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
    """Decorator to register a subcommand."""

    def decorator(func: Callable[..., Any]) -> Callable[..., Any]:
        func_name = getattr(func, "__name__", None) or "command"
        command_name = name or func_name.replace("_", "-")
        command_description = description or func.__doc__
        subcommand = Command(
            name=command_name,
            description=command_description,
            strict=self.strict,
            help=self.help,
        )
        subcommand._callback = func
        subcommand._register_from_function(func)
        self._subcommands[command_name] = subcommand
        self._parser_dirty = True
        return func

    return decorator

register_callback

register_callback(
    func: Callable[..., Any],
) -> Callable[..., Any]

Register the main callback for this command.

Source code in xnano/cli/command.py
def register_callback(
    self, func: Callable[..., Any]
) -> Callable[..., Any]:
    """Register the main callback for this command."""
    self._callback = func
    self._register_from_function(func)
    self._parser_dirty = True
    return func

add_subcommand

add_subcommand(subcommand: 'Command') -> None

Add a subcommand programmatically.

Source code in xnano/cli/command.py
def add_subcommand(self, subcommand: "Command") -> None:
    """Add a subcommand programmatically."""
    if not subcommand.name:
        raise CliError("Subcommand must have a name to be added.")
    self._subcommands[subcommand.name] = subcommand
    self._parser_dirty = True

parse_arguments

parse_arguments(
    arguments: list[str],
) -> tuple["Command", dict[str, Any]]

Parse arguments without exiting the process.

Raises:

Source code in xnano/cli/command.py
def parse_arguments(
    self, arguments: list[str]
) -> tuple["Command", dict[str, Any]]:
    """Parse arguments without exiting the process.

    Raises:
        HelpRequested: When help was requested.
        CliError: On usage / validation failures.
    """
    # Prefer the stable hand-parser semantics for drop-in parity with
    # existing tests, while exposing argparse caching for future work.
    return self._parse_manual(arguments)

run

run(arguments: list[str] | None = None) -> Any

Parse arguments and run the command, exiting on errors/help.

Source code in xnano/cli/command.py
def run(self, arguments: list[str] | None = None) -> Any:
    """Parse arguments and run the command, exiting on errors/help."""
    if arguments is None:
        arguments = sys.argv[1:]
    try:
        target, parsed = self.parse_arguments(arguments)
    except HelpRequested as help_requested:
        print(render_help(help_requested.command), end="")
        sys.exit(0)
    except HelpException as help_exception:
        print(render_help(help_exception.command), end="")
        sys.exit(0)
    except CliError as error:
        print_error(error.message, error.command or self)
        sys.exit(error.exit_code)
    except ValueError as error:
        # Compatibility with callers raising plain ValueError.
        print_error(str(error), self)
        sys.exit(2)

    if target._callback is None:
        print(render_help(target), end="")
        sys.exit(0)
    return target._callback(**parsed)

get_help

get_help() -> str

Return plain help text for this command.

Source code in xnano/cli/command.py
def get_help(self) -> str:
    """Return plain help text for this command."""
    return format_plain_help(self)

__call__

__call__(*args: Any, **kwargs: Any) -> Any

Register a callback or run with sys.argv.

Source code in xnano/cli/command.py
def __call__(self, *args: Any, **kwargs: Any) -> Any:
    """Register a callback or run with ``sys.argv``."""
    if len(args) == 1 and callable(args[0]) and not kwargs:
        return self.register_callback(args[0])
    return self.run(sys.argv[1:])

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

Context dataclass

Context(
    event: "Event",
    terminal: "Runtime[StateT]",
    state: StateT,
    request: "Request | None" = None,
)

Bases: Generic[StateT]

Values and controls available inside an event hook.

Use the event-specific shortcuts such as keyboard_event and mouse_event, read or update application state, move focus, or access the current cursor, device, actions, and stage.

Attributes:

  • event ('Event') –

    Event that triggered the hook.

  • terminal ('Runtime[StateT]') –

    Terminal or runtime handling the event.

  • state (StateT) –

    Application state shared with the runtime.

  • host ('Runtime[StateT]') –

    Session handling the event.

  • runtime ('Runtime[StateT]') –

    Runtime handling the event.

  • surface (str) –

    Active presentation surface.

  • request ('Request | None') –

    HTTP request that triggered the hook, if any.

  • tick_event ('TickEventData | None') –

    Tick payload that triggered the hook, if any.

  • keyboard_event ('KeyboardEventData | None') –

    Keyboard payload that triggered the hook, if any.

  • mouse_event ('MouseEventData | None') –

    Mouse payload that triggered the hook, if any.

  • cursor ('Cursor') –

    Cursor controls for the active runtime.

  • device ('Device') –

    Device controls for the active runtime.

  • actions ('Actions') –

    Synthetic action performer.

  • stage ('Stage') –

    Current layout stage.

  • focused_group (str | None) –

    Name of the focused field group.

Example

def handle_key(ctx: Context[dict[str, int]]) -> None: ... if ctx.keyboard_event is not None: ... ctx.state["keys"] += 1

Methods:

  • get_state

    Return the shared application state.

  • focus

    Focus the field labeled group on any attached grid.

  • blur

    Clear field focus on the active runtime.

  • is_focused

    Return whether the field labeled group currently holds focus.

  • call_soon

    Schedule callback to run on the UI thread before the next pump.

  • field_area

    Return the last painted area for field name, if known.

  • scroll

    Return a scroll handle for Field(scroll=...) labeled group.

  • get_scroll

    Return scroll state for group, or None if it is unavailable.

  • with_event

    Return a copy carrying a different event.

  • with_scope

    Return a shallow copy with the given fields replaced.

  • has_clipboard_event

    Return whether this context contains a clipboard event.

  • has_focus_event

    Return whether this is a focus event.

  • has_keyboard_event

    Return whether this is a keyboard event.

  • has_mouse_event

    Return whether this is a mouse event.

  • has_resize_event

    Return whether this is a resize event.

event instance-attribute

event: 'Event'

Event that triggered the hook.

terminal instance-attribute

terminal: 'Runtime[StateT]'

Terminal or offscreen session handling the event.

state instance-attribute

state: StateT

Application state shared with the runtime.

request class-attribute instance-attribute

request: 'Request | None' = None

HTTP request that triggered the hook, if any.

host property

host: 'Runtime[StateT]'

Session handling the event.

runtime property

runtime: 'Runtime[StateT]'

Runtime handling the event.

surface property

surface: str

Presentation surface: "terminal", "web", or "offscreen".

tick_event property

tick_event: 'TickEventData | None'

Tick payload when this context was triggered by a tick.

keyboard_event property

keyboard_event: 'KeyboardEventData | None'

Keyboard sub-event when triggered by a keyboard event.

mouse_event property

mouse_event: 'MouseEventData | None'

Mouse sub-event when triggered by a mouse event.

tick property

tick: 'TickEventData | None'

Deprecated alias for :attr:tick_event.

keyboard property

keyboard: 'KeyboardEventData | None'

Deprecated alias for :attr:keyboard_event.

mouse property

mouse: 'MouseEventData | None'

Deprecated alias for :attr:mouse_event.

cursor property

cursor: 'Cursor'

Cursor / caret controls for the active runtime.

device property

device: 'Device'

Device controls for the active runtime.

actions property

actions: 'Actions'

Perform synthetic input and requests.

stage property

stage: 'Stage'

Layout map and cell-level paint helpers.

render_size property

render_size: 'Breakpoint'

Current viewport breakpoint tier.

The same value grid_render_<size> / compose_<size> dispatch on — one of "extra_small", "small", "medium", "large", "extra_large" — derived from the live window width. Read it from any hook to branch on size without declaring a per-tier render method.

focused_group property

focused_group: str | None

group of the currently focused field, or None.

get_state

get_state() -> StateT

Return the shared application state.

Raises:

  • RuntimeError

    If no state was attached to this context.

Source code in xnano/context.py
def get_state(self) -> StateT:
    """Return the shared application state.

    Raises:
        RuntimeError: If no state was attached to this context.
    """
    if self.state is None:
        raise RuntimeError("No state attached to this context.")
    return self.state

focus

focus(group: str) -> bool

Focus the field labeled group on any attached grid.

Source code in xnano/context.py
def focus(self, group: str) -> bool:
    """Focus the field labeled ``group`` on any attached grid."""
    focus_group = getattr(self.terminal, "focus_group", None)
    if callable(focus_group):
        return bool(focus_group(group))
    return bool(self.terminal.focus(group))

blur

blur() -> None

Clear field focus on the active runtime.

Source code in xnano/context.py
def blur(self) -> None:
    """Clear field focus on the active runtime."""
    blur_field = getattr(self.terminal, "blur_field", None)
    if callable(blur_field):
        blur_field()
        return
    blur = getattr(self.terminal, "blur", None)
    if callable(blur):
        blur()

is_focused

is_focused(group: str) -> bool

Return whether the field labeled group currently holds focus.

Source code in xnano/context.py
def is_focused(self, group: str) -> bool:
    """Return whether the field labeled ``group`` currently holds focus."""
    return self.focused_group == group

call_soon

call_soon(
    callback: "Callable[..., Any]", *args: Any
) -> None

Schedule callback to run on the UI thread before the next pump.

The thread-safe bridge for updating grid state from a worker thread: the callback runs on the runtime's own thread, so it can freely mutate components/fields without racing the renderer.

Source code in xnano/context.py
def call_soon(self, callback: "Callable[..., Any]", *args: Any) -> None:
    """Schedule ``callback`` to run on the UI thread before the next pump.

    The thread-safe bridge for updating grid state from a worker thread:
    the callback runs on the runtime's own thread, so it can freely mutate
    components/fields without racing the renderer.
    """
    self.terminal.call_soon(callback, *args)

field_area

field_area(name: str) -> 'Area | None'

Return the last painted area for field name, if known.

Reads the always-on layout map so viewports and chrome math can use measured slot sizes instead of guessing.

Source code in xnano/context.py
def field_area(self, name: str) -> "Area | None":
    """Return the last painted area for field ``name``, if known.

    Reads the always-on layout map so viewports and chrome math can use
    measured slot sizes instead of guessing.
    """
    return self.stage.get_area(name)

scroll

scroll(group: str) -> 'ScrollHandle | None'

Return a scroll handle for Field(scroll=...) labeled group.

Source code in xnano/context.py
def scroll(self, group: str) -> "ScrollHandle | None":
    """Return a scroll handle for ``Field(scroll=...)`` labeled ``group``."""
    from xnano.utils.focus import scroll_handle_for_group

    return scroll_handle_for_group(self.terminal, group)

get_scroll

get_scroll(group: str) -> 'ScrollHandle | None'

Return scroll state for group, or None if it is unavailable.

Source code in xnano/context.py
def get_scroll(self, group: str) -> "ScrollHandle | None":
    """Return scroll state for ``group``, or ``None`` if it is unavailable."""
    return self.scroll(group)

with_event

with_event(event: 'Event') -> 'Context[StateT]'

Return a copy carrying a different event.

Source code in xnano/context.py
def with_event(self, event: "Event") -> "Context[StateT]":
    """Return a copy carrying a different event."""
    return dataclasses.replace(self, event=event)

with_scope

with_scope(**kwargs: Any) -> 'Context[StateT]'

Return a shallow copy with the given fields replaced.

Source code in xnano/context.py
def with_scope(self, **kwargs: Any) -> "Context[StateT]":
    """Return a shallow copy with the given fields replaced."""
    return dataclasses.replace(self, **kwargs)

has_clipboard_event

has_clipboard_event() -> bool

Return whether this context contains a clipboard event.

Source code in xnano/context.py
def has_clipboard_event(self) -> bool:
    """Return whether this context contains a clipboard event."""
    return self.event.is_clipboard_event()

has_focus_event

has_focus_event() -> bool

Return whether this is a focus event.

Source code in xnano/context.py
def has_focus_event(self) -> bool:
    """Return whether this is a focus event."""
    return self.event.is_focus_event()

has_keyboard_event

has_keyboard_event() -> bool

Return whether this is a keyboard event.

Source code in xnano/context.py
def has_keyboard_event(self) -> bool:
    """Return whether this is a keyboard event."""
    return self.event.is_keyboard_event()

has_mouse_event

has_mouse_event() -> bool

Return whether this is a mouse event.

Source code in xnano/context.py
def has_mouse_event(self) -> bool:
    """Return whether this is a mouse event."""
    return self.event.is_mouse_event()

has_resize_event

has_resize_event() -> bool

Return whether this is a resize event.

Source code in xnano/context.py
def has_resize_event(self) -> bool:
    """Return whether this is a resize event."""
    return self.event.is_resize_event()

Frame dataclass

Frame(
    width: int,
    height: int,
    text: str = "",
    ansi: str = "",
    cursor_position: tuple[int, int] | None = None,
    cursor_visible: bool = True,
    cursor_style: str | None = None,
    title: str | None = None,
    commands: tuple[Mapping[str, Any], ...] = (),
    revision: int = 0,
)

Immutable snapshot of one painted native frame.

Example

Frame(width=20, height=4, text="Ready")

Attributes:

Methods:

  • contains

    Return whether plain text contains needle.

width instance-attribute

width: int

Frame width in cells.

height instance-attribute

height: int

Frame height in cells.

text class-attribute instance-attribute

text: str = ''

Plain-text cell rows.

ansi class-attribute instance-attribute

ansi: str = ''

ANSI-styled cell rows.

cursor_position class-attribute instance-attribute

cursor_position: tuple[int, int] | None = None

Caret position in cells.

cursor_visible class-attribute instance-attribute

cursor_visible: bool = True

Whether the caret is visible.

cursor_style class-attribute instance-attribute

cursor_style: str | None = None

Caret shape and blink style.

title class-attribute instance-attribute

title: str | None = None

Window or document title.

commands class-attribute instance-attribute

commands: tuple[Mapping[str, Any], ...] = ()

Device commands emitted with the frame.

revision class-attribute instance-attribute

revision: int = 0

Monotonic frame revision.

rows property

rows: tuple[str, ...]

Plain-text rows of the frame.

contains

contains(needle: str) -> bool

Return whether plain text contains needle.

Source code in xnano/core/frame.py
def contains(self, needle: str) -> bool:
    """Return whether plain text contains ``needle``."""
    return needle in self.text

Runtime

Runtime(
    session: CoreSession,
    *,
    live: bool,
    state: StateT | None = None,
    title: str | None = None,
    surface: str = "terminal",
    tick_interval: int = 16
)

Bases: Generic[StateT]

Drive one application through an xnano_core session.

Most applications use :class:xnano.terminal.Terminal; use Runtime directly when you need explicit session ownership.

Attributes:

Example

runtime = Runtime.offscreen(24, 3) frame = runtime.render("Hello") frame.width, frame.height (24, 3) runtime.close()

Methods:

  • live

    Create a runtime backed by the active terminal.

  • offscreen

    Create an active in-memory runtime.

  • supports_live_terminal

    Return whether this build can claim a live terminal.

  • enter

    Bind this runtime to the current context.

  • close

    Restore the native session and release the active binding.

  • set_root

    Set the renderable used by subsequent empty renders.

  • render

    Render one frame and return its immutable snapshot.

  • call_soon

    Schedule callback to run on the UI thread before the next pump.

  • pump

    Poll and dispatch at most one event.

  • dispatch

    Dispatch one event to the root grid or component.

  • play_effect

    Play an effect over fields recorded by the latest render.

  • cancel_effect

    Stop the effect registered under key.

  • is_animating

    Whether any effect is currently running in this session.

  • perform

    Perform a synthetic action through its event representation.

  • resize

    Resize support is fixed at offscreen-session construction.

  • request_exit

    Stop the run loop after the current dispatch.

  • focus

    Focus a named field group.

  • blur

    Clear field focus.

  • focus_next

    Move focus through the root grid when supported.

  • focus_previous

    Move focus backward through the root grid when supported.

  • get_output

    Return the current buffer as plain text.

  • get_output_as_ansi

    Return the current buffer with ANSI styling.

Source code in xnano/core/runtime.py
def __init__(
    self,
    session: CoreSession,
    *,
    live: bool,
    state: StateT | None = None,
    title: str | None = None,
    surface: str = "terminal",
    tick_interval: int = 16,
) -> None:
    self._session = session
    self._live = live
    self._state = state
    self._surface = surface
    self._root: Any = None
    self._revision = 0
    self._closed = False
    self._should_exit = False
    self._focused_group: str | None = None
    self._token: contextvars.Token[Runtime[Any] | None] | None = None
    self._frame_commands: list[dict[str, Any]] = []
    self._stage = Stage()
    self._elapsed_ms = 0
    self._tick_hook_times: dict[tuple[int, str], int] = {}
    self._post_init_grids: set[int] = set()
    self._watch_values: dict[tuple[int, str, str], Any] = {}
    self._grid_breakpoints: dict[int, str] = {}
    self._tick_interval = max(1, tick_interval)
    self._last_tick_ms = time.monotonic() * 1000
    self._signals_installed = False
    self._prev_signal_handlers: dict[signal.Signals, Any] = {}
    self._cursor = Cursor(self)
    self._device = Device(self)
    self._actions = Actions(self)
    self._call_soon_queue: collections.deque[
        tuple[Callable[..., Any], tuple[Any, ...]]
    ] = collections.deque()
    self._call_soon_lock = threading.Lock()
    if title is not None:
        self._device.title = title

session property

session: CoreSession

Native session owned by this runtime.

terminal property

terminal: 'Runtime[StateT]'

Compatibility name for the runtime's terminal surface.

surface property

surface: str

Presentation surface name.

is_live property

is_live: bool

Whether the runtime owns the user's terminal.

state property writable

state: StateT | None

Application state shared with hooks.

device property

device: Device

Display controls for this session.

cursor property

cursor: Cursor

Caret controls for this session.

actions property

actions: Actions

Synthetic action performer for this session.

stage property

stage: Stage

Current layout stage when a grid has rendered.

size property

size: tuple[int, int]

Viewport width and height in cells.

focused_group property

focused_group: str | None

Name of the focused field group.

live classmethod

live(
    *,
    state: StateT | None = None,
    title: str | None = None,
    tick_interval: int = 16,
    mouse_events: bool = False
) -> "Runtime[StateT]"

Create a runtime backed by the active terminal.

Source code in xnano/core/runtime.py
@classmethod
def live(
    cls,
    *,
    state: StateT | None = None,
    title: str | None = None,
    tick_interval: int = 16,
    mouse_events: bool = False,
) -> "Runtime[StateT]":
    """Create a runtime backed by the active terminal."""
    session = CoreSession.init(tick_rate_ms=None)
    if mouse_events:
        session.enable_mouse_capture()
    return cls(
        session,
        live=True,
        state=state,
        title=title,
        tick_interval=tick_interval,
    )

offscreen classmethod

offscreen(
    width: int = 80,
    height: int = 24,
    *,
    state: StateT | None = None,
    title: str | None = None
) -> "Runtime[StateT]"

Create an active in-memory runtime.

Source code in xnano/core/runtime.py
@classmethod
def offscreen(
    cls,
    width: int = 80,
    height: int = 24,
    *,
    state: StateT | None = None,
    title: str | None = None,
) -> "Runtime[StateT]":
    """Create an active in-memory runtime."""
    runtime = cls(
        CoreSession.offscreen(width, height),
        live=False,
        state=state,
        title=title,
        surface="offscreen",
    )
    return runtime.enter()

supports_live_terminal staticmethod

supports_live_terminal() -> bool

Return whether this build can claim a live terminal.

Source code in xnano/core/runtime.py
@staticmethod
def supports_live_terminal() -> bool:
    """Return whether this build can claim a live terminal."""
    return CoreSession.supports_live_terminal()

enter

enter() -> 'Runtime[StateT]'

Bind this runtime to the current context.

Source code in xnano/core/runtime.py
def enter(self) -> "Runtime[StateT]":
    """Bind this runtime to the current context."""
    if self._token is None:
        self._token = _ACTIVE_RUNTIME.set(self)
    if self._live:
        self._install_signal_handlers()
    return self

close

close() -> None

Restore the native session and release the active binding.

Source code in xnano/core/runtime.py
def close(self) -> None:
    """Restore the native session and release the active binding."""
    if self._closed:
        return
    self._closed = True
    if self._token is not None:
        _ACTIVE_RUNTIME.reset(self._token)
        self._token = None
    # Restore the host terminal first (raw mode, mouse tracking,
    # alternate screen, SGR) so a failure restoring signal handlers
    # never leaves the screen corrupted.
    try:
        self._session.restore()
    finally:
        # Runtime owns cursor/device/action proxies that point back to it,
        # so the Python object can remain in a reference cycle after
        # close. Release the unsendable PyO3 session now, on its owner
        # thread, instead of leaving its destructor to a later GC pass.
        del self._session
        self._restore_signal_handlers()

set_root

set_root(root: Any) -> None

Set the renderable used by subsequent empty renders.

Source code in xnano/core/runtime.py
def set_root(self, root: Any) -> None:
    """Set the renderable used by subsequent empty renders."""
    self._root = root

render

render(
    *renderables: Any,
    foreground: ColorLike | None = None,
    background: ColorLike | None = None,
    modifiers: Sequence[CharacterModifier] | None = None,
    horizontal_align: Alignment | None = None,
    vertical_align: VerticalAlignment | None = None,
    border: Border | None = None,
    border_sides: Sequence[Side] | None = None,
    border_color: ColorLike | None = None,
    title: str | None = None,
    title_position: FrameTitlePosition | None = None,
    padding: PaddingLike | None = None,
    gap: int = 0,
    direction: Direction = "vertical",
    color: ColorLike | None = None,
    align: Alignment | None = None
) -> Frame

Render one frame and return its immutable snapshot.

Source code in xnano/core/runtime.py
def render(
    self,
    *renderables: Any,
    foreground: ColorLike | None = None,
    background: ColorLike | None = None,
    modifiers: Sequence[CharacterModifier] | None = None,
    horizontal_align: Alignment | None = None,
    vertical_align: VerticalAlignment | None = None,
    border: Border | None = None,
    border_sides: Sequence[Side] | None = None,
    border_color: ColorLike | None = None,
    title: str | None = None,
    title_position: FrameTitlePosition | None = None,
    padding: PaddingLike | None = None,
    gap: int = 0,
    direction: Direction = "vertical",
    color: ColorLike | None = None,
    align: Alignment | None = None,
) -> Frame:
    """Render one frame and return its immutable snapshot."""
    foreground = resolve_color_alias(foreground, color, stacklevel=3)
    horizontal_align = resolve_renamed_alias(
        horizontal_align,
        align,
        old="align",
        new="horizontal_align",
        stacklevel=3,
    )
    self._render(
        *renderables,
        foreground=foreground,
        background=background,
        modifiers=modifiers,
        horizontal_align=horizontal_align,
        vertical_align=vertical_align,
        border=border,
        border_sides=border_sides,
        border_color=border_color,
        title=title,
        title_position=title_position,
        padding=padding,
        gap=gap,
        direction=direction,
    )
    return self._snapshot_frame()

call_soon

call_soon(callback: Callable[..., Any], *args: Any) -> None

Schedule callback to run on the UI thread before the next pump.

Thread-safe: worker threads enqueue here and the runtime drains the queue on its own thread, so background work can mutate grid state without racing the renderer.

Source code in xnano/core/runtime.py
def call_soon(self, callback: Callable[..., Any], *args: Any) -> None:
    """Schedule ``callback`` to run on the UI thread before the next pump.

    Thread-safe: worker threads enqueue here and the runtime drains the
    queue on its own thread, so background work can mutate grid state
    without racing the renderer.
    """
    with self._call_soon_lock:
        self._call_soon_queue.append((callback, args))

pump

pump(timeout: float = 0.0) -> bool

Poll and dispatch at most one event.

Source code in xnano/core/runtime.py
def pump(self, timeout: float = 0.0) -> bool:
    """Poll and dispatch at most one event."""
    self._drain_call_soon()
    if self._should_exit:
        return False
    timeout_ms = max(0, int(timeout * 1000))
    if self._live and timeout_ms == 0:
        timeout_ms = self._tick_interval
    native_event = self._session.poll_event(timeout_ms)
    if native_event is not None:
        self.dispatch(event_from_core(native_event))
    elif self._root is not None:
        from xnano.core.dispatch import dispatch_idle

        dispatch_idle(self._root, self)
    if self._root is not None:
        from xnano.events import Event, TickEventData

        now = time.monotonic() * 1000
        elapsed_ms = max(0, int(now - self._last_tick_ms))
        self._last_tick_ms = now
        self.dispatch(
            Event.from_data(TickEventData(elapsed_ms=elapsed_ms))
        )
    return not self._should_exit

dispatch

dispatch(event: Any) -> None

Dispatch one event to the root grid or component.

Source code in xnano/core/runtime.py
def dispatch(self, event: Any) -> None:
    """Dispatch one event to the root grid or component."""
    from xnano.core.dispatch import dispatch_event
    from xnano.utils.focus import (
        apply_text_keyboard,
        cycle_field_focus,
        focused_component,
        move_field_focus,
        spatial_focus_enabled,
    )

    consumed = False
    mouse = getattr(event, "mouse_event", None)
    if mouse is not None and mouse.field is None:
        from xnano.core.dispatch import iter_grids
        from xnano.events import Event, MouseEventData

        for grid in iter_grids(self._root):
            for hit in reversed(getattr(grid, "_grid_field_hits", ())):
                if hit.area.contains((mouse.x, mouse.y)):
                    field = hit.grid._grid_field_info(hit.field_name)
                    self._auto_scroll_wheel(hit, field, mouse.kind)
                    self._click_to_focus(hit, field, mouse.kind)
                    component = getattr(hit.grid, hit.field_name, None)
                    hover = getattr(component, "handle_hover", None)
                    if callable(hover):
                        hover(mouse.x, mouse.y)
                    event = Event.from_data(
                        MouseEventData(
                            kind=mouse.kind,
                            x=mouse.x,
                            y=mouse.y,
                            button=mouse.button,
                            field=hit.field_name,
                            group=field.group,
                        )
                    )
                    break
    keyboard = getattr(event, "keyboard_event", None)
    if keyboard is not None:
        if keyboard.matches("ctrl+c"):
            self.request_exit()
            return
        if keyboard.matches("tab"):
            consumed = cycle_field_focus(self)
        elif keyboard.matches("shift+tab"):
            consumed = cycle_field_focus(self, -1)
        else:
            consumed = apply_text_keyboard(
                focused_component(self),
                keyboard,
            )
            if not consumed and spatial_focus_enabled(self):
                for direction in ("up", "down", "left", "right"):
                    if keyboard.matches(direction):
                        consumed = move_field_focus(self, direction)
                        break
    clipboard = getattr(event, "clipboard_event", None)
    tick = getattr(event, "tick_event", None)
    if tick is not None:
        self._elapsed_ms += tick.elapsed_ms
    component = focused_component(self)
    if clipboard is not None and component is not None:
        paste_handler = getattr(component, "handle_paste", None)
        if callable(paste_handler):
            consumed = (
                bool(paste_handler(clipboard.text or "")) or consumed
            )
    if self._root is not None and not consumed:
        dispatch_event(self._root, self, event)
    root_dispatch = getattr(self._root, "dispatch_event", None)
    if callable(root_dispatch):
        root_dispatch(event, self)

play_effect

play_effect(
    effect: Any,
    *,
    fields: list[str] | None = None,
    repeat: bool = False
) -> list[str]

Play an effect over fields recorded by the latest render.

Parameters:

  • effect (Any) –

    Effect description.

  • fields (list[str] | None, default: None ) –

    Field names whose rendered areas receive the effect.

  • repeat (bool, default: False ) –

    Loop the effect until it is cancelled, instead of running once for its duration.

Returns:

  • list[str]

    The session keys registered, one per field that had a

  • list[str]

    rendered area. Empty when nothing was targeted.

Source code in xnano/core/runtime.py
def play_effect(
    self,
    effect: Any,
    *,
    fields: list[str] | None = None,
    repeat: bool = False,
) -> list[str]:
    """Play an effect over fields recorded by the latest render.

    Args:
        effect: Effect description.
        fields: Field names whose rendered areas receive the effect.
        repeat: Loop the effect until it is cancelled, instead of
            running once for its duration.

    Returns:
        The session keys registered, one per field that had a
        rendered area. Empty when nothing was targeted.
    """
    import xnano_core.rust.native as native

    from xnano.core.effects import resolve_native_effect

    keys: list[str] = []
    lowered = None
    for field in fields or ():
        area = self._session.effect_area_for(field)
        if area is None:
            continue
        if lowered is None:
            # Lower once, not once per target field.
            lowered = resolve_native_effect(effect)
            if repeat:
                lowered = native.repeating_effect(lowered)
        key = f"{getattr(effect, 'key', None) or field}:{field}"
        self._session.add_unique_effect(key, lowered.with_area(area))
        keys.append(key)
    return keys

cancel_effect

cancel_effect(key: str) -> None

Stop the effect registered under key.

Source code in xnano/core/runtime.py
def cancel_effect(self, key: str) -> None:
    """Stop the effect registered under ``key``."""
    self._session.cancel_effect(key)

is_animating

is_animating() -> bool

Whether any effect is currently running in this session.

Source code in xnano/core/runtime.py
def is_animating(self) -> bool:
    """Whether any effect is currently running in this session."""
    return bool(self._session.is_animating())

perform

perform(action: Any) -> None

Perform a synthetic action through its event representation.

Source code in xnano/core/runtime.py
def perform(self, action: Any) -> None:
    """Perform a synthetic action through its event representation."""
    from xnano.actions import (
        ClickAction,
        ClipboardAction,
        FocusAction,
        KeyboardAction,
        MouseAction,
        RequestAction,
        ResizeAction,
        TickAction,
    )
    from xnano.events import (
        ClipboardEventData,
        Event,
        FocusEventData,
        KeyboardEventData,
        MouseEventData,
        ResizeEventData,
        TickEventData,
    )

    if isinstance(action, KeyboardAction):
        for binding in action.bindings:
            self.dispatch(
                Event.from_data(
                    KeyboardEventData.from_binding(
                        str(binding),
                        kind=action.kind or "press",
                    )
                )
            )
        return
    if isinstance(action, MouseAction):
        self.dispatch(
            Event.from_data(
                MouseEventData(
                    kind=action.kind or "press",
                    x=0,
                    y=0,
                    button=action.buttons[0]
                    if action.buttons
                    else "unknown",
                )
            )
        )
        return
    if isinstance(action, ClickAction):
        event = Event.from_data(
            MouseEventData(
                kind="press",
                x=0,
                y=0,
                button=action.button,
                field=action.field,
            )
        )
        self.dispatch(event)
        return
    if isinstance(action, FocusAction):
        self.dispatch(
            Event.from_data(
                FocusEventData(
                    kind=action.kind or "gained",
                    field=action.field,
                )
            )
        )
        return
    if isinstance(action, ClipboardAction):
        self.dispatch(
            Event.from_data(ClipboardEventData(text=action.text))
        )
        return
    if isinstance(action, ResizeAction):
        self.dispatch(
            Event.from_data(
                ResizeEventData(
                    width=action.width or self.size[0],
                    height=action.height or self.size[1],
                )
            )
        )
        return
    if isinstance(action, TickAction):
        self.dispatch(
            Event.from_data(
                TickEventData(elapsed_ms=action.interval_ms),
            )
        )
        return
    if isinstance(action, RequestAction) and self._root is not None:
        from xnano.requests import dispatch_request

        dispatch_request(
            self._root,
            action.method,
            action.path,
            runtime=self,
        )
        return
    self.dispatch(action)

resize

resize(width: int, height: int) -> None

Resize support is fixed at offscreen-session construction.

Source code in xnano/core/runtime.py
def resize(self, width: int, height: int) -> None:
    """Resize support is fixed at offscreen-session construction."""
    if self.size != (width, height):
        raise RuntimeError("Create a new offscreen runtime to resize it.")

request_exit

request_exit() -> None

Stop the run loop after the current dispatch.

Source code in xnano/core/runtime.py
def request_exit(self) -> None:
    """Stop the run loop after the current dispatch."""
    self._should_exit = True

focus

focus(group: str) -> bool

Focus a named field group.

Source code in xnano/core/runtime.py
def focus(self, group: str) -> bool:
    """Focus a named field group."""
    from xnano.utils.focus import (
        resolve_group_target,
        set_field_focus,
    )

    target = resolve_group_target(self, group)
    if target is None:
        return False
    set_field_focus(self, target)
    return True

blur

blur() -> None

Clear field focus.

Source code in xnano/core/runtime.py
def blur(self) -> None:
    """Clear field focus."""
    from xnano.utils.focus import clear_field_focus

    clear_field_focus(self)
    self._focused_group = None

focus_next

focus_next() -> bool

Move focus through the root grid when supported.

Source code in xnano/core/runtime.py
def focus_next(self) -> bool:
    """Move focus through the root grid when supported."""
    from xnano.utils.focus import cycle_field_focus

    return cycle_field_focus(self)

focus_previous

focus_previous() -> bool

Move focus backward through the root grid when supported.

Source code in xnano/core/runtime.py
def focus_previous(self) -> bool:
    """Move focus backward through the root grid when supported."""
    from xnano.utils.focus import cycle_field_focus

    return cycle_field_focus(self, -1)

get_output

get_output() -> str

Return the current buffer as plain text.

Source code in xnano/core/runtime.py
def get_output(self) -> str:
    """Return the current buffer as plain text."""
    return "\n".join(self._session.buffer_snapshot().to_string_lines())

get_output_as_ansi

get_output_as_ansi() -> str

Return the current buffer with ANSI styling.

Source code in xnano/core/runtime.py
def get_output_as_ansi(self) -> str:
    """Return the current buffer with ANSI styling."""
    return "\n".join(self._session.buffer_snapshot().to_ansi_lines())

BaseGrid

BaseGrid()

Bases: AbstractInterface

Declarative layout container for a terminal-based UI.

BaseGrid-scoped settings may be declared on the class header (class Dashboard(BaseGrid, direction="horizontal", gap=1): ...), in a class-level grid_settings dict, or both — values in grid_settings override matching header kwargs.

Attributes:

  • grid_settings (GridSettings) –

    Class-level layout and frame configuration.

  • visible (bool) –

    Whether the grid is rendered.

  • z (int) –

    Layering order for overlapping grids.

  • columns (int) –

    Columns available during the current frame.

  • rows (int) –

    Rows available during the current frame.

Examples:

Layout fields render content; state=True fields hold app data. Nested BaseGrid subclasses compose larger layouts:

from xnano import BaseGrid, Field, Terminal

class Sidebar(BaseGrid, direction="vertical"):
    nav: str = Field(default="Home", border="rounded", height="1fr")

class App(BaseGrid, direction="horizontal", gap=1):
    sidebar: Sidebar = Field(default_factory=Sidebar, width="25%")
    content: str = Field(default="Main area", width="1fr")

    selected: int = Field(default=0, state=True)

Terminal().run(App())

Event hooks register handlers on the grid class. Use @on_click to scope mouse handlers to a layout field's region:

from xnano import BaseGrid, Context, Field, Terminal, hooks

class Counter(BaseGrid, direction="vertical", gap=1):
    label: str = Field(default="Count: 0", height=1)
    body: str = Field(default="Click me", height="1fr")

    count: int = Field(default=0, state=True)

    @hooks.on_keyboard("up")
    def increment(self) -> None:
        self.count += 1
        self.label = f"Count: {self.count}"

    @hooks.on_keyboard("down")
    def decrement(self) -> None:
        self.count -= 1
        self.label = f"Count: {self.count}"

    @hooks.on_click("body")
    def on_body(self, ctx: Context) -> None:
        self.body = "Clicked!"

    @hooks.on_tick(1000)
    def reset_body(self) -> None:
        self.body = "Click me"

Terminal().run(Counter())

Methods:

Source code in xnano/grids.py
def __init__(self) -> None:
    self._grid_init_field_states()
    self.__post_init__()

grid_settings class-attribute

grid_settings: GridSettings = {}

Class-level grid configuration, like Pydantic's model_config.

__xnano_grid__ class-attribute

__xnano_grid__: bool = True

Marks this class as a grid, for cheap identity checks.

Faster and clearer than duck-typing _grid_fields, and usable from layers that must not import BaseGrid — see is_grid.

visible class-attribute instance-attribute

visible: bool = True

Whether this grid is rendered in the live session.

z class-attribute instance-attribute

z: int = 0

Z-index used when layering overlapping grids.

columns class-attribute instance-attribute

columns: int = 0

Terminal columns available to this grid — set by the session each frame.

rows class-attribute instance-attribute

rows: int = 0

Terminal rows available to this grid — set by the session each frame.

grid_focused property

grid_focused: bool

Whether any of this grid's fields currently holds field focus.

Live alongside per-component focused: derived from the same per-frame focus flags, so self.grid_focused in a hook and @on_field("focused") both read the current state.

grid_state property

grid_state: Any

Return the active terminal's shared state, or None.

focused property

focused: bool

Deprecated alias for grid_focused.

state property

state: Any

Deprecated alias for grid_state.

grid_get_field_state

grid_get_field_state(name: str) -> FieldState | None

Return tracked state for a field.

Parameters:

  • name (str) –

    Field attribute name.

Returns:

  • FieldState | None

    Its state, or None for an unknown field.

Source code in xnano/core/interface.py
def grid_get_field_state(self, name: str) -> FieldState | None:
    """Return tracked state for a field.

    Args:
        name: Field attribute name.

    Returns:
        Its state, or ``None`` for an unknown field.
    """
    return getattr(self, "_grid_field_states", {}).get(name)

grid_mark_field_dirty

grid_mark_field_dirty(name: str) -> None

Mark a field as changed.

Parameters:

  • name (str) –

    Field attribute name.

Source code in xnano/core/interface.py
def grid_mark_field_dirty(self, name: str) -> None:
    """Mark a field as changed.

    Args:
        name: Field attribute name.
    """
    state = self.grid_get_field_state(name)
    if state is not None:
        state.mark_dirty()
        state.value = getattr(self, name, None)

get_field_state

get_field_state(name: str) -> FieldState | None

Deprecated alias for grid_get_field_state.

Source code in xnano/core/interface.py
@warn_renamed_attribute(
    "AbstractInterface.get_field_state",
    "AbstractInterface.grid_get_field_state",
)
def get_field_state(self, name: str) -> FieldState | None:
    """Deprecated alias for ``grid_get_field_state``."""
    return self.grid_get_field_state(name)

mark_field_dirty

mark_field_dirty(name: str) -> None

Deprecated alias for grid_mark_field_dirty.

Source code in xnano/core/interface.py
@warn_renamed_attribute(
    "AbstractInterface.mark_field_dirty",
    "AbstractInterface.grid_mark_field_dirty",
)
def mark_field_dirty(self, name: str) -> None:
    """Deprecated alias for ``grid_mark_field_dirty``."""
    self.grid_mark_field_dirty(name)

__post_init__

__post_init__() -> None

Called at the end of the generated __init__. Override to run post-construction logic.

Source code in xnano/grids.py
def __post_init__(self) -> None:
    """Called at the end of the generated ``__init__``. Override to run post-construction logic."""

grid_post_init

grid_post_init() -> None

Called exactly once, when this grid is first attached to a live terminal — after its own hooks are bound, before its first paint.

Unlike __post_init__ (construction time, no terminal attached yet), ctx/ctx.state are live here. Override with either signature — the extra parameter is optional, matching every other @on_* hook, dispatched by arity at runtime (see _call_hook):

def grid_post_init(self) -> None: ...
def grid_post_init(self, ctx: Context[MyState]) -> None: ...

A static type checker sees the one-parameter form as an invalid override (this base declares zero); that's a known false positive for this flexible-arity hook pattern — add # ty: ignore[invalid-method-override] on the override line.

Source code in xnano/grids.py
def grid_post_init(self) -> None:
    """Called exactly once, when this grid is first attached to a live
    terminal — after its own hooks are bound, before its first paint.

    Unlike ``__post_init__`` (construction time, no terminal attached
    yet), ``ctx``/``ctx.state`` are live here. Override with either
    signature — the extra parameter is optional, matching every other
    ``@on_*`` hook, dispatched by arity at runtime (see ``_call_hook``):

        def grid_post_init(self) -> None: ...
        def grid_post_init(self, ctx: Context[MyState]) -> None: ...

    A static type checker sees the one-parameter form as an invalid
    override (this base declares zero); that's a known false positive
    for this flexible-arity hook pattern — add
    ``# ty: ignore[invalid-method-override]`` on the override line.
    """

grid_render

grid_render() -> None

Called each frame before layout.

Override to refresh field values every frame. Initial values can be set with Field(default=...), default_factory, or __post_init__.

Override with either signature — the extra Context parameter is optional, dispatched by arity like grid_post_init:

def grid_render(self) -> None: ...
def grid_render(self, ctx: Context[MyState]) -> None: ...

A static type checker sees the one-parameter form as an invalid override; add # ty: ignore[invalid-method-override] on the override line.

Source code in xnano/grids.py
def grid_render(self) -> None:
    """Called each frame before layout.

    Override to refresh field values every frame. Initial values can be set
    with ``Field(default=...)``, ``default_factory``, or ``__post_init__``.

    Override with either signature — the extra ``Context`` parameter is
    optional, dispatched by arity like ``grid_post_init``:

        def grid_render(self) -> None: ...
        def grid_render(self, ctx: Context[MyState]) -> None: ...

    A static type checker sees the one-parameter form as an invalid
    override; add ``# ty: ignore[invalid-method-override]`` on the
    override line.
    """

grid_render_extra_small

grid_render_extra_small() -> None

Refine layout when the viewport is extra small (< 40 cols).

Optional responsive counterpart to :meth:grid_render. Runs when the window enters this size tier — on the first render and on later resizes that cross into it — not every frame, so a per-frame @on_tick mutation is not overwritten by a size hook resetting the same field. Keep shared per-frame logic in grid_render and size-specific setup here. Overriding any grid_render_* variant opts the class into breakpoint dispatch; a class that overrides none pays no per-frame cost.

Source code in xnano/grids.py
@responsive_noop
def grid_render_extra_small(self) -> None:
    """Refine layout when the viewport is extra small (< 40 cols).

    Optional responsive counterpart to :meth:`grid_render`. Runs when
    the window enters this size tier — on the first render and on
    later resizes that cross into it — not every frame, so a
    per-frame ``@on_tick`` mutation is not overwritten by a size hook
    resetting the same field. Keep shared per-frame logic in
    ``grid_render`` and size-specific setup here. Overriding any
    ``grid_render_*`` variant opts the class into breakpoint dispatch;
    a class that overrides none pays no per-frame cost.
    """

grid_render_small

grid_render_small() -> None

Refine layout when the viewport is small (40–79 cols).

See :meth:grid_render_extra_small.

Source code in xnano/grids.py
@responsive_noop
def grid_render_small(self) -> None:
    """Refine layout when the viewport is small (40–79 cols).

    See :meth:`grid_render_extra_small`.
    """

grid_render_medium

grid_render_medium() -> None

Refine layout when the viewport is medium (80–119 cols).

See :meth:grid_render_extra_small.

Source code in xnano/grids.py
@responsive_noop
def grid_render_medium(self) -> None:
    """Refine layout when the viewport is medium (80–119 cols).

    See :meth:`grid_render_extra_small`.
    """

grid_render_large

grid_render_large() -> None

Refine layout when the viewport is large (120–159 cols).

See :meth:grid_render_extra_small.

Source code in xnano/grids.py
@responsive_noop
def grid_render_large(self) -> None:
    """Refine layout when the viewport is large (120–159 cols).

    See :meth:`grid_render_extra_small`.
    """

grid_render_extra_large

grid_render_extra_large() -> None

Refine layout when the viewport is extra large (>= 160 cols).

See :meth:grid_render_extra_small.

Source code in xnano/grids.py
@responsive_noop
def grid_render_extra_large(self) -> None:
    """Refine layout when the viewport is extra large (>= 160 cols).

    See :meth:`grid_render_extra_small`.
    """

grid_play_effect

grid_play_effect(
    effect: AbstractEffect,
    *,
    fields: list[str] | None = None
) -> bool
grid_play_effect(
    effect: KnownEffectKind,
    *,
    duration_ms: int = 300,
    color: ColorLike | None = None,
    background: ColorLike | None = None,
    direction: EffectMotion | None = None,
    gradient_length: int | None = None,
    randomness: int | None = None,
    interpolation: EffectInterpolation | None = None,
    effects: Sequence[AbstractEffect] | None = None,
    child: AbstractEffect | None = None,
    times: int | None = None,
    fields: list[str] | None = None,
    key: str | None = None
) -> bool
grid_play_effect(
    effect: KnownEffectKind | AbstractEffect,
    *,
    duration_ms: int = 300,
    color: ColorLike | None = None,
    background: ColorLike | None = None,
    direction: EffectMotion | None = None,
    gradient_length: int | None = None,
    randomness: int | None = None,
    interpolation: EffectInterpolation | None = None,
    effects: Sequence[AbstractEffect] | None = None,
    child: AbstractEffect | None = None,
    times: int | None = None,
    fields: list[str] | None = None,
    key: str | None = None
) -> bool

Run a visual effect on one or more layout field areas.

Each layout field is tagged with its name as an effect key during rendering, so effects can target field content rects on the frame after grid_render.

Pass a custom AbstractEffect subclass or provide a known effect kind with typed keyword arguments.

Parameters:

  • effect (KnownEffectKind | AbstractEffect) –

    A built effect instance or a known effect kind string.

  • duration_ms (int, default: 300 ) –

    Duration of the effect in milliseconds.

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

    Foreground or accent color for color-driven effects.

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

    Background color for two-color effects.

  • direction (EffectMotion | None, default: None ) –

    Motion direction for slide and sweep effects.

  • gradient_length (int | None, default: None ) –

    Gradient length for slide and sweep effects.

  • randomness (int | None, default: None ) –

    Randomness for slide and sweep effects.

  • interpolation (EffectInterpolation | None, default: None ) –

    Interpolation curve for the effect.

  • effects (Sequence[AbstractEffect] | None, default: None ) –

    Child effects for sequence and parallel composition.

  • child (AbstractEffect | None, default: None ) –

    Child effect for repeat and delay composition.

  • times (int | None, default: None ) –

    Repeat count for repeat effects.

  • fields (list[str] | None, default: None ) –

    Layout field names to target. When omitted or empty, no effect is started.

  • key (str | None, default: None ) –

    Identity used to de-duplicate this effect per target field — see AbstractEffect.key. Only meaningful when effect is a known-kind string; ignored when effect is already a built AbstractEffect instance (set key on the instance itself in that case).

Returns:

  • bool

    True when at least one field area was found and an effect

  • bool

    started.

Source code in xnano/grids.py
@warn_renamed_attribute(
    "BaseGrid.grid_play_effect", "BaseGrid.grid_effect"
)
def grid_play_effect(
    self,
    effect: KnownEffectKind | AbstractEffect,
    *,
    duration_ms: int = 300,
    color: ColorLike | None = None,
    background: ColorLike | None = None,
    direction: EffectMotion | None = None,
    gradient_length: int | None = None,
    randomness: int | None = None,
    interpolation: EffectInterpolation | None = None,
    effects: Sequence[AbstractEffect] | None = None,
    child: AbstractEffect | None = None,
    times: int | None = None,
    fields: list[str] | None = None,
    key: str | None = None,
) -> bool:
    """Run a visual effect on one or more layout field areas.

    Each layout field is tagged with its name as an effect key during
    rendering, so effects can target field content rects on the frame
    after ``grid_render``.

    Pass a custom ``AbstractEffect`` subclass or
    provide a known effect kind with typed keyword arguments.

    Args:
        effect: A built effect instance or a known effect kind string.
        duration_ms: Duration of the effect in milliseconds.
        color: Foreground or accent color for color-driven effects.
        background: Background color for two-color effects.
        direction: Motion direction for slide and sweep effects.
        gradient_length: Gradient length for slide and sweep effects.
        randomness: Randomness for slide and sweep effects.
        interpolation: Interpolation curve for the effect.
        effects: Child effects for sequence and parallel composition.
        child: Child effect for repeat and delay composition.
        times: Repeat count for repeat effects.
        fields: Layout field names to target. When omitted or empty, no
            effect is started.
        key: Identity used to de-duplicate this effect per target
            field — see ``AbstractEffect.key``. Only meaningful when
            ``effect`` is a known-kind string; ignored when ``effect``
            is already a built ``AbstractEffect`` instance (set
            ``key`` on the instance itself in that case).

    Returns:
        ``True`` when at least one field area was found and an effect
        started.
    """
    return bool(
        self.grid_effect(
            effect,
            duration_ms=duration_ms,
            color=color,
            background=background,
            direction=direction,
            gradient_length=gradient_length,
            randomness=randomness,
            interpolation=interpolation,
            effects=effects,
            child=child,
            times=times,
            fields=fields,
            key=key,
        )
    )

grid_effect

grid_effect(
    effect: KnownEffectKind | AbstractEffect,
    *,
    duration_ms: int | None = None,
    color: ColorLike | None = None,
    background: ColorLike | None = None,
    direction: EffectMotion | None = None,
    gradient_length: int | None = None,
    randomness: int | None = None,
    interpolation: EffectInterpolation | None = None,
    effects: Sequence[AbstractEffect] | None = None,
    child: AbstractEffect | None = None,
    times: int | None = None,
    fields: list[str] | None = None,
    key: str | None = None
) -> "EffectHandle"

Run a visual effect on one or more layout field areas.

Returns an EffectHandle: truthy when at least one field was targeted, with .active and .cancel(), and usable as a context manager that cancels the effect on exit.

Starting an effect never blocks. duration_ms is the animation length handed to the renderer, not a sleep — the effect advances one frame at a time, so hooks keep running while it plays. Omit it inside a with block and the effect repeats until the block exits.

Parameters:

  • effect (KnownEffectKind | AbstractEffect) –

    A built effect instance or a known effect kind string.

  • duration_ms (int | None, default: None ) –

    Animation length in milliseconds. Defaults to 300; omitted inside a with block the effect repeats until exit.

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

    Foreground or accent color for color-driven effects.

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

    Background color for two-color effects.

  • direction (EffectMotion | None, default: None ) –

    Motion direction for slide and sweep effects.

  • gradient_length (int | None, default: None ) –

    Gradient length for slide and sweep effects.

  • randomness (int | None, default: None ) –

    Randomness for slide and sweep effects.

  • interpolation (EffectInterpolation | None, default: None ) –

    Interpolation curve for the effect.

  • effects (Sequence[AbstractEffect] | None, default: None ) –

    Child effects for sequence and parallel composition.

  • child (AbstractEffect | None, default: None ) –

    Child effect for repeat and delay composition.

  • times (int | None, default: None ) –

    Repeat count for repeat effects.

  • fields (list[str] | None, default: None ) –

    Layout field names to target. When omitted or empty, no effect is started.

  • key (str | None, default: None ) –

    Identity used to de-duplicate this effect per target field — see AbstractEffect.key. Calling with the same key every tick replaces the running effect rather than stacking new ones.

Returns:

  • 'EffectHandle'

    A handle for the started effect.

Examples:

self.grid_effect("fade", fields=["body"])

with self.grid_effect("pulse", fields=["body"]):
    do_slow_work()
Source code in xnano/grids.py
def grid_effect(
    self,
    effect: KnownEffectKind | AbstractEffect,
    *,
    duration_ms: int | None = None,
    color: ColorLike | None = None,
    background: ColorLike | None = None,
    direction: EffectMotion | None = None,
    gradient_length: int | None = None,
    randomness: int | None = None,
    interpolation: EffectInterpolation | None = None,
    effects: Sequence[AbstractEffect] | None = None,
    child: AbstractEffect | None = None,
    times: int | None = None,
    fields: list[str] | None = None,
    key: str | None = None,
) -> "EffectHandle":
    """Run a visual effect on one or more layout field areas.

    Returns an ``EffectHandle``: truthy when at least one field was
    targeted, with ``.active`` and ``.cancel()``, and usable as a
    context manager that cancels the effect on exit.

    Starting an effect never blocks. ``duration_ms`` is the animation
    length handed to the renderer, not a sleep — the effect advances
    one frame at a time, so hooks keep running while it plays. Omit it
    inside a ``with`` block and the effect repeats until the block
    exits.

    Args:
        effect: A built effect instance or a known effect kind string.
        duration_ms: Animation length in milliseconds. Defaults to
            300; omitted inside a ``with`` block the effect repeats
            until exit.
        color: Foreground or accent color for color-driven effects.
        background: Background color for two-color effects.
        direction: Motion direction for slide and sweep effects.
        gradient_length: Gradient length for slide and sweep effects.
        randomness: Randomness for slide and sweep effects.
        interpolation: Interpolation curve for the effect.
        effects: Child effects for sequence and parallel composition.
        child: Child effect for repeat and delay composition.
        times: Repeat count for repeat effects.
        fields: Layout field names to target. When omitted or empty,
            no effect is started.
        key: Identity used to de-duplicate this effect per target
            field — see ``AbstractEffect.key``. Calling with the same
            ``key`` every tick replaces the running effect rather than
            stacking new ones.

    Returns:
        A handle for the started effect.

    Examples:
        ```python
        self.grid_effect("fade", fields=["body"])

        with self.grid_effect("pulse", fields=["body"]):
            do_slow_work()
        ```
    """
    from xnano.core.runtime import get_active_runtime
    from xnano.effects import EffectHandle, resolve_effect

    runtime = get_active_runtime()
    if runtime is None:
        return EffectHandle()
    resolved_effect = resolve_effect(
        effect,
        duration_ms=300 if duration_ms is None else duration_ms,
        color=color,
        background=background,
        direction=direction,
        gradient_length=gradient_length,
        randomness=randomness,
        interpolation=interpolation,
        effects=effects,
        child=child,
        times=times,
        key=key,
    )
    keys = runtime.play_effect(resolved_effect, fields=fields)
    return EffectHandle(
        keys=tuple(keys),
        runtime=runtime,
        effect=resolved_effect,
        fields=tuple(fields or ()),
        loop_in_context=duration_ms is None,
    )

grid_field_position

grid_field_position(name: str) -> tuple[int, int]

Return the parent-relative slide offset for a layout field.

Source code in xnano/grids.py
def grid_field_position(self, name: str) -> tuple[int, int]:
    """Return the parent-relative slide offset for a layout field."""
    return self._grid_field_position(name)

grid_set_field

grid_set_field(
    name: str,
    value: Any = UNSET,
    *,
    position: tuple[int, int] | None = None,
    strict: bool = UNSET,
    slide: Sequence[Axis] | None = UNSET,
    visible: bool | None = UNSET,
    wireframe: bool | None = UNSET,
    foreground: ColorLike | None = UNSET,
    background: ColorLike | None = UNSET,
    fill: bool | None = UNSET,
    width: SizingLike | None = UNSET,
    height: SizingLike | None = UNSET,
    gap: int | None = UNSET,
    direction: Direction | None = UNSET,
    horizontal_align: Alignment | None = UNSET,
    vertical_align: VerticalAlignment | None = UNSET,
    border: Border | None = UNSET,
    border_sides: Sequence[Side] | None = UNSET,
    border_color: ColorLike | None = UNSET,
    title: str | None = UNSET,
    title_position: FrameTitlePosition | None = UNSET,
    padding: PaddingLike | None = UNSET,
    margin: PaddingLike | None = UNSET,
    z: int | None = UNSET,
    modifiers: Sequence[CharacterModifier] | None = UNSET,
    class_name: ClassNameLike | None = UNSET,
    bold: bool = UNSET,
    dim: bool = UNSET,
    italic: bool = UNSET,
    underline: bool = UNSET,
    slow_blink: bool = UNSET,
    rapid_blink: bool = UNSET,
    reversed: bool = UNSET,
    color: ColorLike | None = UNSET,
    align: Alignment | None = UNSET
) -> None

Set a layout field's runtime value and/or per-instance field metadata.

Cannot be used on state fields. default, default_factory, init, and state cannot be changed at runtime.

For frequent, value-free style ticks (e.g. from on_tick or an effect callback), prefer grid_update_field — it skips the value/position handling this method carries for the general case.

color is a deprecated alias for foreground.

Source code in xnano/grids.py
def grid_set_field(
    self,
    name: str,
    value: Any = UNSET,
    *,
    position: tuple[int, int] | None = None,
    strict: bool = UNSET,
    slide: Sequence[Axis] | None = UNSET,
    visible: bool | None = UNSET,
    wireframe: bool | None = UNSET,
    foreground: ColorLike | None = UNSET,
    background: ColorLike | None = UNSET,
    fill: bool | None = UNSET,
    width: SizingLike | None = UNSET,
    height: SizingLike | None = UNSET,
    gap: int | None = UNSET,
    direction: Direction | None = UNSET,
    horizontal_align: Alignment | None = UNSET,
    vertical_align: VerticalAlignment | None = UNSET,
    border: Border | None = UNSET,
    border_sides: Sequence[Side] | None = UNSET,
    border_color: ColorLike | None = UNSET,
    title: str | None = UNSET,
    title_position: FrameTitlePosition | None = UNSET,
    padding: PaddingLike | None = UNSET,
    margin: PaddingLike | None = UNSET,
    z: int | None = UNSET,
    modifiers: Sequence[CharacterModifier] | None = UNSET,
    class_name: ClassNameLike | None = UNSET,
    bold: bool = UNSET,
    dim: bool = UNSET,
    italic: bool = UNSET,
    underline: bool = UNSET,
    slow_blink: bool = UNSET,
    rapid_blink: bool = UNSET,
    reversed: bool = UNSET,
    color: ColorLike | None = UNSET,
    align: Alignment | None = UNSET,
) -> None:
    """Set a layout field's runtime value and/or per-instance field metadata.

    Cannot be used on state fields. ``default``, ``default_factory``,
    ``init``, and ``state`` cannot be changed at runtime.

    For frequent, value-free style ticks (e.g. from ``on_tick`` or an
    effect callback), prefer ``grid_update_field`` — it skips the
    value/position handling this method carries for the general case.

    ``color`` is a deprecated alias for ``foreground``.
    """
    foreground = resolve_color_alias(
        foreground, color, unset=UNSET, stacklevel=3
    )
    horizontal_align = resolve_renamed_alias(
        horizontal_align,
        align,
        old="align",
        new="horizontal_align",
        unset=UNSET,
        stacklevel=3,
    )
    field_config: dict[str, Any] = {
        key: option
        for key, option in {
            "strict": strict,
            "slide": slide,
            "visible": visible,
            "wireframe": wireframe,
            "foreground": foreground,
            "background": background,
            "fill": fill,
            "width": width,
            "height": height,
            "gap": gap,
            "direction": direction,
            "horizontal_align": horizontal_align,
            "vertical_align": vertical_align,
            "border": border,
            "border_sides": border_sides,
            "border_color": border_color,
            "title": title,
            "title_position": title_position,
            "padding": padding,
            "margin": margin,
            "z": z,
            "modifiers": modifiers,
            "class_name": class_name,
            "bold": bold,
            "dim": dim,
            "italic": italic,
            "underline": underline,
            "slow_blink": slow_blink,
            "rapid_blink": rapid_blink,
            "reversed": reversed,
        }.items()
        if option is not UNSET
    }

    self._grid_apply_field_config(
        name, field_config, caller="grid_set_field"
    )

    if position is not None:
        slot = getattr(self, "_grid_last_slot_areas", {}).get(name)
        parent = getattr(self, "_grid_last_parent_area", None)
        if slot is not None and parent is not None:
            self._grid_set_field_position(
                name,
                position,
                parent_area=parent,
                slot_area=slot,
            )
        else:
            self.__dict__.setdefault("_grid_field_positions", {})[name] = (
                position
            )

    if value is not UNSET:
        field = self._grid_field_info(name)
        if self._grid_strict:
            value = self._grid_validate_field(name, value, field=field)
        object.__setattr__(self, name, value)

grid_update_field

grid_update_field(
    name: str,
    *,
    slide: Sequence[Axis] | None = UNSET,
    visible: bool | None = UNSET,
    wireframe: bool | None = UNSET,
    foreground: ColorLike | None = UNSET,
    background: ColorLike | None = UNSET,
    fill: bool | None = UNSET,
    width: SizingLike | None = UNSET,
    height: SizingLike | None = UNSET,
    gap: int | None = UNSET,
    direction: Direction | None = UNSET,
    horizontal_align: Alignment | None = UNSET,
    vertical_align: VerticalAlignment | None = UNSET,
    border: Border | None = UNSET,
    border_sides: Sequence[Side] | None = UNSET,
    border_color: ColorLike | None = UNSET,
    title: str | None = UNSET,
    title_position: FrameTitlePosition | None = UNSET,
    padding: PaddingLike | None = UNSET,
    margin: PaddingLike | None = UNSET,
    z: int | None = UNSET,
    modifiers: Sequence[CharacterModifier] | None = UNSET,
    class_name: ClassNameLike | None = UNSET,
    bold: bool = UNSET,
    dim: bool = UNSET,
    italic: bool = UNSET,
    underline: bool = UNSET,
    slow_blink: bool = UNSET,
    rapid_blink: bool = UNSET,
    reversed: bool = UNSET,
    color: ColorLike | None = UNSET,
    align: Alignment | None = UNSET
) -> None

Update a layout field's style/layout attributes, live, in-place.

A narrower sibling of grid_set_field for frequent, value-free attribute changes — pulsing a border color from on_tick, toggling a modifier from an effect callback. It has no value= or position= parameters at all, so a per-frame style tick never pays for that method's value-validation branch. Optional style patches never raise: a missing or state-only name is a no-op (no try/except needed), though unknown keyword arguments still raise as a programming error.

color is a deprecated alias for foreground.

Source code in xnano/grids.py
def grid_update_field(
    self,
    name: str,
    *,
    slide: Sequence[Axis] | None = UNSET,
    visible: bool | None = UNSET,
    wireframe: bool | None = UNSET,
    foreground: ColorLike | None = UNSET,
    background: ColorLike | None = UNSET,
    fill: bool | None = UNSET,
    width: SizingLike | None = UNSET,
    height: SizingLike | None = UNSET,
    gap: int | None = UNSET,
    direction: Direction | None = UNSET,
    horizontal_align: Alignment | None = UNSET,
    vertical_align: VerticalAlignment | None = UNSET,
    border: Border | None = UNSET,
    border_sides: Sequence[Side] | None = UNSET,
    border_color: ColorLike | None = UNSET,
    title: str | None = UNSET,
    title_position: FrameTitlePosition | None = UNSET,
    padding: PaddingLike | None = UNSET,
    margin: PaddingLike | None = UNSET,
    z: int | None = UNSET,
    modifiers: Sequence[CharacterModifier] | None = UNSET,
    class_name: ClassNameLike | None = UNSET,
    bold: bool = UNSET,
    dim: bool = UNSET,
    italic: bool = UNSET,
    underline: bool = UNSET,
    slow_blink: bool = UNSET,
    rapid_blink: bool = UNSET,
    reversed: bool = UNSET,
    color: ColorLike | None = UNSET,
    align: Alignment | None = UNSET,
) -> None:
    """Update a layout field's style/layout attributes, live, in-place.

    A narrower sibling of ``grid_set_field`` for frequent, value-free
    attribute changes — pulsing a border color from ``on_tick``,
    toggling a modifier from an effect callback. It has no ``value=``
    or ``position=`` parameters at all, so a per-frame style tick never
    pays for that method's value-validation branch. Optional style
    patches never raise: a missing or state-only ``name`` is a no-op
    (no ``try``/``except`` needed), though unknown keyword arguments
    still raise as a programming error.

    ``color`` is a deprecated alias for ``foreground``.
    """
    foreground = resolve_color_alias(
        foreground, color, unset=UNSET, stacklevel=3
    )
    horizontal_align = resolve_renamed_alias(
        horizontal_align,
        align,
        old="align",
        new="horizontal_align",
        unset=UNSET,
        stacklevel=3,
    )
    field_config: dict[str, Any] = {
        key: option
        for key, option in {
            "slide": slide,
            "visible": visible,
            "wireframe": wireframe,
            "foreground": foreground,
            "background": background,
            "fill": fill,
            "width": width,
            "height": height,
            "gap": gap,
            "direction": direction,
            "horizontal_align": horizontal_align,
            "vertical_align": vertical_align,
            "border": border,
            "border_sides": border_sides,
            "border_color": border_color,
            "title": title,
            "title_position": title_position,
            "padding": padding,
            "margin": margin,
            "z": z,
            "modifiers": modifiers,
            "class_name": class_name,
            "bold": bold,
            "dim": dim,
            "italic": italic,
            "underline": underline,
            "slow_blink": slow_blink,
            "rapid_blink": rapid_blink,
            "reversed": reversed,
        }.items()
        if option is not UNSET
    }
    self._grid_apply_field_config(
        name, field_config, caller="grid_update_field", missing="ignore"
    )

grid_set_frame

grid_set_frame(
    frame: Frame | None = UNSET,
    *,
    background: ColorLike | None = UNSET,
    border: Border | None = UNSET,
    border_color: ColorLike | None = UNSET,
    border_sides: Sequence[Side] | None = UNSET,
    title: str | None = UNSET,
    title_position: FrameTitlePosition | None = UNSET,
    padding: PaddingLike | None = UNSET
) -> None

Set this grid instance's outer frame (chrome + background fill).

The public replacement for mutating the private _grid_frame. Pass a whole frame to replace it outright, or individual keyword arguments to patch the current frame in place — a bare background fills the grid's whole area with no border required, so nested grids can be themed without any chrome. Pass frame=None to clear the frame.

Source code in xnano/grids.py
def grid_set_frame(
    self,
    frame: Frame | None = UNSET,
    *,
    background: ColorLike | None = UNSET,
    border: Border | None = UNSET,
    border_color: ColorLike | None = UNSET,
    border_sides: Sequence[Side] | None = UNSET,
    title: str | None = UNSET,
    title_position: FrameTitlePosition | None = UNSET,
    padding: PaddingLike | None = UNSET,
) -> None:
    """Set this grid instance's outer frame (chrome + background fill).

    The public replacement for mutating the private ``_grid_frame``.
    Pass a whole ``frame`` to replace it outright, or individual keyword
    arguments to patch the current frame in place — a bare
    ``background`` fills the grid's whole area with no border required,
    so nested grids can be themed without any chrome. Pass ``frame=None``
    to clear the frame.
    """
    if frame is not UNSET:
        object.__setattr__(
            self, "_grid_frame", None if frame is None else frame
        )
        return
    current = getattr(self, "_grid_frame", None) or Frame()
    patch = {
        key: value
        for key, value in {
            "background": background,
            "border": border,
            "border_color": border_color,
            "border_sides": border_sides,
            "title": title,
            "title_position": title_position,
            "padding": padding,
        }.items()
        if value is not UNSET
    }
    if not patch:
        return
    updated = dataclasses.replace(current, **patch)
    object.__setattr__(
        self, "_grid_frame", None if updated.is_empty() else updated
    )

grid_set_background

grid_set_background(background: ColorLike | None) -> None

Fill this grid instance's whole area with background.

Shorthand for grid_set_frame(background=...) — the path for theming a nested grid, replacing private _grid_frame mutation.

Source code in xnano/grids.py
def grid_set_background(self, background: ColorLike | None) -> None:
    """Fill this grid instance's whole area with ``background``.

    Shorthand for ``grid_set_frame(background=...)`` — the path for
    theming a nested grid, replacing private ``_grid_frame`` mutation.
    """
    self.grid_set_frame(background=background)

grid_schedule_update

grid_schedule_update(
    callback: Callable[[], Any] | None = None,
    *,
    field: str | None = None
) -> None

Apply an update on the UI thread before the next frame.

The thread-safe way to reflect background work in the UI: pass a callback to run on the runtime thread (so it can mutate this grid without racing the renderer), and/or a field to mark dirty. Safe to call from any thread; a no-op when no runtime is active.

Source code in xnano/grids.py
def grid_schedule_update(
    self,
    callback: Callable[[], Any] | None = None,
    *,
    field: str | None = None,
) -> None:
    """Apply an update on the UI thread before the next frame.

    The thread-safe way to reflect background work in the UI: pass a
    ``callback`` to run on the runtime thread (so it can mutate this grid
    without racing the renderer), and/or a ``field`` to mark dirty. Safe
    to call from any thread; a no-op when no runtime is active.
    """
    from xnano.core.runtime import get_active_runtime

    runtime = get_active_runtime()

    def _apply() -> None:
        if callback is not None:
            callback()
        if field is not None:
            self.grid_mark_field_dirty(field)

    if runtime is None:
        _apply()
    else:
        runtime.call_soon(_apply)

set_frame

set_frame(*args: Any, **kwargs: Any) -> None

Deprecated alias for grid_set_frame.

Source code in xnano/grids.py
@warn_renamed_attribute("BaseGrid.set_frame", "BaseGrid.grid_set_frame")
def set_frame(self, *args: Any, **kwargs: Any) -> None:
    """Deprecated alias for ``grid_set_frame``."""
    self.grid_set_frame(*args, **kwargs)

set_background

set_background(background: ColorLike | None) -> None

Deprecated alias for grid_set_background.

Source code in xnano/grids.py
@warn_renamed_attribute(
    "BaseGrid.set_background", "BaseGrid.grid_set_background"
)
def set_background(self, background: ColorLike | None) -> None:
    """Deprecated alias for ``grid_set_background``."""
    self.grid_set_background(background)

schedule_update

schedule_update(
    callback: Callable[[], Any] | None = None,
    *,
    field: str | None = None
) -> None

Deprecated alias for grid_schedule_update.

Source code in xnano/grids.py
@warn_renamed_attribute(
    "BaseGrid.schedule_update", "BaseGrid.grid_schedule_update"
)
def schedule_update(
    self,
    callback: Callable[[], Any] | None = None,
    *,
    field: str | None = None,
) -> None:
    """Deprecated alias for ``grid_schedule_update``."""
    self.grid_schedule_update(callback, field=field)

GridSettings

Bases: TypedDict

Rendering, layout, and frame settings for a BaseGrid subclass.

Attributes:

foreground instance-attribute

foreground: NotRequired[ColorLike]

The foreground color of the grid's content.

background instance-attribute

background: NotRequired[ColorLike]

The background color of the grid's frame area.

direction instance-attribute

direction: NotRequired[Direction]

The direction in which content within this grid should be laid out.

gap instance-attribute

The gap between fields in this grid.

border instance-attribute

The border style to be applied onto the outer frame of the grid.

border_sides instance-attribute

border_sides: NotRequired[list[Side]]

The sides of the border to be applied onto the outer frame of the grid.

border_color instance-attribute

border_color: NotRequired[ColorLike]

The color of the border.

title instance-attribute

title: NotRequired[str]

The title to be displayed around the outer frame of the grid.

title_position instance-attribute

The position of the title within the outer frame of the grid.

padding instance-attribute

The padding to be applied around the content area of the grid.

bold instance-attribute

Whether the grid should be rendered in bold.

dim instance-attribute

Whether the grid should be rendered in dim.

italic instance-attribute

italic: NotRequired[bool]

Whether the grid should be rendered in italic.

underline instance-attribute

underline: NotRequired[bool]

Whether the grid should be rendered in underline.

slow_blink: NotRequired[bool]

Whether the grid should be rendered in slow blink.

rapid_blink: NotRequired[bool]

Whether the grid should be rendered in rapid blink.

reversed instance-attribute

reversed: NotRequired[bool]

Whether the grid should be rendered in reversed color.

strict instance-attribute

strict: NotRequired[bool]

When True (the default), all field values are validated against their type annotations during grid construction.

Style dataclass

Style(
    *,
    foreground: ColorLike | None = None,
    background: ColorLike | None = None,
    border: Border | None = None,
    border_color: ColorLike | None = None,
    border_sides: tuple[Side, ...] | None = None,
    padding: Padding | None = None,
    margin: Padding | None = None,
    gap: int | None = None,
    width: Sizing | None = None,
    height: Sizing | None = None,
    modifiers: tuple[CharacterModifier, ...] = (),
    horizontal_align: Alignment | None = None,
    direction: Direction | None = None,
    title: str | None = None,
    title_position: str | None = None,
    visible: bool | None = None,
    cursor: str | None = None,
    passthrough_classes: tuple[str, ...] = (),
    classes: tuple[str, ...] = ()
)

Style derived from Tailwind utility classes.

Attributes:

Examples:

style = resolve_tailwind_style("text-red-500 bg-black p-2 rounded")

foreground class-attribute instance-attribute

foreground: ColorLike | None = None

Foreground color.

background class-attribute instance-attribute

background: ColorLike | None = None

Background color.

border class-attribute instance-attribute

border: Border | None = None

Border style.

border_color class-attribute instance-attribute

border_color: ColorLike | None = None

Border color.

border_sides class-attribute instance-attribute

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

Visible border sides.

padding class-attribute instance-attribute

padding: Padding | None = None

Inner spacing.

margin class-attribute instance-attribute

margin: Padding | None = None

Outer spacing.

gap class-attribute instance-attribute

gap: int | None = None

Cells between children.

width class-attribute instance-attribute

width: Sizing | None = None

Horizontal size.

height class-attribute instance-attribute

height: Sizing | None = None

Vertical size.

modifiers class-attribute instance-attribute

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

Character modifiers.

horizontal_align class-attribute instance-attribute

horizontal_align: Alignment | None = None

Horizontal alignment.

direction class-attribute instance-attribute

direction: Direction | None = None

Child layout direction.

title class-attribute instance-attribute

title: str | None = None

Optional frame title.

title_position class-attribute instance-attribute

title_position: str | None = None

Frame title alignment.

visible class-attribute instance-attribute

visible: bool | None = None

Visibility override.

cursor class-attribute instance-attribute

cursor: str | None = None

Browser cursor name.

passthrough_classes class-attribute instance-attribute

passthrough_classes: tuple[str, ...] = ()

Classes retained for browser output.

classes class-attribute instance-attribute

classes: tuple[str, ...] = ()

Normalized source classes.

Terminal

Terminal(
    *,
    state: StateT | None = None,
    title: str | None = None,
    tick_interval: int = 16,
    mouse_events: bool = False
)

Bases: Generic[StateT]

Paint and interact with an application in a terminal.

Terminal selects a live session when one is available and otherwise uses an offscreen buffer. Use :meth:offscreen explicitly in tests.

Pass mouse_events=True to receive clicks, drags, hovers, and wheel events — required for click-to-focus and @on_click/@on_mouse hooks; it is off by default so a keyboard-only app pays nothing.

Attributes:

  • runtime (Runtime[StateT]) –

    Runtime owned by the terminal.

  • state (StateT | None) –

    Application state shared with event hooks.

  • device

    Display controls for the active session.

  • cursor

    Cursor controls for the active session.

  • actions

    Synthetic action performer.

  • stage

    Layout stage for the current root.

  • size (tuple[int, int]) –

    Viewport width and height in cells.

  • focused_group (str | None) –

    Name of the focused field group.

  • surface

    Active presentation surface.

Example

terminal = Terminal.offscreen(cols=24, rows=3) frame = terminal.render("Hello, xnano") "Hello, xnano" in frame.text True terminal.close()

Methods:

  • offscreen

    Create a terminal backed by an in-memory cell buffer.

  • supports_live_terminal

    Return whether interactive terminal sessions are available.

  • attach_grid

    Set the grid used by subsequent renders and dispatch.

  • render

    Paint one frame and return its immutable snapshot.

  • run

    Render and dispatch events until an exit is requested.

  • request_exit

    Stop the active run loop.

  • focus

    Focus a named field group.

  • blur

    Clear field focus.

  • focus_next

    Move focus forward.

  • focus_previous

    Move focus backward.

  • get_output

    Return the current cell buffer as plain text.

  • get_output_as_ansi

    Return the current cell buffer with ANSI styling.

  • copy_to_clipboard

    Copy text when the active platform supports clipboard writes.

  • close

    Restore and release the owned runtime.

Source code in xnano/terminal.py
def __init__(
    self,
    *,
    state: StateT | None = None,
    title: str | None = None,
    tick_interval: int = 16,
    mouse_events: bool = False,
) -> None:
    self._state = state
    self._title = title
    self._tick_interval = tick_interval
    self._mouse_events = mouse_events
    self._runtime: Runtime[StateT] | None = None
    self.surface = "terminal"

runtime property

runtime: Runtime[StateT]

Runtime owned by this terminal.

state property writable

state: StateT | None

Application state shared with event hooks.

device property

device

Display controls.

cursor property

cursor

Caret controls.

actions property

actions

Synthetic action performer.

stage property

stage

Layout stage for the current root.

size property

size: tuple[int, int]

Viewport width and height in cells.

focused_group property

focused_group: str | None

Name of the focused field group.

offscreen classmethod

offscreen(
    *,
    cols: int = 40,
    rows: int = 12,
    state: StateT | None = None,
    title: str | None = None
) -> "Terminal[StateT]"

Create a terminal backed by an in-memory cell buffer.

Source code in xnano/terminal.py
@classmethod
def offscreen(
    cls,
    *,
    cols: int = 40,
    rows: int = 12,
    state: StateT | None = None,
    title: str | None = None,
) -> "Terminal[StateT]":
    """Create a terminal backed by an in-memory cell buffer."""
    terminal = cls(state=state, title=title)
    terminal._runtime = Runtime.offscreen(
        cols,
        rows,
        state=state,
        title=title,
    )
    terminal.surface = "offscreen"
    return terminal

supports_live_terminal staticmethod

supports_live_terminal() -> bool

Return whether interactive terminal sessions are available.

Source code in xnano/terminal.py
@staticmethod
def supports_live_terminal() -> bool:
    """Return whether interactive terminal sessions are available."""
    return Runtime.supports_live_terminal()

attach_grid

attach_grid(grid: Any) -> None

Set the grid used by subsequent renders and dispatch.

Source code in xnano/terminal.py
def attach_grid(self, grid: Any) -> None:
    """Set the grid used by subsequent renders and dispatch."""
    self.runtime.set_root(grid)

render

render(
    *renderables: Any,
    foreground: ColorLike | None = None,
    background: ColorLike | None = None,
    modifiers: Sequence[CharacterModifier] | None = None,
    horizontal_align: Alignment | None = None,
    vertical_align: VerticalAlignment | None = None,
    border: Border | None = None,
    border_sides: Sequence[Side] | None = None,
    border_color: ColorLike | None = None,
    title: str | None = None,
    title_position: FrameTitlePosition | None = None,
    padding: PaddingLike | None = None,
    gap: int = 0,
    direction: Direction = "vertical",
    color: ColorLike | None = None,
    align: Alignment | None = None
) -> Frame

Paint one frame and return its immutable snapshot.

Parameters:

  • *renderables (Any, default: () ) –

    Grids, components, content primitives, or plain values to paint.

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

    Foreground color applied to plain values.

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

    Background color for the rendered area.

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

    Character modifiers applied to plain values.

  • horizontal_align (Alignment | None, default: None ) –

    Horizontal alignment applied to plain values.

vertical_align: Vertical alignment applied to plain values. border: Border style around the rendered area. border_sides: Border sides to draw. border_color: Border foreground color. title: Optional border title. title_position: Border edge that holds the title. padding: Space between the border and content. gap: Cells between multiple renderables. direction: Direction used to lay out multiple renderables.

Returns:

  • Frame

    A snapshot of the rendered terminal frame.

Source code in xnano/terminal.py
def render(
    self,
    *renderables: Any,
    foreground: ColorLike | None = None,
    background: ColorLike | None = None,
    modifiers: Sequence[CharacterModifier] | None = None,
    horizontal_align: Alignment | None = None,
    vertical_align: VerticalAlignment | None = None,
    border: Border | None = None,
    border_sides: Sequence[Side] | None = None,
    border_color: ColorLike | None = None,
    title: str | None = None,
    title_position: FrameTitlePosition | None = None,
    padding: PaddingLike | None = None,
    gap: int = 0,
    direction: Direction = "vertical",
    color: ColorLike | None = None,
    align: Alignment | None = None,
) -> Frame:
    """Paint one frame and return its immutable snapshot.

    Args:
        *renderables: Grids, components, content primitives, or plain
            values to paint.
        foreground: Foreground color applied to plain values.
        background: Background color for the rendered area.
        modifiers: Character modifiers applied to plain values.
        horizontal_align: Horizontal alignment applied to plain values.
    vertical_align: Vertical alignment applied to plain values.
        border: Border style around the rendered area.
        border_sides: Border sides to draw.
        border_color: Border foreground color.
        title: Optional border title.
        title_position: Border edge that holds the title.
        padding: Space between the border and content.
        gap: Cells between multiple renderables.
        direction: Direction used to lay out multiple renderables.

    Returns:
        A snapshot of the rendered terminal frame.
    """
    foreground = resolve_color_alias(foreground, color, stacklevel=3)
    horizontal_align = resolve_renamed_alias(
        horizontal_align,
        align,
        old="align",
        new="horizontal_align",
        stacklevel=3,
    )
    runtime = self._ensure_runtime()
    if renderables:
        runtime.set_root(renderables[0] if len(renderables) == 1 else None)
    return runtime.render(
        *renderables,
        foreground=foreground,
        background=background,
        modifiers=modifiers,
        horizontal_align=horizontal_align,
        vertical_align=vertical_align,
        border=border,
        border_sides=border_sides,
        border_color=border_color,
        title=title,
        title_position=title_position,
        padding=padding,
        gap=gap,
        direction=direction,
    )

run

run(
    *renderables: Any,
    foreground: ColorLike | None = None,
    background: ColorLike | None = None,
    modifiers: Sequence[CharacterModifier] | None = None,
    horizontal_align: Alignment | None = None,
    vertical_align: VerticalAlignment | None = None,
    border: Border | None = None,
    border_sides: Sequence[Side] | None = None,
    border_color: ColorLike | None = None,
    title: str | None = None,
    title_position: FrameTitlePosition | None = None,
    padding: PaddingLike | None = None,
    gap: int = 0,
    direction: Direction = "vertical",
    color: ColorLike | None = None,
    align: Alignment | None = None
) -> None

Render and dispatch events until an exit is requested.

Parameters:

  • *renderables (Any, default: () ) –

    Grids, components, content primitives, or plain values to paint.

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

    Foreground color applied to plain values.

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

    Background color for the rendered area.

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

    Character modifiers applied to plain values.

  • horizontal_align (Alignment | None, default: None ) –

    Horizontal alignment applied to plain values.

vertical_align: Vertical alignment applied to plain values. border: Border style around the rendered area. border_sides: Border sides to draw. border_color: Border foreground color. title: Optional border title. title_position: Border edge that holds the title. padding: Space between the border and content. gap: Cells between multiple renderables. direction: Direction used to lay out multiple renderables.

Source code in xnano/terminal.py
def run(
    self,
    *renderables: Any,
    foreground: ColorLike | None = None,
    background: ColorLike | None = None,
    modifiers: Sequence[CharacterModifier] | None = None,
    horizontal_align: Alignment | None = None,
    vertical_align: VerticalAlignment | None = None,
    border: Border | None = None,
    border_sides: Sequence[Side] | None = None,
    border_color: ColorLike | None = None,
    title: str | None = None,
    title_position: FrameTitlePosition | None = None,
    padding: PaddingLike | None = None,
    gap: int = 0,
    direction: Direction = "vertical",
    color: ColorLike | None = None,
    align: Alignment | None = None,
) -> None:
    """Render and dispatch events until an exit is requested.

    Args:
        *renderables: Grids, components, content primitives, or plain
            values to paint.
        foreground: Foreground color applied to plain values.
        background: Background color for the rendered area.
        modifiers: Character modifiers applied to plain values.
        horizontal_align: Horizontal alignment applied to plain values.
    vertical_align: Vertical alignment applied to plain values.
        border: Border style around the rendered area.
        border_sides: Border sides to draw.
        border_color: Border foreground color.
        title: Optional border title.
        title_position: Border edge that holds the title.
        padding: Space between the border and content.
        gap: Cells between multiple renderables.
        direction: Direction used to lay out multiple renderables.
    """
    foreground = resolve_color_alias(foreground, color, stacklevel=3)
    horizontal_align = resolve_renamed_alias(
        horizontal_align,
        align,
        old="align",
        new="horizontal_align",
        stacklevel=3,
    )
    runtime = self._ensure_runtime(live=True)
    if renderables:
        runtime.set_root(renderables[0] if len(renderables) == 1 else None)
    live = runtime.is_live
    render_frame = runtime._render if live else runtime.render
    try:
        while True:
            render_frame(
                *renderables,
                foreground=foreground,
                background=background,
                modifiers=modifiers,
                horizontal_align=horizontal_align,
                vertical_align=vertical_align,
                border=border,
                border_sides=border_sides,
                border_color=border_color,
                title=title,
                title_position=title_position,
                padding=padding,
                gap=gap,
                direction=direction,
            )
            if live:
                runtime._frame_commands.clear()
            if not runtime.pump():
                break
    finally:
        self.close()

request_exit

request_exit() -> None

Stop the active run loop.

Source code in xnano/terminal.py
def request_exit(self) -> None:
    """Stop the active run loop."""
    if self._runtime is not None:
        self._runtime.request_exit()

focus

focus(group: str) -> bool

Focus a named field group.

Source code in xnano/terminal.py
def focus(self, group: str) -> bool:
    """Focus a named field group."""
    return self.runtime.focus(group)

blur

blur() -> None

Clear field focus.

Source code in xnano/terminal.py
def blur(self) -> None:
    """Clear field focus."""
    self.runtime.blur()

focus_next

focus_next() -> bool

Move focus forward.

Source code in xnano/terminal.py
def focus_next(self) -> bool:
    """Move focus forward."""
    return self.runtime.focus_next()

focus_previous

focus_previous() -> bool

Move focus backward.

Source code in xnano/terminal.py
def focus_previous(self) -> bool:
    """Move focus backward."""
    return self.runtime.focus_previous()

get_output

get_output() -> str

Return the current cell buffer as plain text.

Source code in xnano/terminal.py
def get_output(self) -> str:
    """Return the current cell buffer as plain text."""
    return self.runtime.get_output()

get_output_as_ansi

get_output_as_ansi() -> str

Return the current cell buffer with ANSI styling.

Source code in xnano/terminal.py
def get_output_as_ansi(self) -> str:
    """Return the current cell buffer with ANSI styling."""
    return self.runtime.get_output_as_ansi()

copy_to_clipboard

copy_to_clipboard(text: str) -> bool

Copy text when the active platform supports clipboard writes.

Source code in xnano/terminal.py
def copy_to_clipboard(self, text: str) -> bool:
    """Copy text when the active platform supports clipboard writes."""
    return self.runtime.device.copy_to_clipboard(text)

close

close() -> None

Restore and release the owned runtime.

Source code in xnano/terminal.py
def close(self) -> None:
    """Restore and release the owned runtime."""
    if self._runtime is not None:
        self._runtime.close()
        self._runtime = None

Web

Web(
    *,
    state: Any = None,
    title: str | None = None,
    width: int = 80,
    height: int = 24
)

Serve an application in a browser from an offscreen runtime.

Attributes:

  • state

    Application state shared with event and request hooks.

  • title

    Browser document title.

  • width

    Offscreen viewport width in cells.

  • height

    Offscreen viewport height in cells.

  • surface

    Presentation surface name.

Example

from xnano.components import Text web = Web(title="Status")

web.run(Text("Ready"), host="127.0.0.1", port=8000)

Methods:

  • run

    Serve an application until interrupted.

  • close

    Stop a managed server when one has been attached.

Source code in xnano/web.py
def __init__(
    self,
    *,
    state: Any = None,
    title: str | None = None,
    width: int = 80,
    height: int = 24,
) -> None:
    self.state = state
    """Application state passed to the offscreen runtime."""
    self.title = title or "xnano"
    """Browser document title."""
    self.width = width
    """Offscreen viewport width in cells."""
    self.height = height
    """Offscreen viewport height in cells."""
    self.surface = "web"
    """Presentation surface name."""
    self._server: Any | None = None

state instance-attribute

state = state

Application state passed to the offscreen runtime.

title instance-attribute

title = title or 'xnano'

Browser document title.

width instance-attribute

width = width

Offscreen viewport width in cells.

height instance-attribute

height = height

Offscreen viewport height in cells.

surface instance-attribute

surface = 'web'

Presentation surface name.

run

run(
    source: Any,
    *,
    host: str = "127.0.0.1",
    port: int = 8000
) -> None

Serve an application until interrupted.

Parameters:

  • source (Any) –

    Grid/component instance, class, or factory.

  • host (str, default: '127.0.0.1' ) –

    Bind address.

  • port (int, default: 8000 ) –

    Bind port.

Source code in xnano/web.py
def run(
    self,
    source: Any,
    *,
    host: str = "127.0.0.1",
    port: int = 8000,
) -> None:
    """Serve an application until interrupted.

    Args:
        source: Grid/component instance, class, or factory.
        host: Bind address.
        port: Bind port.
    """
    from xnano.server.native import serve_native

    factory, _, _ = grid_factory(source)
    serve_native(
        factory,
        state=self.state,
        title=self.title,
        host=host,
        port=port,
        width=self.width,
        height=self.height,
    )

close

close() -> None

Stop a managed server when one has been attached.

Source code in xnano/web.py
def close(self) -> None:
    """Stop a managed server when one has been attached."""
    if self._server is not None:
        self._server.shutdown()
        self._server.server_close()
        self._server = None

Field

Field(
    default: None,
    *,
    default_factory: None = None,
    state: bool = False,
    strict: bool = False,
    init: bool = True,
    visible: bool | None = None,
    modifiers: Sequence[CharacterModifier] | None = None,
    foreground: ColorLike | None = None,
    color: ColorLike | None = _COLOR_UNSET,
    background: ColorLike | None = None,
    fill: bool | None = None,
    width: SizingLike | None = None,
    height: SizingLike | None = None,
    gap: int | None = None,
    direction: Direction | None = None,
    horizontal_align: Alignment | None = None,
    vertical_align: VerticalAlignment | None = None,
    border: Border | None = None,
    border_sides: Sequence[Side] | None = None,
    border_color: ColorLike | None = None,
    title: str | None = None,
    title_position: FrameTitlePosition | None = None,
    padding: PaddingLike | None = None,
    margin: PaddingLike | None = None,
    z: int | None = None,
    overlay: bool = False,
    slide: Sequence[Axis] | None = None,
    group: str | None = None,
    autofocus: bool | None = None,
    scroll: ScrollLike | None = None,
    wireframe: bool | None = None,
    class_name: ClassNameLike | None = None,
    align: Alignment | None = None
) -> Any
Field(
    default: _T,
    *,
    default_factory: None = None,
    state: bool = False,
    strict: bool = False,
    init: bool = True,
    visible: bool | None = None,
    modifiers: Sequence[CharacterModifier] | None = None,
    foreground: ColorLike | None = None,
    color: ColorLike | None = _COLOR_UNSET,
    background: ColorLike | None = None,
    fill: bool | None = None,
    width: SizingLike | None = None,
    height: SizingLike | None = None,
    gap: int | None = None,
    direction: Direction | None = None,
    horizontal_align: Alignment | None = None,
    vertical_align: VerticalAlignment | None = None,
    border: Border | None = None,
    border_sides: Sequence[Side] | None = None,
    border_color: ColorLike | None = None,
    title: str | None = None,
    title_position: FrameTitlePosition | None = None,
    padding: PaddingLike | None = None,
    margin: PaddingLike | None = None,
    z: int | None = None,
    overlay: bool = False,
    slide: Sequence[Axis] | None = None,
    group: str | None = None,
    autofocus: bool | None = None,
    scroll: ScrollLike | None = None,
    wireframe: bool | None = None,
    class_name: ClassNameLike | None = None,
    align: Alignment | None = None
) -> _T
Field(
    *,
    default_factory: Callable[[], _T],
    state: bool = False,
    strict: bool = False,
    init: bool = True,
    visible: bool | None = None,
    modifiers: Sequence[CharacterModifier] | None = None,
    foreground: ColorLike | None = None,
    color: ColorLike | None = _COLOR_UNSET,
    background: ColorLike | None = None,
    fill: bool | None = None,
    width: SizingLike | None = None,
    height: SizingLike | None = None,
    gap: int | None = None,
    direction: Direction | None = None,
    horizontal_align: Alignment | None = None,
    vertical_align: VerticalAlignment | None = None,
    border: Border | None = None,
    border_sides: Sequence[Side] | None = None,
    border_color: ColorLike | None = None,
    title: str | None = None,
    title_position: FrameTitlePosition | None = None,
    padding: PaddingLike | None = None,
    margin: PaddingLike | None = None,
    z: int | None = None,
    overlay: bool = False,
    slide: Sequence[Axis] | None = None,
    group: str | None = None,
    autofocus: bool | None = None,
    scroll: ScrollLike | None = None,
    wireframe: bool | None = None,
    class_name: ClassNameLike | None = None,
    align: Alignment | None = None
) -> _T
Field(
    *,
    default: Any = UNSET,
    default_factory: None = None,
    state: bool = False,
    strict: bool = False,
    init: bool = True,
    visible: bool | None = None,
    modifiers: Sequence[CharacterModifier] | None = None,
    foreground: ColorLike | None = None,
    color: ColorLike | None = _COLOR_UNSET,
    background: ColorLike | None = None,
    fill: bool | None = None,
    width: SizingLike | None = None,
    height: SizingLike | None = None,
    gap: int | None = None,
    direction: Direction | None = None,
    horizontal_align: Alignment | None = None,
    vertical_align: VerticalAlignment | None = None,
    border: Border | None = None,
    border_sides: Sequence[Side] | None = None,
    border_color: ColorLike | None = None,
    title: str | None = None,
    title_position: FrameTitlePosition | None = None,
    padding: PaddingLike | None = None,
    margin: PaddingLike | None = None,
    z: int | None = None,
    overlay: bool = False,
    slide: Sequence[Axis] | None = None,
    group: str | None = None,
    autofocus: bool | None = None,
    scroll: ScrollLike | None = None,
    wireframe: bool | None = None,
    class_name: ClassNameLike | None = None,
    align: Alignment | None = None
) -> Any
Field(
    default: Any = UNSET,
    *,
    default_factory: Callable[[], Any] | None = None,
    state: bool = False,
    strict: bool = False,
    init: bool = True,
    visible: bool | None = None,
    modifiers: Sequence[CharacterModifier] | None = None,
    foreground: ColorLike | None = None,
    color: ColorLike | None = _COLOR_UNSET,
    background: ColorLike | None = None,
    fill: bool | None = None,
    width: SizingLike | None = None,
    height: SizingLike | None = None,
    gap: int | None = None,
    direction: Direction | None = None,
    horizontal_align: Alignment | None = None,
    vertical_align: VerticalAlignment | None = None,
    border: Border | None = None,
    border_sides: Sequence[Side] | None = None,
    border_color: ColorLike | None = None,
    title: str | None = None,
    title_position: FrameTitlePosition | None = None,
    padding: PaddingLike | None = None,
    margin: PaddingLike | None = None,
    z: int | None = None,
    overlay: bool = False,
    slide: Sequence[Axis] | None = None,
    group: str | None = None,
    autofocus: bool | None = None,
    scroll: ScrollLike | None = None,
    wireframe: bool | None = None,
    class_name: ClassNameLike | None = None,
    align: Alignment | None = None
) -> GridFieldInfo

Create a new grid field info instance.

Parameters:

  • default (Any, default: UNSET ) –

    Static default value for this field.

  • default_factory (Callable[[], Any] | None, default: None ) –

    Callable that produces the default value each time an instance is created.

  • state (bool, default: False ) –

    Whether this field is a stateful field (does not ever represent renderable content).

  • strict (bool, default: False ) –

    When True and this field is a state field, assignments are validated against the field's type annotation using pydantic_core.

  • init (bool, default: True ) –

    Whether this field should be included within the constructor of it's parent grid class.

  • visible (bool | None, default: None ) –

    Whether this field is visible on the live terminal display.

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

    Modifiers to apply to all characters within this field. This can be a list including "bold", "dim", "italic", "underline", "slow_blink", "rapid_blink", "reversed".

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

    The foreground color of content within this field.

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

    Deprecated alias for foreground; passing it emits a DeprecationWarning and sets the foreground.

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

    The background color of this field. Fills the whole slot by default when set (see fill); also fills the framed content area when the field defines border/title/padding chrome.

  • fill (bool | None, default: None ) –

    Whether background fills the whole slot. None fills when a background is set; True forces a full-slot fill; False paints the color only behind text glyphs.

  • width (SizingLike | None, default: None ) –

    Horizontal extent sizing (10, "50%", "1fr", "fit", or a Sizing). Drives the split constraint in horizontal layouts; shrinks the slot along the cross axis otherwise.

  • height (SizingLike | None, default: None ) –

    Vertical extent sizing (3, "50%", "1fr", "fit", or a Sizing). Drives the split constraint in vertical layouts; shrinks the slot along the cross axis otherwise.

  • gap (int | None, default: None ) –

    The gap between fields in this field or area.

  • direction (Direction | None, default: None ) –

    The direction in which content within this field or area should be laid out.

  • horizontal_align (Alignment | None, default: None ) –

    The horizontal alignment of content within this field's area.

  • vertical_align (VerticalAlignment | None, default: None ) –

    The vertical alignment of content within this field's area.

  • border (Border | None, default: None ) –

    A border style to be applied onto the outer frame of the rectangular area this field occupies.

  • border_sides (Sequence[Side] | None, default: None ) –

    The sides of the border to be applied onto the outer frame of the area this field occupies.

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

    The color of this field's border, if one is set.

  • title (str | None, default: None ) –

    A title to be displayed around the outer frame of this field's area.

  • title_position (FrameTitlePosition | None, default: None ) –

    The alignment of the title within the outer frame of this field's area.

  • padding (PaddingLike | None, default: None ) –

    The padding to be applied around the content area of this field.

  • margin (PaddingLike | None, default: None ) –

    The margin to be applied around the outer area of this field.

  • z (int | None, default: None ) –

    Layering order for this field's slot relative to its grid. None inherits the grid's own z; an explicit value stacks the field's whole subtree (text, component, or nested grid) above or below its siblings and is additive with the grid's z.

  • overlay (bool, default: False ) –

    Float this field over the grid's content area (centered, sized by width/height) instead of giving it a layout slot. Pair with a higher z for a popup or modal over the panels behind it.

  • slide (Sequence[Axis] | None, default: None ) –

    The axes along which this field may slide within its parent grid.

  • class_name (ClassNameLike | None, default: None ) –

    Tailwind CSS classes styling this field — a space-separated string or a sequence of class tokens. Classes are lowered into the standard field attributes (see xnano.tailwind); an explicit keyword argument always overrides a class-derived value. Classes with no terminal equivalent are ignored by the terminal backend and emitted verbatim by the web backend.

Returns:

  • GridFieldInfo

    A new GridFieldInfo instance with all display/layout metadata,

  • GridFieldInfo

    including the normalized class_name tokens.

Source code in xnano/fields.py
def Field(
    default: Any = UNSET,
    *,
    default_factory: Callable[[], Any] | None = None,
    state: bool = False,
    strict: bool = False,
    init: bool = True,
    visible: bool | None = None,
    modifiers: Sequence[types.CharacterModifier] | None = None,
    foreground: ColorLike | None = None,
    color: ColorLike | None = _COLOR_UNSET,
    background: ColorLike | None = None,
    fill: bool | None = None,
    width: SizingLike | None = None,
    height: SizingLike | None = None,
    gap: int | None = None,
    direction: types.Direction | None = None,
    horizontal_align: area.Alignment | None = None,
    vertical_align: area.VerticalAlignment | None = None,
    border: types.Border | None = None,
    border_sides: Sequence[types.Side] | None = None,
    border_color: ColorLike | None = None,
    title: str | None = None,
    title_position: FrameTitlePosition | None = None,
    padding: area.PaddingLike | None = None,
    margin: area.PaddingLike | None = None,
    z: int | None = None,
    overlay: bool = False,
    slide: Sequence[types.Axis] | None = None,
    group: str | None = None,
    autofocus: bool | None = None,
    scroll: types.ScrollLike | None = None,
    wireframe: bool | None = None,
    class_name: ClassNameLike | None = None,
    align: area.Alignment | None = None,
) -> GridFieldInfo:
    """Create a new grid field info instance.

    Args:
        default: Static default value for this field.
        default_factory: Callable that produces the default value each time an instance is created.
        state: Whether this field is a stateful field (does not ever represent renderable content).
        strict: When ``True`` and this field is a state field, assignments are validated
            against the field's type annotation using ``pydantic_core``.
        init: Whether this field should be included within the constructor of it's parent grid class.
        visible: Whether this field is visible on the live terminal display.
        modifiers: Modifiers to apply to all characters within this field. This can be a list
            including "bold", "dim", "italic", "underline", "slow_blink", "rapid_blink",
            "reversed".
        foreground: The foreground color of content within this field.
        color: Deprecated alias for ``foreground``; passing it emits a
            ``DeprecationWarning`` and sets the foreground.
        background: The background color of this field. Fills the whole slot by
            default when set (see ``fill``); also fills the framed content area
            when the field defines border/title/padding chrome.
        fill: Whether ``background`` fills the whole slot. ``None`` fills when a
            background is set; ``True`` forces a full-slot fill; ``False`` paints
            the color only behind text glyphs.
        width: Horizontal extent sizing (``10``, ``"50%"``, ``"1fr"``, ``"fit"``,
            or a ``Sizing``). Drives the split constraint in horizontal layouts;
            shrinks the slot along the cross axis otherwise.
        height: Vertical extent sizing (``3``, ``"50%"``, ``"1fr"``, ``"fit"``,
            or a ``Sizing``). Drives the split constraint in vertical layouts;
            shrinks the slot along the cross axis otherwise.
        gap: The gap between fields in this field or area.
        direction: The direction in which content within this field or area should be laid out.
        horizontal_align: The horizontal alignment of content within this
            field's area.
        vertical_align: The vertical alignment of content within this
            field's area.
        border: A border style to be applied onto the outer frame of the rectangular area this
            field occupies.
        border_sides: The sides of the border to be applied onto the outer frame of the area
            this field occupies.
        border_color: The color of this field's border, if one is set.
        title: A title to be displayed around the outer frame of this field's area.
        title_position: The alignment of the title within the outer frame of this
            field's area.
        padding: The padding to be applied around the content area of this field.
        margin: The margin to be applied around the outer area of this field.
        z: Layering order for this field's slot relative to its grid. ``None``
            inherits the grid's own ``z``; an explicit value stacks the field's
            whole subtree (text, component, or nested grid) above or below its
            siblings and is additive with the grid's ``z``.
        overlay: Float this field over the grid's content area (centered, sized
            by ``width``/``height``) instead of giving it a layout slot. Pair
            with a higher ``z`` for a popup or modal over the panels behind it.
        slide: The axes along which this field may slide within its parent grid.
        class_name: Tailwind CSS classes styling this field — a space-separated
            string or a sequence of class tokens. Classes are lowered into the
            standard field attributes (see ``xnano.tailwind``); an explicit
            keyword argument always overrides a class-derived value. Classes
            with no terminal equivalent are ignored by the terminal backend and
            emitted verbatim by the web backend.

    Returns:
        A new ``GridFieldInfo`` instance with all display/layout metadata,
        including the normalized ``class_name`` tokens.
    """
    if color is not _COLOR_UNSET:
        warn_color_alias(stacklevel=2)
        if foreground is None:
            foreground = color
    horizontal_align = resolve_renamed_alias(
        horizontal_align,
        align,
        old="align",
        new="horizontal_align",
        stacklevel=2,
    )
    tokens: tuple[str, ...] | None = None
    if class_name is not None:
        from xnano.tailwind import (
            normalize_tailwind_classes,
            resolve_tailwind_classes,
        )

        tokens = normalize_tailwind_classes(class_name)
        resolved = resolve_tailwind_classes(tokens)
        if foreground is None:
            foreground = resolved.foreground
        if background is None:
            background = resolved.background
        if border is None:
            border = resolved.border
        if border_color is None:
            border_color = resolved.border_color
        if border_sides is None:
            border_sides = resolved.border_sides
        if padding is None:
            padding = resolved.padding
        if margin is None:
            margin = resolved.margin
        if gap is None:
            gap = resolved.gap
        if width is None:
            width = resolved.width
        if height is None:
            height = resolved.height
        if modifiers is None and resolved.modifiers:
            modifiers = resolved.modifiers
        if horizontal_align is None:
            horizontal_align = resolved.horizontal_align
        if direction is None:
            direction = resolved.direction

    return GridFieldInfo(
        default=default,
        default_factory=default_factory,
        state=state,
        strict=strict,
        init=init,
        visible=visible,
        foreground=foreground,
        modifiers=modifiers,
        background=background,
        fill=fill,
        width=Sizing.parse(width),
        height=Sizing.parse(height),
        gap=gap,
        direction=direction,
        horizontal_align=horizontal_align,
        vertical_align=vertical_align,
        border=border,
        border_sides=border_sides,
        border_color=border_color,
        title=title,
        title_position=title_position,
        padding=padding,
        margin=margin,
        z=z,
        overlay=overlay,
        slide=_normalize_slide_axes(slide),
        group=group,
        autofocus=autofocus,
        scroll=scroll,
        wireframe=wireframe,
        class_name=tokens,
    )  # type: ignore[return-value]

on_action

on_action(
    action: Any,
) -> Callable[[EventHookFunction], EventHookFunction]

Bind a prebuilt Action as a hook trigger.

User-facing sugar for storing an Action on a handler:

SAVE = Action.keyboard("ctrl+s")

@on_action(SAVE)
def save(self, ctx): ...
Source code in xnano/hooks.py
def on_action(
    action: Any,
    /,
) -> Callable[[EventHookFunction], EventHookFunction]:
    """Bind a prebuilt ``Action`` as a hook trigger.

    User-facing sugar for storing an Action on a handler:

        SAVE = Action.keyboard("ctrl+s")

        @on_action(SAVE)
        def save(self, ctx): ...
    """
    from xnano.actions import (
        ClickAction,
        ClipboardAction,
        FocusAction,
        KeyboardAction,
        MouseAction,
        ResizeAction,
        TickAction,
    )

    def decorator(fn: EventHookFunction) -> EventHookFunction:
        if isinstance(action, KeyboardAction):
            setattr(fn, ON_KEYBOARD_HOOK_ATTR, True)
            setattr(
                fn, ON_KEYBOARD_FILTER_ATTR, (action.bindings, action.kind)
            )
        elif isinstance(action, ClickAction):
            return _decorate_on_mouse_hook(
                fn, buttons=(action.button,), kind="press", field=action.field
            )
        elif isinstance(action, MouseAction):
            return _decorate_on_mouse_hook(
                fn, buttons=tuple(action.buttons), kind=action.kind
            )
        elif isinstance(action, FocusAction):
            setattr(fn, ON_FOCUS_HOOK_ATTR, True)
            setattr(fn, ON_FOCUS_FIELD_ATTR, action.field)
            setattr(fn, ON_FOCUS_KIND_ATTR, action.kind)
        elif isinstance(action, ClipboardAction):
            setattr(fn, ON_CLIPBOARD_HOOK_ATTR, True)
        elif isinstance(action, ResizeAction):
            setattr(fn, ON_RESIZE_HOOK_ATTR, True)
        elif isinstance(action, TickAction):
            return _decorate_on_tick_hook(fn, action.interval_ms)
        else:
            raise TypeError(
                f"@on_action expects an Action instance, got {type(action)!r}"
            )
        return _decorate_hook_function(fn)

    return decorator

on_click

on_click(
    field: str,
) -> Callable[[EventHookFunction], EventHookFunction]
on_click(
    handler: EventHookFunction,
    /,
    *,
    field: str,
    button: MouseButton = "left",
    kind: MouseEventKind = "press",
) -> EventHookFunction
on_click(
    *,
    group: str,
    button: MouseButton = "left",
    kind: MouseEventKind = "press"
) -> Callable[[EventHookFunction], EventHookFunction]
on_click(
    field_or_handler: "str | EventHookFunction | None" = None,
    /,
    *,
    field: str | None = None,
    group: str | None = None,
    button: MouseButton = "left",
    kind: MouseEventKind = "press",
) -> "EventHookFunction | Callable[[EventHookFunction], EventHookFunction]"

Register a click handler for a grid layout field, or a group.

A field-scoped handler binds to one field on the declaring grid class. A group-scoped handler fires whenever any field sharing group is clicked, regardless of which grid it lives on — see Field(group=...).

Example

@on_click("body") def highlight_body(self, ctx): ...

@on_click(group="composer") def focus_composer(self, ctx): ctx.focus("composer")

Source code in xnano/hooks.py
def on_click(
    field_or_handler: "str | EventHookFunction | None" = None,
    /,
    *,
    field: str | None = None,
    group: str | None = None,
    button: MouseButton = "left",
    kind: MouseEventKind = "press",
) -> "EventHookFunction | Callable[[EventHookFunction], EventHookFunction]":
    """Register a click handler for a grid layout field, or a group.

    A field-scoped handler binds to one field on the declaring grid
    class. A group-scoped handler fires whenever any field sharing
    ``group`` is clicked, regardless of which grid it lives on — see
    ``Field(group=...)``.

    Example:
        @on_click("body")
        def highlight_body(self, ctx): ...

        @on_click(group="composer")
        def focus_composer(self, ctx): ctx.focus("composer")
    """
    if isinstance(field_or_handler, str):
        field_name = field_or_handler

        def decorator(fn: EventHookFunction) -> EventHookFunction:
            return _decorate_on_mouse_hook(
                fn, buttons=(button,), kind=kind, field=field_name
            )

        return decorator

    if field_or_handler is None:
        if group is None and field is None:
            raise TypeError("on_click requires a field name or a group")

        def group_decorator(fn: EventHookFunction) -> EventHookFunction:
            return _decorate_on_mouse_hook(
                fn, buttons=(button,), kind=kind, field=field, group=group
            )

        return group_decorator

    if field is None and group is None:
        raise TypeError("on_click requires a field name or a group")
    return _decorate_on_mouse_hook(
        field_or_handler,
        buttons=(button,),
        kind=kind,
        field=field,
        group=group,
    )

on_clipboard

on_clipboard(fn: EventHookFunction) -> EventHookFunction

Register a hook fired on clipboard paste events.

Source code in xnano/hooks.py
def on_clipboard(fn: EventHookFunction) -> EventHookFunction:
    """Register a hook fired on clipboard paste events."""
    setattr(fn, ON_CLIPBOARD_HOOK_ATTR, True)
    return _decorate_hook_function(fn)

on_event

on_event(fn: EventHookFunction) -> EventHookFunction

Register an event hook that triggers on every detected event.

Source code in xnano/hooks.py
def on_event(fn: EventHookFunction) -> EventHookFunction:
    """Register an event hook that triggers on every detected event."""
    setattr(fn, ON_EVENT_HOOK_ATTR, True)
    return _decorate_hook_function(fn)

on_field

on_field(
    expression: str,
) -> Callable[[EventHookFunction], EventHookFunction]

Fire the decorated handler based on this grid's own field values.

Pass an expression ("count > 0") to fire each frame it is truthy against the grid's fields. Pass a bare reference ("count", "items[0]") to fire only when that field is mutated — once per change rather than every frame.

Example

@on_field("count > 0") def _show_count(self): ...

@on_field("count") def _on_count_changed(self): ...

Source code in xnano/hooks.py
def on_field(
    expression: str,
) -> Callable[[EventHookFunction], EventHookFunction]:
    """Fire the decorated handler based on this grid's own field values.

    Pass an expression (``"count > 0"``) to fire each frame it is truthy
    against the grid's fields. Pass a bare reference (``"count"``,
    ``"items[0]"``) to fire only when that field is *mutated* — once per
    change rather than every frame.

    Example:
        @on_field("count > 0")
        def _show_count(self): ...

        @on_field("count")
        def _on_count_changed(self): ...
    """

    def decorator(fn: EventHookFunction) -> EventHookFunction:
        setattr(fn, ON_FIELD_HOOK_ATTR, True)
        setattr(fn, ON_FIELD_EXPRESSION_ATTR, expression)
        return _decorate_hook_function(fn)

    return decorator

on_focus

on_focus(handler: EventHookFunction) -> EventHookFunction
on_focus(
    field: str, /, *, kind: FocusHookKind | None = None
) -> Callable[[EventHookFunction], EventHookFunction]
on_focus(
    *,
    field: str | None = None,
    group: str | None = None,
    kind: FocusHookKind | None = None
) -> Callable[[EventHookFunction], EventHookFunction]
on_focus(
    handler_or_field: "EventHookFunction | str | None" = None,
    /,
    *,
    field: str | None = None,
    group: str | None = None,
    kind: FocusHookKind | None = None,
) -> "EventHookFunction | Callable[[EventHookFunction], EventHookFunction]"

Register a focus hook for the terminal window, a field, or a group.

Bare @on_focus fires on OS-level terminal focus gained/lost. Pass a field name for application field focus; pass group= to listen across grids — see Field(group=...).

Source code in xnano/hooks.py
def on_focus(
    handler_or_field: "EventHookFunction | str | None" = None,
    /,
    *,
    field: str | None = None,
    group: str | None = None,
    kind: FocusHookKind | None = None,
) -> "EventHookFunction | Callable[[EventHookFunction], EventHookFunction]":
    """Register a focus hook for the terminal window, a field, or a group.

    Bare ``@on_focus`` fires on OS-level terminal focus gained/lost.
    Pass a field name for application field focus; pass ``group=`` to
    listen across grids — see ``Field(group=...)``.
    """

    def _decorate(
        fn: EventHookFunction,
        *,
        field_name: str | None,
        group_name: str | None,
        focus_kind: FocusHookKind | None,
    ) -> EventHookFunction:
        setattr(fn, ON_FOCUS_HOOK_ATTR, True)
        if field_name is not None:
            setattr(fn, ON_FOCUS_FIELD_ATTR, field_name)
        if group_name is not None:
            setattr(fn, ON_FOCUS_GROUP_ATTR, group_name)
        if focus_kind is not None:
            setattr(fn, ON_FOCUS_KIND_ATTR, focus_kind)
        return _decorate_hook_function(fn)

    if callable(handler_or_field):
        return _decorate(
            handler_or_field,
            field_name=field,
            group_name=group,
            focus_kind=kind,
        )

    resolved_field = (
        handler_or_field if isinstance(handler_or_field, str) else field
    )

    def decorator(fn: EventHookFunction) -> EventHookFunction:
        return _decorate(
            fn, field_name=resolved_field, group_name=group, focus_kind=kind
        )

    return decorator

on_keyboard

on_keyboard(
    key: KeyboardBinding,
    /,
    *keys: KeyboardBinding,
    kind: KeyboardEventKind | None = None,
) -> Callable[[EventHookFunction], EventHookFunction]
on_keyboard(
    *,
    key: KeyboardBinding,
    kind: KeyboardEventKind | None = None
) -> Callable[[EventHookFunction], EventHookFunction]
on_keyboard(
    handler: EventHookFunction,
    /,
    *,
    key: KeyboardBinding | None = None,
    kind: KeyboardEventKind | None = None,
) -> EventHookFunction
on_keyboard(
    handler_or_key: "EventHookFunction | KeyboardBinding | None" = None,
    /,
    *keys: KeyboardBinding,
    key: KeyboardBinding | None = None,
    kind: KeyboardEventKind | None = None,
) -> "EventHookFunction | Callable[[EventHookFunction], EventHookFunction]"

Register a keyboard event hook.

Example

@on_keyboard("q") def quit(self, ctx): ...

@on_keyboard("enter", kind="press") def submit(self): ...

Source code in xnano/hooks.py
def on_keyboard(
    handler_or_key: "EventHookFunction | KeyboardBinding | None" = None,
    /,
    *keys: KeyboardBinding,
    key: KeyboardBinding | None = None,
    kind: KeyboardEventKind | None = None,
) -> "EventHookFunction | Callable[[EventHookFunction], EventHookFunction]":
    """Register a keyboard event hook.

    Example:
        @on_keyboard("q")
        def quit(self, ctx): ...

        @on_keyboard("enter", kind="press")
        def submit(self): ...
    """
    if callable(handler_or_key):
        bindings: tuple[KeyboardBinding, ...] = (
            (key,) if key is not None else keys
        )
        setattr(handler_or_key, ON_KEYBOARD_HOOK_ATTR, True)
        setattr(handler_or_key, ON_KEYBOARD_FILTER_ATTR, (bindings, kind))
        return _decorate_hook_function(handler_or_key)

    if handler_or_key is not None:
        keys = (handler_or_key, *keys)  # type: ignore[assignment]
    elif key is not None:
        keys = (key, *keys)
    filter_spec = (keys, kind)

    def decorator(fn: EventHookFunction) -> EventHookFunction:
        setattr(fn, ON_KEYBOARD_HOOK_ATTR, True)
        setattr(fn, ON_KEYBOARD_FILTER_ATTR, filter_spec)
        return _decorate_hook_function(fn)

    return decorator

on_mouse

on_mouse(
    button: MouseButton,
    /,
    *,
    field: str | None = None,
    kind: MouseEventKind | None = None,
) -> Callable[[EventHookFunction], EventHookFunction]
on_mouse(
    *,
    button: MouseButton | None = None,
    field: str | None = None,
    kind: MouseEventKind | None = None
) -> Callable[[EventHookFunction], EventHookFunction]
on_mouse(
    handler: EventHookFunction,
    /,
    *,
    button: MouseButton | None = None,
    field: str | None = None,
    kind: MouseEventKind | None = None,
) -> EventHookFunction
on_mouse(
    handler_or_button: "EventHookFunction | MouseButton | None" = None,
    /,
    *buttons: MouseButton,
    button: MouseButton | None = None,
    field: str | None = None,
    kind: MouseEventKind | None = None,
) -> "EventHookFunction | Callable[[EventHookFunction], EventHookFunction]"

Register a mouse event hook.

Defaults to left-button press when button and kind are omitted. Pass field to bind to a grid layout field's region.

Source code in xnano/hooks.py
def on_mouse(
    handler_or_button: "EventHookFunction | MouseButton | None" = None,
    /,
    *buttons: MouseButton,
    button: MouseButton | None = None,
    field: str | None = None,
    kind: MouseEventKind | None = None,
) -> "EventHookFunction | Callable[[EventHookFunction], EventHookFunction]":
    """Register a mouse event hook.

    Defaults to left-button press when ``button`` and ``kind`` are
    omitted. Pass ``field`` to bind to a grid layout field's region.
    """
    if callable(handler_or_button):
        selected: tuple[MouseButton, ...] = (
            (button,) if button is not None else buttons
        )
        return _decorate_on_mouse_hook(
            handler_or_button, buttons=selected, kind=kind, field=field
        )

    if handler_or_button is not None:
        buttons = (handler_or_button, *buttons)  # type: ignore[assignment]
    elif button is not None:
        buttons = (button, *buttons)

    def decorator(fn: EventHookFunction) -> EventHookFunction:
        return _decorate_on_mouse_hook(
            fn, buttons=buttons, kind=kind, field=field
        )

    return decorator

on_poll

on_poll(handler: EventHookFunction) -> EventHookFunction
on_poll(
    when: PollWhen = "idle",
) -> Callable[[EventHookFunction], EventHookFunction]
on_poll(
    *, when: PollWhen
) -> Callable[[EventHookFunction], EventHookFunction]
on_poll(
    handler_or_when: "EventHookFunction | PollWhen | None" = None,
    /,
    *,
    when: PollWhen | None = None,
) -> "EventHookFunction | Callable[[EventHookFunction], EventHookFunction]"

Register a poll hook that fires on idle event waits or every frame.

Source code in xnano/hooks.py
def on_poll(
    handler_or_when: "EventHookFunction | PollWhen | None" = None,
    /,
    *,
    when: PollWhen | None = None,
) -> "EventHookFunction | Callable[[EventHookFunction], EventHookFunction]":
    """Register a poll hook that fires on idle event waits or every frame."""

    def _validate_when(resolved_when: PollWhen) -> None:
        if resolved_when not in ("idle", "frame"):
            raise TypeError(
                "on_poll expects when='idle' or when='frame', got "
                f"{resolved_when!r}"
            )

    def _decorate(
        fn: EventHookFunction, resolved_when: PollWhen
    ) -> EventHookFunction:
        _validate_when(resolved_when)
        setattr(fn, ON_POLL_HOOK_ATTR, True)
        setattr(fn, ON_POLL_WHEN_ATTR, resolved_when)
        return _decorate_hook_function(fn)

    if callable(handler_or_when):
        return _decorate(handler_or_when, when if when is not None else "idle")

    resolved: PollWhen
    if handler_or_when is not None:
        resolved = handler_or_when
    elif when is not None:
        resolved = when
    else:
        resolved = "idle"

    _validate_when(resolved)

    def decorator(fn: EventHookFunction) -> EventHookFunction:
        return _decorate(fn, resolved)

    return decorator

on_resize

on_resize(fn: EventHookFunction) -> EventHookFunction

Register a hook fired on terminal resize events.

Source code in xnano/hooks.py
def on_resize(fn: EventHookFunction) -> EventHookFunction:
    """Register a hook fired on terminal resize events."""
    setattr(fn, ON_RESIZE_HOOK_ATTR, True)
    return _decorate_hook_function(fn)

on_state

on_state(
    expression: str,
) -> Callable[[EventHookFunction], EventHookFunction]

Fire the decorated handler based on the application state.

Pass an expression ("count > 0") to fire each frame it is truthy against the state's attributes. Pass a bare reference ("count", "user.name") to fire only when that value is mutated — once per change rather than every frame.

Example

@on_state("count > 0") def _on_positive_count(self, ctx): ...

@on_state("count") def _on_count_changed(self, ctx): ...

Source code in xnano/hooks.py
def on_state(
    expression: str,
) -> Callable[[EventHookFunction], EventHookFunction]:
    """Fire the decorated handler based on the application state.

    Pass an expression (``"count > 0"``) to fire each frame it is truthy
    against the state's attributes. Pass a bare reference (``"count"``,
    ``"user.name"``) to fire only when that value is *mutated* — once per
    change rather than every frame.

    Example:
        @on_state("count > 0")
        def _on_positive_count(self, ctx): ...

        @on_state("count")
        def _on_count_changed(self, ctx): ...
    """

    def decorator(fn: EventHookFunction) -> EventHookFunction:
        setattr(fn, ON_STATE_HOOK_ATTR, True)
        setattr(fn, ON_STATE_EXPRESSION_ATTR, expression)
        return _decorate_hook_function(fn)

    return decorator

on_tick

on_tick(handler: EventHookFunction) -> EventHookFunction
on_tick(
    interval: int,
    /,
    *,
    interval_milliseconds: int | None = None,
) -> Callable[[EventHookFunction], EventHookFunction]
on_tick(
    *, interval_milliseconds: int
) -> Callable[[EventHookFunction], EventHookFunction]
on_tick(
    handler_or_interval: "EventHookFunction | int | None" = None,
    /,
    *,
    interval_milliseconds: int | None = None,
) -> "EventHookFunction | Callable[[EventHookFunction], EventHookFunction]"

Register a tick hook, optionally at a fixed interval.

Source code in xnano/hooks.py
def on_tick(
    handler_or_interval: "EventHookFunction | int | None" = None,
    /,
    *,
    interval_milliseconds: int | None = None,
) -> "EventHookFunction | Callable[[EventHookFunction], EventHookFunction]":
    """Register a tick hook, optionally at a fixed interval."""
    if callable(handler_or_interval):
        return _decorate_on_tick_hook(handler_or_interval, 0)
    resolved = (
        int(handler_or_interval)
        if isinstance(handler_or_interval, int)
        else interval_milliseconds
    )
    if isinstance(resolved, int):
        return lambda fn: _decorate_on_tick_hook(fn, resolved)
    raise TypeError(
        "on_tick expects a callable or an interval in milliseconds"
    )

render

render(
    *renderables: Any,
    direction: Direction = "vertical",
    foreground: ColorLike | None = None,
    background: ColorLike | None = None,
    modifiers: Sequence[CharacterModifier] | None = None,
    horizontal_align: Alignment | None = None,
    vertical_align: VerticalAlignment | None = None,
    border: Border | None = None,
    border_sides: Sequence[Side] | None = None,
    border_color: ColorLike | None = None,
    title: str | None = None,
    title_position: FrameTitlePosition | None = None,
    padding: PaddingLike | None = None,
    gap: int = 0,
    sep: str | None = " ",
    end: str | None = "\n",
    file: IO[str] | TextIO | None = None,
    flush: bool = False,
    stream: str | bool | None = None,
    update: bool = False,
    color: ColorLike | None = None,
    align: Alignment | None = None
) -> None

Display renderables as print-like terminal output.

A live runtime paints the values into its current viewport. Otherwise xnano uses an offscreen native terminal and writes the resulting cells to file or standard output.

Parameters:

  • *renderables (Any, default: () ) –

    Grids, components, content primitives, or plain values.

  • direction (Direction, default: 'vertical' ) –

    Direction used to lay out multiple renderables.

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

    Foreground color applied to plain values.

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

    Background color for the rendered area.

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

    Character modifiers applied to plain values.

  • horizontal_align (Alignment | None, default: None ) –

    Horizontal alignment applied to plain values.

  • vertical_align (VerticalAlignment | None, default: None ) –

    Vertical alignment applied to plain values.

  • border (Border | None, default: None ) –

    Border style around the rendered area.

  • border_sides (Sequence[Side] | None, default: None ) –

    Border sides to draw.

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

    Border foreground color.

  • title (str | None, default: None ) –

    Optional border title.

  • title_position (FrameTitlePosition | None, default: None ) –

    Border edge that holds the title.

  • padding (PaddingLike | None, default: None ) –

    Space between the border and content.

  • gap (int, default: 0 ) –

    Cells between multiple renderables.

  • sep (str | None, default: ' ' ) –

    Separator used between horizontally arranged plain values.

  • end (str | None, default: '\n' ) –

    Text appended after the rendered output.

  • file (IO[str] | TextIO | None, default: None ) –

    Text stream written outside a live runtime.

  • flush (bool, default: False ) –

    Whether to flush the output stream after writing.

  • stream (str | bool | None, default: None ) –

    Named append-or-replace output region.

  • update (bool, default: False ) –

    Replace the named stream instead of appending to it.

Source code in xnano/rendering.py
def render(
    *renderables: Any,
    direction: Direction = "vertical",
    foreground: ColorLike | None = None,
    background: ColorLike | None = None,
    modifiers: Sequence[CharacterModifier] | None = None,
    horizontal_align: Alignment | None = None,
    vertical_align: VerticalAlignment | None = None,
    border: Border | None = None,
    border_sides: Sequence[Side] | None = None,
    border_color: ColorLike | None = None,
    title: str | None = None,
    title_position: FrameTitlePosition | None = None,
    padding: PaddingLike | None = None,
    gap: int = 0,
    sep: str | None = " ",
    end: str | None = "\n",
    file: IO[str] | TextIO | None = None,
    flush: bool = False,
    stream: str | bool | None = None,
    update: bool = False,
    color: ColorLike | None = None,
    align: Alignment | None = None,
) -> None:
    """Display renderables as print-like terminal output.

    A live runtime paints the values into its current viewport. Otherwise
    xnano uses an offscreen native terminal and writes the resulting cells to
    ``file`` or standard output.

    Args:
        *renderables: Grids, components, content primitives, or plain values.
        direction: Direction used to lay out multiple renderables.
        foreground: Foreground color applied to plain values.
        background: Background color for the rendered area.
        modifiers: Character modifiers applied to plain values.
        horizontal_align: Horizontal alignment applied to plain values.
        vertical_align: Vertical alignment applied to plain values.
        border: Border style around the rendered area.
        border_sides: Border sides to draw.
        border_color: Border foreground color.
        title: Optional border title.
        title_position: Border edge that holds the title.
        padding: Space between the border and content.
        gap: Cells between multiple renderables.
        sep: Separator used between horizontally arranged plain values.
        end: Text appended after the rendered output.
        file: Text stream written outside a live runtime.
        flush: Whether to flush the output stream after writing.
        stream: Named append-or-replace output region.
        update: Replace the named stream instead of appending to it.
    """
    foreground = resolve_color_alias(foreground, color, stacklevel=3)
    horizontal_align = resolve_renamed_alias(
        horizontal_align,
        align,
        old="align",
        new="horizontal_align",
        stacklevel=3,
    )
    from xnano.core.runtime import get_active_runtime

    runtime = get_active_runtime()
    target = sys.stdout if file is None else file
    if runtime is not None and target is sys.stdout and stream is None:
        runtime.render(
            *renderables,
            direction=direction,
            foreground=foreground,
            background=background,
            modifiers=modifiers,
            horizontal_align=horizontal_align,
            vertical_align=vertical_align,
            border=border,
            border_sides=border_sides,
            border_color=border_color,
            title=title,
            title_position=title_position,
            padding=padding,
            gap=gap,
        )
        if flush:
            target.flush()
        return

    from xnano.terminal import Terminal

    values = renderables
    if direction == "horizontal" and all(
        isinstance(value, (str, int, float, bool)) for value in values
    ):
        values = ((sep if sep is not None else " ").join(map(str, values)),)
    measured_size = _plain_render_size(
        values,
        direction=direction,
        gap=gap,
        border=border,
        border_sides=border_sides,
        padding=padding,
    )
    columns, rows = (
        measured_size
        if measured_size is not None
        else shutil.get_terminal_size((80, 24))
    )
    terminal = Terminal.offscreen(cols=columns, rows=rows)
    try:
        frame = terminal.render(
            *values,
            direction=direction,
            foreground=foreground,
            background=background,
            modifiers=modifiers,
            horizontal_align=horizontal_align,
            vertical_align=vertical_align,
            border=border,
            border_sides=border_sides,
            border_color=border_color,
            title=title,
            title_position=title_position,
            padding=padding,
            gap=gap,
        )
    finally:
        terminal.close()

    # Emit ANSI when styling is present, matching print-like semantics: a
    # bare scalar with no style kwargs stays plain text, but any component,
    # grid, or content primitive carries its own color/modifiers and must
    # keep them — driving ``styled`` off the top-level kwargs alone dropped
    # the color from ``render(Text(...))``, ``render(chart)``, etc.
    styled = any(
        value is not None
        for value in (
            foreground,
            background,
            modifiers,
            border,
            border_sides,
            border_color,
        )
    ) or any(
        not isinstance(value, (str, int, float, bool)) for value in values
    )
    body = _frame_text(frame, styled=styled)
    chunk = body + ("\n" if end is None else end)
    stream_id = _normalize_stream_id(stream)
    if stream_id is None:
        target.write(chunk)
    else:
        region = _STREAM_REGIONS.setdefault(stream_id, _StreamRegion())
        if update:
            _rewind_stream_region(target, region.line_count)
            region.content = chunk
        else:
            region.content += chunk
        target.write(region.content if update else chunk)
        region.line_count = _count_display_lines(region.content)
    if flush:
        target.flush()