# EditTable

<style>
    img { max-width: 100%; }
    pre { background-color: #F5F5F5; padding: .5rem; }
</style>

## Definición

Archivo: `induxsoft.controls.editable.js`

Produce una interfaz de tabla que permite al usuario manipular cada una de sus celdas. Puede mostrarse como tabla, como árbol o como tarjetas cuando el espacio es estrecho, y cada fila puede llevar indicadores visuales (badges), clases y acentos de color.

<img src="/es/api/web-components/img/edittable-demo-01.jpg">

Demos en el repositorio:

- `examples/EditTable-demo.html`: tipos de columna y edición.
- `examples/_treeTest.html`: vista de árbol.
- `examples/EditTable-agentes-demo.html`: vista de conversaciones con tarjetas tipo WhatsApp, badges, acentos y fechas relativas.

> **Compatibilidad.** Las funciones marcadas como *(nuevo)* son opcionales. Una tabla que no usa ningún atributo ni propiedad nueva genera exactamente el mismo DOM, CSS y datos que la versión anterior. Ver [Historial de cambios](#historial-de-cambios).

## Ejemplos

### Maquetación HTML por tipos de columnas

```html
<edit-table style="width: 100%;" id="editable" data="[]">
    <edit-thead>
        <edit-tr>
            <edit-th type="NoEditable" field="uno">NoEditable</edit-th>
            <edit-th type="Text" field="dos" maxlength="25">Text</edit-th>
            <edit-th type="Number" field="tres" textalign="end" format="true" thousandssep="true" decs="2" prefix="$ " sufix=" MXN" default="0.00">Number</edit-th>
            <edit-th type="Select" field="cuatro" options='{"PZA":"PIEZA","PAQ":"PAQUETE","CAJA":"CAJA"}'>Select</edit-th>
            <edit-th type="Date" field="cinco" textalign="center">Date</edit-th>
            <edit-th type="DateTime" field="seis" textalign="center">DateTime</edit-th>
            <edit-th type="Check" field="siete" textalign="center" default="No">Check</edit-th>
            <edit-th type="Custom" field="ocho" button="true">Custom</edit-th>
            <edit-th type="Memo" field="nueve" minwidth="100px" maxwidth="300px">Memo</edit-th>
        </edit-tr>
    </edit-thead>
</edit-table>
```

### Datos mediante el atributo `data`

El atributo `data` recibe una cadena JSON con un arreglo de objetos; cada propiedad corresponde al `field` de una columna.

```html
<edit-table id="editable" min-row-height="1rem" max-row-height="5rem"
    data='[{ "uno": "", "dos": "Texto", "tres": 123456.789, "cuatro": "PAQ",
             "cinco": "2025-01-01", "seis": "2025-01-03 12:00", "siete": "Sí",
             "ocho": "", "nueve": "Texto largo…" }]'>
    <edit-thead>
        <edit-tr>
            <!-- mismas columnas que el ejemplo anterior -->
        </edit-tr>
    </edit-thead>
</edit-table>
```

Si el valor de `data` se genera en el servidor o en JS, se puede asignar antes de que se dispare `DOMContentLoaded`:

```html
<edit-table id="editable">…</edit-table>
<script>
    document.getElementById('editable').setAttribute('data', JSON.stringify(filas));
</script>
```

### Datos mediante etiquetas

```html
<edit-table id="editable" min-row-height="1rem" max-row-height="5rem">
    <edit-thead>
        <edit-tr>
            <edit-th type="NoEditable" field="uno">NoEditable</edit-th>
            <edit-th type="Text" field="dos" maxlength="25">Text</edit-th>
        </edit-tr>
    </edit-thead>
    <edit-tbody>
        <edit-tr>
            <edit-td></edit-td>
            <edit-td>Cillum consequat laboris mollit irure.</edit-td>
        </edit-tr>
    </edit-tbody>
</edit-table>
```

Si no se proporciona `data`, el `DataArray` se genera a partir del contenido de las etiquetas.

### Columnas con plantilla

El atributo `template` define el contenido HTML de la celda con la sintaxis de *template literal* de JavaScript; `DataRow` es el objeto de la fila.

```html
<edit-th type="NoEditable" field="_" template='<a href="./${DataRow.chat_id}/">Ver mensajes</a>'>Chat</edit-th>
```

Si los datos provienen de terceros, use `template-encode="true"` para que los valores de `DataRow` se escapen antes de insertarse (ver [Seguridad](#seguridad)).

### Atajos de teclado

- `↓` en la última fila: agregar una fila nueva.
- `Ctrl` + `Supr`: eliminar la fila seleccionada.

Se desactivan con las propiedades del componente:

```js
document.addEventListener("DOMContentLoaded", () => {
    const table = document.getElementById("editable");
    table.AutoAddRow = false;
    table.AutoDelRow = false;
});
```

### Eventos de la tabla

```js
document.addEventListener("DOMContentLoaded", () => {
    const table = document.getElementById("editable");
    const events = table.EdiTable.Const.Events;

    table.Events[events.EnterCell] = (e) => { /* e.sender, e.td */ };
    table.Events[events.FieldUpdated] = (e) => { /* e.sender, e.row, e.field, e.value */ };
    table.Events[events.RowChanged] = (e) => { /* e.sender, e.td, e.previous_index, e.index */ };

    // Forma equivalente:
    table.addEventListener("fieldupdated", (e) => { /* … */ });
});
```

Los argumentos de cada evento se describen en [Eventos](#eventos).

### Celda personalizada con botón

Una columna `Custom` con `button="true"` muestra un botón en la celda seleccionada; su acción se define con `ButtonOnClick`.

```js
table.ButtonOnClick = (irow, icol, coldef) => {
    if (coldef.field == "ocho") {
        table.DataArray[irow][coldef.field] = prompt("Escribe el contenido aquí…", "");
        table.UpdateRow(irow);
    }
};
```

### Establecer datos con `DataArray`

```js
table.DataArray = [{ uno: "", dos: "Texto", tres: 10 }];
table._printRows();
```

### Vista de tarjetas tipo lista *(nuevo)*

```html
<edit-table id="agentes"
    hidde-selector="true" html-encode="true" template-encode="true"
    cards-mode="auto" cards-breakpoint="600px" cards-breakpoint-target="container"
    cards-layout="slots" cards-hide-empty="true" badge-tooltip="custom">
    <edit-thead>
        <edit-tr>
            <edit-th type="Badges" field="_avatar" badgegroup="avatar" card="true" cardslot="avatar"></edit-th>
            <edit-th type="NoEditable" field="username" card="true" cardslot="title">Usuario</edit-th>
            <edit-th type="NoEditable" field="userphone" card="true" cardslot="subtitle">Teléfono</edit-th>
            <edit-th type="NoEditable" field="usermail">Correo</edit-th>
            <edit-th type="NoEditable" field="lastchat" format="relative" card="true" cardslot="meta">Últ. mensaje</edit-th>
            <edit-th type="NoEditable" field="human_nm" card="true" cardslot="subtitle" cardlabel="Atiende">Humano</edit-th>
            <edit-th type="Badges" field="_estado" card="true" cardslot="badge">Estado</edit-th>
            <edit-th type="NoEditable" field="_" card="true" cardslot="footer" cardlabel=""
                template='<a href="#chat-${DataRow.chat_id}">Ver mensajes</a>'>Chat</edit-th>
        </edit-tr>
    </edit-thead>
</edit-table>
```

```js
document.addEventListener("DOMContentLoaded", () => {
    const table = document.getElementById("agentes");

    table.RowBadges = (r) => [
        { id: "avatar", group: "avatar", text: iniciales(r.username), bg: "#0B6E63", tooltip: r.userphone },
        { id: "count", text: r.messages, bg: "#1FAF5A", tooltip: `${r.messages} mensajes` },
        r.human_nm && { id: "human", icon: "👤", bg: "#0B6E63", tooltip: `Atendido por ${r.human_nm}`,
                        onClick: (e) => reasignar(e.row) }
    ];
    table.RowAccent = (r) => ({ 2: "#E5B700", 4: "#C8453B" })[r.revision] ?? null;
    table.RowClass = (r) => r.revision ? `rev-${r.revision}` : "";

    table.RefreshView();
});
```

## Comentarios

### Tipos de columnas

#### NoEditable
Todas las celdas de la columna son de solo lectura.

#### Text
Editor `input[type="text"]`. El texto ocupa una sola línea.

#### Number
Editor `input[type="number"]`. No admite caracteres alfabéticos ni especiales.

#### Select
Editor `select`, configurado con el atributo `options`.

#### Date
Editor `input[type="date"]`.

#### DateTime
Editor `input[type="datetime-local"]`.

#### Check
Editor `input[type="checkbox"]`. Los valores se convierten entre texto (`Sí`/`No`) y booleano.

#### Custom
Editor personalizado; normalmente se usa con `button="true"` y `ButtonOnClick`.

#### Memo
Editor `textarea`. El texto puede ocupar varias líneas.

#### Badges *(nuevo)*
Columna de solo lectura que muestra los [badges](#badges-por-fila) de la fila. No lee ni escribe en `DataArray`. Con varias columnas `Badges`, el atributo `badgegroup` reparte los badges entre ellas.

### Vista de tarjetas

La vista de tarjetas muestra cada fila como un bloque con sus campos uno debajo de otro. Existen dos modos:

- **Original:** se activa con `cards-responsive="true"` por debajo de 768 px del ancho de la ventana, y solo si todas las columnas son `NoEditable` (o si se usa `editable-responsive="true"`). No se modificó.
- **Configurable *(nuevo)*:** se activa al usar cualquier atributo `cards-*` de la tabla o cualquier atributo de tarjeta en las columnas (`card`, `table`, `cardorder`, `cardlabel`, `cardslot`, `cardhideempty`). Permite elegir el modo, el punto de corte, el ancho de referencia, el diseño, las columnas, su orden y etiqueta, y el espaciado.

Diseños disponibles del modo configurable:

- `grid`: etiqueta a la izquierda y valor a la derecha, como la vista original.
- `stacked`: etiqueta arriba y valor abajo.
- `slots`: lista tipo WhatsApp. Cada columna se ubica en una zona con `cardslot`:

```
┌────────┬─────────────────────────┬─────────┐
│ avatar │ title                   │ meta    │
│        ├─────────────────────────┼─────────┤
│        │ subtitle                │ badge   │
│        │ body                    │         │
├────────┴─────────────────────────┴─────────┤
│ footer                                     │
└────────────────────────────────────────────┘
```

En `slots` las etiquetas se ocultan, salvo en las columnas que declaran `cardlabel`, donde aparecen en línea ("Atiende: Ventas 28").

### Badges por fila

Un badge es un indicador circular (o en forma de píldora si contiene texto largo) que puede llevar ícono o texto, colores propios, tooltip y acción al hacer clic.

#### Modelo

```js
{
    id: "human",            // opcional; permite reemplazarlo o quitarlo
    icon: "👤",             // texto/emoji, SVG inline o "img:https://…"
    text: "3",              // texto o contador (puede combinarse con icon)
    bg: "#1FAF5A",          // color de fondo
    color: "#FFF",          // color del texto o ícono
    tooltip: "3 mensajes",  // se inserta siempre como texto
    position: "start",      // start | end | overlay-start | overlay-end
    group: "avatar",        // columna Badges de destino (badgegroup)
    size: "2rem",           // opcional
    className: "mi-clase",  // opcional
    onClick: (args) => {}   // opcional; recibe los argumentos del evento BadgeClick
}
```

Un valor simple (`"7"` o `7`) equivale a `{ text: "7" }`.

#### Cómo se asignan

De menor a mayor precedencia:

1. **Campo reservado** de la fila, configurable con `badges-field` (predeterminado `__badges`). Acepta un objeto o un arreglo.
2. **Función calculada** `RowBadges(row, irow)`. Es la forma recomendada: no altera `DataArray` y se recalcula en cada `UpdateRow`, al ordenar y al cambiar de vista.
3. **Métodos** `SetRowBadges`, `AddRowBadge` y `RemoveRowBadge`. Se asocian al objeto de la fila, o a su `key` en modo árbol, por lo que sobreviven al reordenamiento. `ResetRowBadges` los descarta.

#### Dónde se dibujan

- **Sin columnas `Badges`:** en la primera celda visible (`start`, `overlay-start`) o en la última (`end`, `overlay-end`) de la vista actual. Las posiciones `overlay-*` se superponen en la esquina de la celda. Si se oculta una columna o cambia la vista, se reubican solos.
- **Con columnas `Badges`:** dentro de esas columnas; `position` se ignora. Una columna con `badgegroup="x"` toma los badges con `group: "x"`, y una columna sin `badgegroup` toma el resto.

Los badges se colocan fuera del contenido de la celda (`div[iscellcontent]`), por lo que `GetTdValue`, `UpdateData` y el modo edición no los leen ni los borran.

#### Clic y accesibilidad

- **Cuándo es clicable:** un badge lo es si tiene `onClick`, si existe `BadgeOnClick` o si hay un manejador del evento `BadgeClick`. En ese caso recibe `role="button"` y `tabindex="0"`, y se activa con Enter o Espacio.
- **Selección de la fila:** el clic en un badge clicable no selecciona la fila ni inicia la edición, salvo con `badge-selects-row="true"`. Un badge no clicable se comporta como parte de la celda.
- **Orden de ejecución:** primero el evento `BadgeClick` (se cancela con `e.cancel = true`); después `badge.onClick(args)` o, si no existe, `BadgeOnClick(irow, badge, row, event)`.

### Fechas relativas *(nuevo)*

Con `format="relative"`, una columna de fechas se muestra como en WhatsApp: la hora si es de hoy, "Ayer", el día de la semana en los últimos siete días y la fecha corta después. El valor completo queda en el `title` de la celda y **el dato no cambia**, porque solo se modifica la presentación. El idioma se toma de `locale`; el formato puede reemplazarse con `RelativeDateFormatter`.

### Seguridad

Con `html-encode="false"` (valor predeterminado), las celdas se insertan con `innerHTML` sin escapar, y las plantillas interpolan `${DataRow.x}` sin escapar. Cuando los datos provienen de terceros (por ejemplo, nombres de perfil de WhatsApp), se recomienda:

- `html-encode="true"`, para que los valores de las celdas se muestren como texto.
- `template-encode="true"` *(nuevo)*, para que los valores dentro de las plantillas se escapen. El HTML propio de la plantilla se conserva.

En los badges:

- `tooltip` y `text` se insertan siempre como texto.
- Los SVG se sanean: se eliminan `script`, `foreignObject`, `iframe`, `animate` y `set`, los atributos `on*`, los `href` externos y las URL `javascript:`.
- `img:` solo acepta `http(s)`, rutas relativas y `data:image/png|jpeg|gif|webp`.

### Personalización visual

Los estilos del componente viven en un *shadow root* cerrado. Hay tres formas de personalizarlos:

- **Atributo `control-styles`:** CSS que se inserta dentro del componente y tiene precedencia sobre los estilos de badges y tarjetas configurables.
- **Variables CSS *(nuevo)*:** se definen desde la página y atraviesan el *shadow root*:

```css
edit-table#agentes {
    display: block;
    --et-card-field-gap: 2px;      /* distancia entre campos */
    --et-card-padding: .6rem .8rem;/* relleno de la tarjeta */
    --et-card-gap: 0px;            /* separación entre tarjetas */
    --et-card-label-width: 30%;    /* ancho de la etiqueta */
    --et-badge-size: 1.45rem;      /* tamaño de los badges */
    --et-avatar-size: 2.7rem;      /* badges en la zona avatar */
    --et-avatar-rows: 3;           /* filas que abarca el avatar */
    --et-badges-gap: .25rem;       /* separación entre badges */
}
```

- **Partes *(nuevo)*:** `badge`, `badges` y `tooltip`, estilizables con `edit-table::part(badge) { … }`.

Los atributos `cards-*` tienen precedencia sobre las variables equivalentes.

### Inicialización

- **Momento:** el componente se inicializa en el evento `DOMContentLoaded`. Un `<edit-table>` insertado después de la carga de la página no se inicializa.
- **Lectura de atributos:** los atributos se leen una sola vez, al inicializar. Para cambiar la configuración después, use las propiedades JS y llame a `RefreshView()`.
- **Registro de manejadores:** registre los eventos y *hooks* dentro de su propio manejador de `DOMContentLoaded`, declarado después del elemento.

## Atributos

### Atributos del componente

#### id (opcional)
Identificador del componente; permite acceder a él fácilmente desde JavaScript.

Tipo: `string`, Predeterminado: `""`

```html
<edit-table id="editable">
```

#### data (opcional)
Cadena JSON con los datos de la tabla.

Tipo: `array`, Predeterminado: `""`

```html
<edit-table data='[]'>
```

#### show-tree (opcional)
Muestra la tabla como árbol.

Tipo: `boolean`, Predeterminado: `false`

```html
<edit-table show-tree="false">
```

#### key (opcional)
Campo con el identificador de la fila.

Tipo: `string`, Predeterminado: `id`

```html
<edit-table key="id">
```

#### parentkey (opcional)
Campo con el identificador de la fila padre.

Tipo: `string`, Predeterminado: `idp`

```html
<edit-table parentkey="idp">
```

#### childs-field (opcional)
Campo que almacena la lista de filas hijas.

Tipo: `string`, Predeterminado: `__items`

```html
<edit-table childs-field="__items">
```

#### can-move-row (opcional)
Permite mover las filas.

Tipo: `boolean`, Predeterminado: `false`; `true` si `show-tree="true"`

```html
<edit-table can-move-row="false">
```

#### hidde-selector (opcional)
Oculta el selector de edición de la celda.

Tipo: `boolean`, Predeterminado: `false`

```html
<edit-table hidde-selector="false">
```

#### hide-row-selector (opcional)
Oculta el resaltado de la fila seleccionada.

Tipo: `boolean`, Predeterminado: `false`

```html
<edit-table hide-row-selector="false">
```

#### html-encode (opcional)
Escapa los caracteres especiales de HTML (`<`, `>`, `"`, `'`) en los valores de las celdas. No afecta el HTML propio de las columnas con `template`.

Tipo: `boolean`, Predeterminado: `false`

```html
<edit-table html-encode="true">
```

#### template-encode (opcional) *(nuevo)*
Escapa los valores de `DataRow` dentro de las plantillas (`template`).

Tipo: `boolean`, Predeterminado: `false`

```html
<edit-table template-encode="true">
```

#### readonly (opcional)
Desactiva la edición de forma global.

Tipo: `boolean` (basta con su presencia), Predeterminado: ausente

```html
<edit-table readonly>
```

#### auto-confirm (opcional)
Confirma automáticamente la edición en curso cuando la tabla pierde el foco.

Tipo: `boolean`, Predeterminado: `false`

```html
<edit-table auto-confirm="true">
```

#### sorTable (opcional)
Permite ordenar los datos al hacer clic en los encabezados de columna.

Tipo: `boolean`, Predeterminado: `true`

```html
<edit-table sorTable="false">
```

#### min-row-height (opcional)
Alto mínimo de las filas (ej: `10px`).

Tipo: `string`, Predeterminado: `none`

```html
<edit-table min-row-height="1rem">
```

#### max-row-height (opcional)
Alto máximo de las filas (ej: `30px`).

Tipo: `string`, Predeterminado: `none`

```html
<edit-table max-row-height="5rem">
```

#### row-height (opcional)
Alto de las filas (ej: `10px`).

Tipo: `string`, Predeterminado: `100%`

```html
<edit-table row-height="auto">
```

#### control-styles (opcional)
Estilos personalizados que se aplican dentro del componente (ej: `input{border:1px solid red;}`).

Tipo: `string`, Predeterminado: `""`

```html
<edit-table control-styles="">
```

#### native-events (opcional) *(nuevo)*
Permite registrar eventos nativos del DOM (`click`, `keydown`, etc.) con `addEventListener`. Sin este atributo se ignoran, como en versiones anteriores.

Tipo: `boolean`, Predeterminado: `false`

```html
<edit-table native-events="true">
```

#### cards-responsive (opcional)
Muestra las filas como tarjetas cuando la ventana mide menos de 768 px y todas las columnas son `NoEditable`.

Tipo: `boolean`, Predeterminado: `false`

```html
<edit-table cards-responsive="true">
```

#### editable-responsive (opcional)
Permite la vista de tarjetas aunque haya columnas editables.

Tipo: `boolean`, Predeterminado: `false`

```html
<edit-table cards-responsive="true" editable-responsive="true">
```

#### cards-mode (opcional) *(nuevo)*
Controla cuándo se muestran tarjetas.

- `auto`: por debajo de `cards-breakpoint`, siempre que todas las columnas sean `NoEditable` o `Badges` (o haya `editable-responsive="true"`).
- `always`: siempre tarjetas, sin la restricción de columnas.
- `never`: siempre tabla.

Tipo: `string`, Predeterminado: `auto` si `cards-responsive="true"`, si no `never`

```html
<edit-table cards-mode="auto">
```

#### cards-breakpoint (opcional) *(nuevo)*
Ancho por debajo del cual se muestran tarjetas en modo `auto`. Acepta `px`, `rem` y `em`.

Tipo: `string`, Predeterminado: `768px`

```html
<edit-table cards-breakpoint="600px">
```

#### cards-breakpoint-target (opcional) *(nuevo)*
Referencia del ancho: `viewport` (ventana) o `container` (espacio real del componente, útil en paneles laterales o vistas divididas).

Tipo: `string`, Predeterminado: `viewport`

```html
<edit-table cards-breakpoint-target="container">
```

#### cards-layout (opcional) *(nuevo)*
Diseño de la tarjeta: `grid`, `stacked` o `slots` (ver [Vista de tarjetas](#vista-de-tarjetas)).

Tipo: `string`, Predeterminado: `grid`

```html
<edit-table cards-layout="slots">
```

#### cards-field-gap (opcional) *(nuevo)*
Distancia vertical entre los campos de la tarjeta.

Tipo: `string`, Predeterminado: `4px` (`2px` en `slots`)

```html
<edit-table cards-field-gap="2px">
```

#### cards-padding (opcional) *(nuevo)*
Relleno interno de la tarjeta; acepta la forma abreviada de CSS.

Tipo: `string`, Predeterminado: `2rem 0` (`.6rem .75rem` en `slots`)

```html
<edit-table cards-padding=".6rem .8rem">
```

#### cards-gap (opcional) *(nuevo)*
Separación entre tarjetas.

Tipo: `string`, Predeterminado: `0`

```html
<edit-table cards-gap="8px">
```

#### cards-label-width (opcional) *(nuevo)*
Ancho de la etiqueta en los diseños `grid`; con `0` se ocultan todas las etiquetas.

Tipo: `string`, Predeterminado: `35%`

```html
<edit-table cards-label-width="30%">
```

#### cards-hide-empty (opcional) *(nuevo)*
Oculta en la tarjeta los campos sin valor.

Tipo: `boolean`, Predeterminado: `false`

```html
<edit-table cards-hide-empty="true">
```

#### badges-field (opcional) *(nuevo)*
Campo reservado de cada fila que contiene sus badges.

Tipo: `string`, Predeterminado: `__badges`

```html
<edit-table badges-field="__badges">
```

#### badge-tooltip (opcional) *(nuevo)*
Tipo de tooltip de los badges: `native` (atributo `title`) o `custom` (tooltip propio de posición fija, que no se recorta por el desplazamiento del contenedor y se muestra al tocar en pantallas táctiles).

Tipo: `string`, Predeterminado: `native`

```html
<edit-table badge-tooltip="custom">
```

#### badge-selects-row (opcional) *(nuevo)*
Hace que el clic en un badge clicable también seleccione la celda.

Tipo: `boolean`, Predeterminado: `false`

```html
<edit-table badge-selects-row="true">
```

#### row-class-field (opcional) *(nuevo)*
Campo de cada fila con clases CSS para su `<tr>`.

Tipo: `string`, Predeterminado: `""`

```html
<edit-table row-class-field="__class">
```

#### locale (opcional) *(nuevo)*
Idioma de las fechas relativas.

Tipo: `string`, Predeterminado: atributo `lang` del documento, o `es-MX`

```html
<edit-table locale="es-MX">
```

### Atributos de las columnas

Todos los atributos de `<edit-th>` se copian a la definición de la columna (`Columns[i]`), por lo que también pueden leerse desde JavaScript.

#### type (opcional)
Tipo de celda: `Text`, `Number`, `Date`, `DateTime`, `Memo`, `Check`, `Select`, `Custom`, `NoEditable` o `Badges` *(nuevo)*.

Tipo: `string`, Predeterminado: `Text`

```html
<edit-th type="Number">
```

#### field (opcional)
Nombre del campo donde se guarda el valor de la celda.

Tipo: `string`, Predeterminado: `""`

```html
<edit-th field="Precio">
```

#### template (opcional)
Plantilla HTML de la celda, con sintaxis de *template literal*; `DataRow` es el objeto de la fila.

Tipo: `string`, Predeterminado: `""`

```html
<edit-th template='<a href="./${DataRow.id}/">Abrir</a>'>
```

#### default (opcional)
Valor inicial de la celda al crear una fila.

Tipo: `string`, Predeterminado: `""`

```html
<edit-th default="0.0000">
```

#### readonly (opcional)
Impide editar la columna. Requiere un valor no vacío: cualquier valor, incluso `"false"`, la bloquea, mientras que el atributo sin valor (`<edit-th readonly>`) no tiene efecto.

Tipo: `string`, Predeterminado: ausente

```html
<edit-th readonly="true">
```

#### maxlength (opcional)
Límite de caracteres en columnas `Text` y `Memo`.

Tipo: `number`, Predeterminado: `""`

```html
<edit-th maxlength="25">
```

#### options (opcional)
Opciones de una columna `Select`, como objeto JSON `{ "valor": "texto" }`.

Tipo: `object`, Predeterminado: `""`

```html
<edit-th options='{"PZA":"PIEZA","CAJA":"CAJA"}'>
```

#### keyfield (opcional)
En columnas `Select`, campo de la fila donde se guarda la clave seleccionada, mientras `field` conserva el texto mostrado.

Tipo: `string`, Predeterminado: `""`

```html
<edit-th type="Select" field="unidad" keyfield="unidad_id" options='{"1":"PIEZA"}'>
```

#### button (opcional)
Muestra un botón de función personalizada en la celda seleccionada.

Tipo: `boolean`, Predeterminado: `false`

```html
<edit-th button="true">
```

#### buttondata (opcional)
Contenido HTML del botón de función personalizada.

Tipo: `string`, Predeterminado: ícono de tres puntos

```html
<edit-th buttondata="…">
```

#### textalign (opcional)
Alineación del texto de la columna: `start`/`left`, `center`, `end`/`right`.

Tipo: `string`, Predeterminado: `start`

```html
<edit-th textalign="end">
```

#### format (opcional)
- `true`: formato numérico para columnas `Number` y `NoEditable` (usa `decs`, `prefix`, `sufix` y `thousandssep`).
- `relative` *(nuevo)*: formato de fecha relativa (ver [Fechas relativas](#fechas-relativas-nuevo)).

Tipo: `string`, Predeterminado: `""`

```html
<edit-th format="true">
<edit-th format="relative">
```

#### decs (opcional)
Decimales a mostrar cuando `format="true"`.

Tipo: `number`, Predeterminado: `0`

```html
<edit-th decs="4">
```

#### prefix (opcional)
Prefijo del valor cuando `format="true"`.

Tipo: `string`, Predeterminado: `""`

```html
<edit-th prefix="$">
```

#### sufix (opcional)
Sufijo del valor cuando `format="true"`.

Tipo: `string`, Predeterminado: `""`

```html
<edit-th sufix="MXN">
```

#### thousandssep (opcional)
Separador de miles cuando `format="true"`.

Tipo: `boolean`, Predeterminado: `false`

```html
<edit-th thousandssep="true">
```

#### minwidth, maxwidth, width (opcionales)
Ancho mínimo, máximo o fijo de la columna (ej: `100px`, `10em`, `auto`).

Tipo: `string`, Predeterminado: `""`

```html
<edit-th minwidth="100px" maxwidth="300px">
```

#### class (opcional)
Clases de la columna. Con `hide-on-print` o `no-print`, la columna se oculta al imprimir.

Tipo: `string`, Predeterminado: `""`

```html
<edit-th class="no-print">
```

#### card (opcional) *(nuevo)*
Indica si la columna aparece en la tarjeta. Si **ninguna** columna declara `card`, se muestran todas; si al menos una lo declara, solo las marcadas con `true`.

Tipo: `boolean`, Predeterminado: ausente

```html
<edit-th card="true">
```

#### table (opcional) *(nuevo)*
Con `false`, la columna solo aparece en la vista de tarjetas.

Tipo: `boolean`, Predeterminado: `true`

```html
<edit-th table="false">
```

#### cardorder (opcional) *(nuevo)*
Orden dentro de la tarjeta, independiente del orden de la tabla. Acepta números negativos.

Tipo: `number`, Predeterminado: posición de la columna

```html
<edit-th cardorder="-1">
```

#### cardlabel (opcional) *(nuevo)*
Etiqueta de la columna en la tarjeta. Con `""` se quita la etiqueta completa, incluidos los dos puntos.

Tipo: `string`, Predeterminado: título de la columna

```html
<edit-th cardlabel="Atiende">
```

#### cardslot (opcional) *(nuevo)*
Zona de la tarjeta en `cards-layout="slots"`: `avatar`, `title`, `meta`, `subtitle`, `badge`, `body` o `footer`.

Tipo: `string`, Predeterminado: `body`

```html
<edit-th cardslot="title">
```

#### cardhideempty (opcional) *(nuevo)*
Sobrescribe `cards-hide-empty` para esta columna.

Tipo: `boolean`, Predeterminado: valor de `cards-hide-empty`

```html
<edit-th cardhideempty="true">
```

#### badgegroup (opcional) *(nuevo)*
En columnas `Badges`, grupo de badges que muestra la columna (`group` del badge).

Tipo: `string`, Predeterminado: `""` (badges sin grupo asignado a otra columna)

```html
<edit-th type="Badges" badgegroup="avatar">
```

## Propiedades

### Generales

- `TheadRowIndex`: (number, def: `0`) Índice de la fila del encabezado.
- `AutoAddRow`: (bool, def: `true`) Agrega filas nuevas al avanzar desde la última.
- `AutoDelRow`: (bool, def: `true`) Permite eliminar filas con el teclado.
- `EverMove`: (bool, def: `true`) Si es `true`, avanzar a la izquierda desde la primera celda sube a la última celda de la fila anterior, y avanzar a la derecha desde la última baja a la primera de la siguiente.
- `PagOffSet`: (number, def: `10`) Filas que se desplazan con `AvPág`/`RePág`.
- `DataArray`: (Array obj) Datos asociados a las filas.
- `ColumnsDefaultType`: (text, def: `"Text"`) Tipo predeterminado de las columnas.
- `Columns`: (Array obj) Definición de las columnas.
- `ReadOnly`: (bool, def: `false`) Desactiva la edición.
- `ShowAsTree`: (bool, def: `false`) Muestra la tabla como árbol.
- `CanMoveRow`: (bool, def: `false`; `true` si `ShowAsTree`) Permite mover filas.
- `TreeOptions`: (obj) Campos de la vista de árbol; si no se establece, se forma con `Key`, `ParentKey` y `Childs`.
- `Key`: (text, def: `"id"`) Campo identificador de la fila.
- `ParentKey`: (text, def: `"idp"`) Campo identificador de la fila padre.
- `Childs`: (text, def: `"__items"`) Campo con las filas hijas.
- `NumFormat`: (obj) Caracteres de separación de miles y decimales para columnas con `format="true"`.
- `hiddeSelector`: (bool, def: `false`) Oculta el selector de edición.
- `hiddeRowSelector`: (bool, def: `false`) Oculta el resaltado de la fila seleccionada.
- `htmlEncode`: (bool, def: `false`) Equivalente a `html-encode`.
- `autoConfirm`: (bool, def: `false`) Equivalente a `auto-confirm`.
- `sorTable`: (bool, def: `true`) Permite ordenar al hacer clic en los encabezados.
- `IsDirty`: (bool, solo lectura) Indica si hay filas modificadas respecto al respaldo creado con `backup()`.
- `ButtonOnClick`: (function) `(irow, icol, coldef)`; se dispara con el botón de las columnas con `button="true"`.
- `onTdPaint`: (function) `(td, irow, icol, field)`; se dispara al crear cada celda.

### Vista de tarjetas *(nuevo)*

- `CardsMode`, `CardsBreakpoint`, `CardsBreakpointTarget`, `CardsLayout`, `CardsFieldGap`, `CardsPadding`, `CardsGap`, `CardsLabelWidth`, `CardsHideEmpty`: equivalentes de los atributos `cards-*`.
- `CardColumns`: (Array) `string | {field, order, slot, label, hideEmpty}`. Alternativa programática a los atributos de columna; si se define, determina qué columnas aparecen en la tarjeta.
- `IsCardView`: (bool, solo lectura) Indica si la tabla se muestra como tarjetas en este momento.
- `onCardPaint`: (function) `(tr, irow, row)`; se dispara al pintar cada fila en vista de tarjetas y al entrar a ella.

### Badges *(nuevo)*

- `RowBadges`: (function) `(row, irow) => badge | badge[]`.
- `BadgeOnClick`: (function) `(irow, badge, row, event)`.
- `BadgesField`, `BadgeTooltip`, `BadgeSelectsRow`: equivalentes de los atributos correspondientes.

### Decoración de filas y celdas *(nuevo)*

- `RowClass`: (function) `(row, irow) => "clase1 clase2" | string[]`. Clases del `<tr>`.
- `RowClassField`: (text) Equivalente de `row-class-field`.
- `RowAccent`: (function) `(row, irow) => color | null`. Barra de color en el borde izquierdo de la fila y de la tarjeta.
- `CellClass`: (function) `(row, field, irow, coldef) => "clase" | string[]`. Clases por celda.
- `onRowPaint`: (function) `(tr, irow, row)`; se dispara al pintar o actualizar cada fila.
- `RelativeDateFormatter`: (function) `(valor, date) => string`. Reemplaza el formato de `format="relative"`.
- `Locale`, `TemplateEncode`, `NativeEvents`: equivalentes de los atributos correspondientes.

Como los atributos solo se leen al inicializar, después de cambiar cualquiera de estas propiedades llame a `RefreshView()`.

## Métodos

### Filas y datos

#### AddRow
Agrega una fila nueva al final.

```js
AddRow(): void
```

#### InsertRow
Crea una fila nueva en el índice indicado.

```js
InsertRow(rw, nofocus = false): void
```

- `number` **rw** Índice de la nueva fila.
- `boolean` **nofocus** Evita seleccionar la fila creada.

#### DeleteRow
Elimina la fila indicada.

```js
DeleteRow(row): boolean
```

#### DeleteCurrentRow
Elimina la fila seleccionada.

```js
DeleteCurrentRow(): void
```

#### UpdateRow
Actualiza lo que se muestra en la fila a partir de `DataArray`, incluidas las columnas con `template`, y vuelve a aplicar su decoración (clases, acento y badges).

```js
UpdateRow(row): boolean
```

- `number` **row** Índice de la fila.

Retorna `true` si se completó la tarea, en caso contrario `false`.

#### UpdateData
Actualiza `DataArray` con los valores de todas las filas de la tabla.

```js
UpdateData(): void
```

#### UpdateDataRow
Actualiza el objeto de `DataArray` con los valores de la fila indicada.

```js
UpdateDataRow(row): boolean
```

#### UpdateDataMember
Actualiza un campo del objeto de la fila.

```js
UpdateDataMember(row, field, value, stopfire = false): object
```

- `number` **row** Índice de la fila.
- `string` **field** Campo a actualizar.
- `string` **value** Valor a establecer.
- `boolean` **stopfire** Evita que se dispare el evento `FieldUpdated`.

Devuelve el objeto de la fila.

#### backup, restore, restoreRow
Guardan una copia de `DataArray` y la restauran, completa o por fila. Mientras existe el respaldo, `IsDirty` indica si hay cambios y se dispara `IsDirtyChanged`.

```js
backup(): void
restore(): void
restoreRow(indexRow): void
```

### Navegación y consulta

#### CellFocus
Establece el foco en la celda indicada.

```js
CellFocus(td): void
```

#### NavTo, NavToHome, NavToEnd, NavToFirstCell, NavToLastCell
Mueven el selector a una celda concreta, a la primera o última de la tabla, o a la primera o última de una fila.

```js
NavTo(row, col): void
NavToHome(): void
NavToEnd(): void
NavToFirstCell(row): void
NavToLastCell(row): void
```

#### CurrentRowIndex, CurrentColIndex, CurrentTd
Devuelven el índice de la fila, el índice de la columna o la celda seleccionada; `-1` o `null` si no hay selección.

```js
CurrentRowIndex(): number
CurrentColIndex(): number
CurrentTd(): HTMLTableCellElement
```

#### RowIndexOfTd, ColIndexOfTd, TrOfTd
Devuelven el índice de fila, el índice de columna o la fila de una celda.

```js
RowIndexOfTd(td): number
ColIndexOfTd(td): number
TrOfTd(td): HTMLTableRowElement
```

#### GetTrByIndex
Devuelve la fila con el índice indicado.

```js
GetTrByIndex(row): HTMLTableRowElement
```

#### TRCount, THCount, TDCount, ColumnsCount
Cuentan filas, encabezados, celdas de una fila y columnas.

#### GetColumnDef, GetColumnDefOfTd
Devuelven la definición de la columna seleccionada o de la columna de una celda.

#### GetTdValue, SetTdValue
Leen o establecen el valor mostrado en una celda, aplicando formato y `html-encode` cuando corresponde.

```js
GetTdValue(td, valideEncode = false): string
SetTdValue(td, value, valideEncode = false, coldef = null): void
```

### Columnas

#### hideColumn
Oculta o muestra una columna por su `field`. Los badges se reubican automáticamente.

```js
hideColumn(fieldColumn, hide = true): boolean
```

#### changeColumnTitle
Cambia el título visible de una columna.

```js
changeColumnTitle(field, title): boolean
```

#### setInputKey
Asocia un componente de búsqueda (`input-key`) a una columna `Text`.

```js
setInputKey(column, inputkey): void
```

### Árbol

- `ExpandRow(tr)` / `CollapseRow(tr)`: expanden o contraen una fila.
- `RowIndent(tr, inFront = true)`: aumenta o reduce el nivel de una fila.
- `RowIndentation(tr)`: devuelve el nivel de una fila.
- `GetTree(treeOptions)` / `SetTree(treeData, treeOptions)`: obtienen o establecen los datos en forma de árbol.
- `TreeArray(dataArray)` / `TableArray(treeArray)`: convierten entre la forma plana y la de árbol.
- `Sort(dataArray, field, desc)` / `SortTree(treeArray, field, desc)`: ordenan los datos.

### Vista y decoración *(nuevo)*

#### RefreshView
Vuelve a aplicar la configuración de tarjetas y la decoración de todas las filas. Úsela después de cambiar propiedades como `CardsMode`, `CardsLayout`, `CardColumns`, `RowClass` o `RowBadges`.

```js
RefreshView(): void
```

#### RefreshDecorations
Vuelve a aplicar clases, acentos, atributos de tarjeta y badges de todas las filas.

```js
RefreshDecorations(): void
```

### Badges *(nuevo)*

#### SetRowBadges
Reemplaza los badges de la fila; tiene precedencia sobre `RowBadges` y el campo reservado.

```js
SetRowBadges(irow, badges): boolean
```

#### AddRowBadge
Agrega un badge a la fila, o lo reemplaza si su `id` ya existe.

```js
AddRowBadge(irow, badge): boolean
```

#### RemoveRowBadge
Quita un badge por `id`, o todos los de la fila si no se indica.

```js
RemoveRowBadge(irow, id?): boolean
```

#### ResetRowBadges
Descarta los cambios hechos con los métodos anteriores y vuelve a `RowBadges` o al campo reservado.

```js
ResetRowBadges(irow): boolean
```

#### GetRowBadges
Devuelve los badges efectivos de la fila.

```js
GetRowBadges(irow): object[]
```

#### RefreshBadges
Vuelve a dibujar los badges de todas las filas sin reconstruir la tabla.

```js
RefreshBadges(): void
```

## Eventos

Se registran en `table.Events[table.EdiTable.Const.Events.Nombre]` o con `table.addEventListener("nombre", fn)`.

| Evento | Nombre | Ocurre | Argumentos |
|---|---|---|---|
| `BeforeCellFocus` | `beforecellfocus` | Antes de que una celda tome el foco. | `sender`, `td`, `cancel` |
| `EnterCell` | `entercell` | Cuando una celda toma el foco. | `sender`, `td` |
| `LeaveCell` | `leavecell` | Cuando una celda pierde el foco. | `sender`, `td` |
| `StartEdition` | `startedition` | Al iniciar la edición de una celda. | `sender`, `coldef`, `td`, `text` |
| `ConfirmEdition` | `confirmedition` | Al confirmar con Enter o al salir de la celda editada. | `sender`, `coldef`, `td`, `text`, `cancel` |
| `CancelEdition` | `canceledition` | Al cancelar con Esc. | `sender`, `td` |
| `InputCreated` | `inputcreated` | Al crear el editor de la celda. | `sender`, `coldef`, `td`, `input`, `text` |
| `BeforeSetInput` | `beforesetinput` | Antes de cargar el contenido en el editor. | `sender`, `coldef`, `td`, `input`, `text` |
| `BeforeUpdateCell` | `beforeupdatecell` | Antes de escribir el nuevo contenido en la celda. | `sender`, `coldef`, `td`, `input`, `text`, `cancel` |
| `FieldUpdated` | `fieldupdated` | Después de actualizar un campo de `DataArray`. | `sender`, `row`, `field`, `value` |
| `RowAdded` | `rowadded` | Al crear una fila. | `sender`, `tr`, `rowIndex` |
| `BeforeRowDelete` | `beforerowdelete` | Antes de eliminar una fila. | `sender`, `rowIndex`, `cancel` |
| `RowDeleted` | `rowdeleted` | Después de eliminar una fila. | `sender`, `rowIndex` |
| `BeforeMoveRow` | `beforemoverow` | Antes de mover una fila. | `sender`, `source`, `target`, `child`, `cancel` |
| `RowMoved` | `rowmoved` | Después de mover una fila. | `sender`, `source`, `target`, `child` |
| `RowChanged` | `rowchanged` | Al seleccionar una fila distinta. | `sender`, `td`, `previous_index`, `index` |
| `LostFocus` | `lostfocus` | Cuando la tabla pierde el foco. | — |
| `IsDirtyChanged` | `isdirtychanged` | Con respaldo activo, cuando cambia `IsDirty`. | — |
| `OnSort` | `onsort` | Al ordenar una columna. | `coldef`, `th`, `caption` |
| `BadgeClick` *(nuevo)* | `badgeclick` | Al hacer clic en un badge o activarlo con el teclado. | `sender`, `irow`, `badge`, `row`, `td`, `event`, `cancel` |
| `ViewModeChanged` *(nuevo)* | `viewmodechanged` | Al cambiar entre tabla y tarjetas. No se dispara en la carga inicial; consulte `IsCardView`. | `sender`, `mode` (`"table"` \| `"cards"`) |

```js
table.addEventListener("badgeclick", (e) => {
    if (e.badge.id == "human") e.cancel = true; // detiene badge.onClick y BadgeOnClick
});

table.addEventListener("viewmodechanged", (e) => console.log(e.mode));
```

Con `native-events="true"`, `addEventListener` también acepta eventos nativos del DOM.

## Historial de cambios

### Mejoras visuales *(nuevo)*

- Vista de tarjetas configurable: modo, punto de corte por ventana o por contenedor, diseños `grid`, `stacked` y `slots`, selección y orden de columnas, etiquetas, espaciado y ocultamiento de campos vacíos.
- Badges por fila con ícono o texto, colores, tooltip, acción de clic, accesibilidad por teclado y columna `Badges`.
- Clases por fila y por celda, acento de color por fila y fechas relativas.
- Variables CSS y partes (`::part`) para personalizar desde fuera del componente.
- Nuevos atributos `template-encode` y `native-events`.

### Reparaciones

| Problema | Corrección |
|---|---|
| `UpdateRow()` dejaba vacías las columnas con `template`. | Ahora aplica la plantilla, igual que el pintado inicial y el ordenamiento. |
| `UpdateRow()` fallaba si `DataArray[fila]` no existía. | Usa un objeto vacío. |
| Con `html-encode="true"`, al seleccionar una celda con `template` y salir de ella, su HTML (por ejemplo, un enlace) se mostraba como texto. | El contenido de las columnas con plantilla ya no se escapa ni se decodifica; sus valores se protegen con `template-encode`. |
| Una columna sin atributo `type` provocaba un error en `checkAndSetRowCards()` y `_withFormat()`, que impedía configurar la vista de tarjetas. | Lectura segura del tipo. |
| La vista de tarjetas original mostraba las columnas ocultas con `hideColumn()`. | Corregido en la vista de tarjetas configurable; la original se mantiene sin cambios. |

### Compatibilidad verificada

Se compararon la versión anterior y la nueva con los ejemplos del repositorio (`EditTable-demo`, `_treeTest`, `index`, `index2`) y con una vista de conversaciones real, en 1280 px y 600 px. El HTML generado, el CSS, `DataArray` y las capturas de pantalla (píxel a píxel) resultaron idénticos. También coinciden la edición con teclado, el alta de filas, el ordenamiento y la vista de árbol.

### Limitaciones conocidas

- **Árbol y `UpdateRow`:** en modo árbol, `UpdateRow(i)` usa `DataArray[i]` aunque el orden visual no coincida.
- **Inicialización tardía:** el componente se inicializa solo en `DOMContentLoaded`.
- **Atributos en tiempo de ejecución:** los cambios de atributos después de inicializar no se detectan; use las propiedades y `RefreshView()`.
- **Errores al teclear muy rápido:** se observaron errores intermitentes en `StartEdit` cuando la entrada de teclado es muy rápida (por ejemplo, en pruebas automatizadas).
