Skip to content

xnano.components.input

xnano.components.input

xnano.components.input


Edit single-line or multiline text with optional masking and length limits.

Classes:

  • Input

    Editable text field.

Input dataclass

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

Bases: Text

Editable text field.

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

Example

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

Attributes:

  • submit_keys (Sequence[str]) –

    Keys reserved for submit hooks instead of text editing.

  • auto_height (bool) –

    Grow the reported height to fit soft-wrapped content.

  • min_rows (int) –

    Minimum reported height when auto_height is set.

  • max_rows (int | None) –

    Maximum reported height when auto_height is set.

Methods:

  • component_post_init

    Force input mode, then run Text editor setup.

  • get_size

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

  • get_frame

    Optional frame/panel chrome around composed content.

  • before_render

    Called before rendering; returns the effective render area.

  • after_render

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

  • compose

    Compose interface-neutral content for this Text.

  • compose_extra_small

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

  • compose_small

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

  • compose_medium

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

  • compose_large

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

  • compose_extra_large

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

  • handle_keyboard

    Edit this text when it has focus.

  • handle_paste

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

  • get_terminal_node

    Return composed content for terminal compatibility.

submit_keys class-attribute instance-attribute

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

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

auto_height class-attribute instance-attribute

auto_height: bool = False

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

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

min_rows class-attribute instance-attribute

min_rows: int = 1

Smallest reported height (rows) while auto_height is set.

max_rows class-attribute instance-attribute

max_rows: int | None = None

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

visible class-attribute instance-attribute

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

Whether this component paints at all.

z class-attribute instance-attribute

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

Stacking order among sibling content.

fit_content class-attribute instance-attribute

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

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

focused property

focused: bool

Whether this component currently holds field focus.

content class-attribute instance-attribute

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

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

foreground class-attribute instance-attribute

foreground: ColorLike | None = None

Foreground color (deprecated alias: color).

background class-attribute instance-attribute

background: ColorLike | None = None

Background color.

modifiers class-attribute instance-attribute

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

Character modifiers such as bold or underline.

horizontal_align class-attribute instance-attribute

horizontal_align: Alignment | None = None

Horizontal alignment at the paragraph level.

vertical_align class-attribute instance-attribute

vertical_align: VerticalAlignment | None = None

Vertical alignment within the painted area.

wrap class-attribute instance-attribute

wrap: bool = True

Whether long lines may wrap.

input class-attribute instance-attribute

input: bool = False

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

placeholder class-attribute instance-attribute

placeholder: str | Text | None = None

Shown when input is empty and unfocused.

cursor class-attribute instance-attribute

cursor: int | None = None

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

multiline class-attribute instance-attribute

multiline: bool = False

When True with input, editing uses CoreTextEditor.

rows class-attribute instance-attribute

rows: int | None = None

Preferred visible height (lines) for a multiline input.

ansi class-attribute instance-attribute

ansi: bool = False

Parse ANSI SGR sequences in leaf content.

markdown class-attribute instance-attribute

markdown: bool = False

Parse leaf content as markdown.

language class-attribute instance-attribute

language: str | None = None

Pygments lexer name for syntax highlighting only.

passthrough class-attribute instance-attribute

passthrough: Sequence[str] = ()

Key bindings this input never captures while focused.

mask class-attribute instance-attribute

mask: str | None = None

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

max_length class-attribute instance-attribute

max_length: int | None = None

Maximum plain-string length; longer input is clamped.

read_only class-attribute instance-attribute

read_only: bool = False

Reject edits while remaining focusable.

tab_size class-attribute instance-attribute

tab_size: int = 4

Tab width applied to multiline tab insertion and paste.

focusable class-attribute instance-attribute

focusable: bool = False

Whether this component participates in field focus.

fill class-attribute instance-attribute

fill: bool = False

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

owns_cursor property

owns_cursor: bool

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

cursor_position property

cursor_position: tuple[int, int] | None

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

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

value property writable

value: str

Canonical plain-string content for leaf and input modes.

component_post_init

component_post_init() -> None

Force input mode, then run Text editor setup.

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

get_size

get_size(ctx: 'ComponentRenderContext') -> Size

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

Source code in xnano/components/input.py
def get_size(self, ctx: "ComponentRenderContext") -> Size:
    """Report the preferred cell size, growing with content when asked."""
    text = self.value
    width = ctx.area.width
    if self.auto_height:
        rows = self._wrapped_row_count(text, width)
        rows = max(self.min_rows, rows)
        if self.max_rows is not None:
            rows = min(self.max_rows, rows)
    else:
        rows = self.rows if self.rows is not None else text.count("\n") + 1
    preferred_width = width or max(
        (len(line) for line in text.split("\n")), default=0
    )
    return Size(width=preferred_width, height=rows)

get_frame

get_frame() -> Any | None

Optional frame/panel chrome around composed content.

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

before_render

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

Called before rendering; returns the effective render area.

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

after_render

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

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

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

compose

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

Compose interface-neutral content for this Text.

Parameters:

Returns:

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

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

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

compose_extra_small

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

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

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

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

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

compose_small

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

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

See :meth:compose_extra_small.

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

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

compose_medium

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

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

See :meth:compose_extra_small.

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

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

compose_large

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

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

See :meth:compose_extra_small.

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

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

compose_extra_large

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

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

See :meth:compose_extra_small.

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

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

handle_keyboard

handle_keyboard(keyboard: 'KeyboardEventData') -> bool

Edit this text when it has focus.

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

Parameters:

  • keyboard ('KeyboardEventData') –

    The keyboard event payload.

Returns:

  • bool

    True when the key was consumed as text editing.

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

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

    Args:
        keyboard: The keyboard event payload.

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

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

handle_paste

handle_paste(text: str) -> bool

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

Parameters:

  • text (str) –

    The pasted clipboard text.

Returns:

  • bool

    True when the paste was consumed by the native editor.

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

    Args:
        text: The pasted clipboard text.

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

get_terminal_node

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

Return composed content for terminal compatibility.

Parameters:

Returns:

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

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

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