# TextInput

> Base abstracta de entrada de texto — cursor, selección, clipboard, foco y teclado, comunes a TextField y TextArea.

Source: https://crossfly.dev/es/docs/ui/widgets/textinput — CrossFly documentation. Full docs as one file: https://crossfly.dev/llms-full.txt

# TextInput

`TextInput` es la **clase abstracta** que concentra toda la lógica de edición compartida entre [`TextField`](/docs/ui/widgets/textfield) (una línea) y [`TextArea`](/docs/ui/widgets/textarea) (multilínea): cursor, selección, clipboard, foco/blink, atajos de teclado, navegación por flechas, long-touch con `ActionSheet` e integración con el teclado virtual. Las subclases implementan el mapeo espacial (1D vs 2D), desplazamiento, renderizado y máscaras.

**No instancias** `TextInput` directamente — instancias `TextField` o `TextArea`. Esta página es la visión general de lo que ambos heredan. Implementa `UIConstants.TextInputComponent` (`setText`/`getText`/`isPassword`/`replaceText`/`keyboardHidden`/`getCursorPos`/`hasFocus`).

> 🟡 · **Paquete:** `dev.crossfly.ui` · **Extiende:** `Component` · **Implementa:** `UIConstants.TextInputComponent` · **Abstracta**

## Cuándo usar
- Como **tipo común** al referenciar cualquier campo de entrada (`TextInput campo = new TextField(...)`).
- Al escribir código que opera sobre cursor/selección/clipboard sin importar si es 1 o N líneas.

## Cuándo NO usar
- Para instanciar un campo concreto → usa [`TextField`](/docs/ui/widgets/textfield) (1 línea) o [`TextArea`](/docs/ui/widgets/textarea) (multilínea).

## Jerarquía
`Component` → **`TextInput`** (abstracta) → [`TextField`](/docs/ui/widgets/textfield) / [`TextArea`](/docs/ui/widgets/textarea). El constructor recibe la capacidad inicial del buffer: `TextInput(int initialCapacity)`. Es `isPressable() → true`.

## Estado interno (campos clave)
| Campo | Tipo | Significado |
|---|---|---|
| `chars` | `StringBuffer` | buffer en bruto del texto |
| `text` | `String` | texto mostrado (actualizado por la subclase; inicia `""`) |
| `insertPos` | `int` | posición del cursor (inserción) |
| `startSelectPos` | `int` | inicio de la selección, o `-1` si no hay selección |
| `cursorShowing` | `boolean` | fase visible del blink del cursor |
| `keyboardCanOpen` | `boolean` | el teclado puede abrir en el próximo touch-up |
| `hasFocus` | `boolean` | tiene foco de teclado |
| `caption` | `String` | placeholder / caption flotante |

## Métodos heredados (cursor, selección, clipboard)
| Método | Retorno | Qué hace |
|---|---|---|
| `setText(String)` | `void`* | define el texto (implementado por la subclase) |
| `getText()` | `String` | texto actual (con máscara en `TextField`) |
| `setCursorPos(int start, int end)` | `void` | define selección; si `start==end`, solo mueve el cursor |
| `setCursorPos(int pos)` | `void` | cursor sin selección |
| `getCursorPos()` | `int[]` | `[startSelectPos, insertPos]` |
| `getInsertPos()` | `int` | posición del cursor |
| `hasSelection()` | `boolean` | ¿hay un intervalo seleccionado? |
| `getSelectionStart()` / `getSelectionEnd()` | `int` | min / max de las posiciones |
| `getTextBuffer()` / `getLength()` | `StringBuffer` / `int` | buffer en bruto / tamaño |
| `clipboardCopy()` / `clipboardCut()` | `void` | copia / corta la selección (vía `Device.clipboard*`) |
| `clipboardPaste()` / `clearText()` | `void` | **abstractos** — la subclase maneja máscara/`\n` |
| `mapKeys(String from, String to)` | `void` | remapeo carácter a carácter (lanza `IllegalArgumentException` si los tamaños difieren; `null`/`null` limpia) |
| `hasFocus()` | `boolean` | tiene foco de teclado |
| `restoreFocusAtEnd()` | `void` | reasigna foco y mueve el cursor al final (tras cerrar picker/keypad) |

*`setText` retorna `void` en el contrato `TextComponent`; `TextField.text(...)`/`TextArea.text(...)` ofrecen variantes **fluidas** que retornan el propio campo.

## Callbacks y eventos
| Callback | Cuándo se dispara |
|---|---|
| `onAction(Action)` (heredado) | en `onTouchUp` (tras abrir el teclado) |
| `onFocus()` / `onBlur()` | ganancia / pérdida de foco (activa/desactiva cursor y blink) |

No existe un `onChange` dedicado: para reaccionar a cambios, observa vía callback de acción o consulta `getText()`.

## Estados
- **focused:** cursor activado y parpadeando (`cursorShowing`), el teclado/`Keypad` abre al toque; `onFocus()`/`onBlur()` controlan la transición.
- **selection:** intervalo `[startSelectPos, insertPos]`; long-touch selecciona todo y abre el `ActionSheet` (Copy/Cut/Paste/Clear).
- **disabled / read-only:** `setEnabled(false)` o `editable=false` en el estilo impiden la edición.

## Teclas (heredadas de `onKeyDown`)
- **Ctrl+C / V / X** → copiar / pegar / cortar; **Ctrl+A** → seleccionar todo.
- **Backspace / Delete** → borra la selección o un carácter.
- **Left / Right / Home / End** → mueve el cursor; con **Shift** extiende la selección.

## Ejemplo completo (copiar, pegar y ejecutar)
```java
package com.exemplo;

import dev.crossfly.ui.*;
import dev.crossfly.ui.layout.*;

/** Pantalla principal ejecutable. Apunta el launcher (classname) y corre: gradlew :lwjgl3:run */
public class MainWindow extends Window
{
   @Override
   public void onShow() throws Exception
   {
      super.onShow();
      setLayout(new VerticalLayout());

      // Referencia polimórfica: TextInput es la base abstracta de TextField/TextArea
      TextInput campo = new TextField("valor inicial");
      add(LAY_AUTO, (Component) campo);

      // Label de salida
      Label saida = new Label("Leia o campo com o botão");
      add(LAY_AUTO, saida);

      // No hay evento por tecla: lee getText() al enviar
      Button enviar = new Button("Enviar")
         .onAction(c -> saida.setText("Você digitou: " + campo.getText()));
      add(LAY_AUTO, enviar);
   }
}
```

> Los fragmentos de abajo muestran **solo el núcleo** (lo que va dentro de `onShow()`). Para ejecutarlos, colócalos en el esqueleto `MainWindow` de arriba.

## Ejemplo (usar el tipo base)
```java
// Referencia polimórfica al campo concreto
TextInput campo = new TextField("valor inicial");
container.add(LAY_AUTO, (Component) campo);

// Operaciones comunes vía la base
if (campo.hasSelection())
   campo.clipboardCopy();
int[] sel = campo.getCursorPos();     // [start, insert]
campo.setCursorPos(campo.getLength()); // cursor al final
```

## Buenas prácticas
- Programa contra `TextInput`/`UIConstants.TextInputComponent` cuando el código no dependa del formato (1 vs N líneas).
- Tras cerrar un picker/`Keypad`, llama a `restoreFocusAtEnd()` para devolver el foco correctamente.

## Errores comunes
- **Intentar instanciar `TextInput`:** es abstracta — usa `TextField`/`TextArea`.
- **Esperar que `clipboardPaste()`/`clearText()` funcionen en la base:** son abstractos; el comportamiento viene de la subclase (máscara en `TextField`, `\n` en `TextArea`).

## Véase también
- Guía del área: [`ui/`](/docs/ui) · Recetas: [`cookbook/`](/docs/cookbook)
- Concepto (foco, eventos de entrada): [`concepts/`](/docs/concepts)
- Componentes relacionados: [`TextField`](/docs/ui/widgets/textfield) · [`TextArea`](/docs/ui/widgets/textarea) · [`Keypad`](/docs/ui/widgets/keypad)
