Referencia
Exports por subpath, diagnósticos, atajos de teclado, preferencias de vista guardadas, imágenes optimizadas y el resto de opciones.
Diagnósticos
listkit emite diagnósticos con código para que una config rota aparezca en
desarrollo y no en producción. Los LK1xxx lanzan en dev (no-op en prod); los
LK2xxx avisan una vez; los LK3xxx se muestran en la UI.
| Código | Severidad | Significado |
|---|---|---|
| LK1001 | error | Key de campo duplicada en el universo exportable. |
| LK1002 | error | Export solicitado sin configuración de export. |
| LK1003 | error | La petición traía una key fuera del whitelist (se descarta). |
| LK1004 | error | Sort sobre un path que resuelve a array — precomputa un campo plano. |
| LK2001 | warn | Un campo produjo un valor no primitivo; la celda queda vacía. |
| LK2002 | warn | Celda sobre el límite de 32,767 caracteres de Excel; truncada. |
| LK2003 | warn | data: URI descartada de una celda (URLs planas sí pasan). |
| LK2004 | warn | Petición GET demasiado grande — cambia el resolver a POST. |
| LK3001 | info | maxRows truncó el export — se muestra en el diálogo ("N de M filas"). |
Estado vacío
"Sin resultados" significa cosas distintas: una lista a la que nadie ha escrito quiere una invitación, una filtrada quiere una pista para aflojar los filtros. Compónlo con empty, sin reemplazar el layout:
import { PackageOpen } from 'lucide-react'
defineListConfig<Product>({
empty: {
title: 'Aún no hay productos',
message: 'Agrega tu primer producto para verlo aquí.',
icon: <PackageOpen size={40} />,
action: <button onClick={openCreate}>Nuevo producto</button>,
},
})| Campo | Efecto |
|---|---|
title | Encabezado. Por defecto usa la etiqueta empty activa. |
message | Línea de apoyo bajo el título. |
icon | Glifo propio. null quita el bloque del ícono — más denso para una lista embebida. |
action | Un botón o enlace, para que la pantalla vacía tenga un siguiente paso. |
emptyMessage es el atajo de una línea y empty lo sobreescribe; renderEmpty reemplaza el bloque completo y es el último recurso — usa empty primero para que el espaciado y el tema sigan siendo consistentes con el resto de la lista.
Atajos de teclado
Activos por defecto. Cada atajo se enlaza por capacidad, no por estado: una lista con filtros siempre responde +, haya o no un filtro aplicado en este momento — así las teclas nunca se mueven bajo tus manos.
| Teclas | Acción |
|---|---|
⌘ K / Ctrl K | Enfocar la búsqueda |
+ | Abrir el panel de filtros, en su buscador |
- | Quitar el último filtro aplicado |
Shift + C | Limpiar todos los filtros |
Shift + V | Alternar tabla / tarjetas |
Shift + D | Alternar filas compactas / amplias |
Shift + E | Abrir la exportación configurable |
Shift + R | Refrescar la lista |
Shift + A | Seleccionar la página actual |
Esc | Limpiar la selección |
← / → | Página anterior / siguiente |
Shift + ← / Shift + → | Primera / última página |
? | Mostrar esta lista, en un overlay |
? abre el overlay de ayuda, que lista solo los atajos que esa lista realmente enlaza — lee el mismo registro que los handlers, así que no puede anunciar una tecla muerta. La pista de ? también vive en el menú de opciones, para quien no la descubra tecleando.
Los atajos nunca disparan mientras escribes en un input, textarea o contenteditable, así que - dentro de la búsqueda sigue siendo un guion. Cada uno se enlaza solo si la lista tiene la función: sin config de export, no hay Shift + E, y el overlay no lo lista.
Preferencias de vista guardadas
Las preferencias que describen cómo trabaja un usuario con una lista persisten por id de lista, para que la lista abra como la dejó. Lo que se guarda:
| Preferencia | Se define desde |
|---|---|
| Orden de columnas | Gestor de columnas, arrastre |
| Columnas ocultas | Gestor de columnas |
| Ancho de columnas | Redimensionar el encabezado |
| Densidad | Menú de opciones |
| Vista (tabla/tarjetas) | Toggle de vista |
| Filas por página | Selector del footer |
| Barra de filtros rápidos | Menú de opciones |
Un parámetro de URL siempre le gana al valor guardado, así que un enlace compartido muestra la vista de quien lo mandó, no la de quien lo recibe.
La vista es la única preferencia que el dispositivo puede sobreescribir: una pantalla angosta abre en tarjetas sin importar lo guardado — una tabla no cabe — y un cambio hecho ahí no se guarda, así que revisar las columnas desde el teléfono nunca cambia cómo abre la lista en escritorio.
El almacenamiento es localStorage por defecto y es reemplazable — respáldalo con tu tabla de configuración de usuario para llevar las preferencias entre dispositivos:
import type { ColumnStorage } from 'listkit'
const dbColumnStorage: ColumnStorage = {
get: key => cache.get(key) ?? null,
set: (key, prefs) => {
cache.set(key, prefs)
void api.saveColumnPrefs(key, prefs)
},
}
<ListView config={config} adapter={adapter} columnStorage={dbColumnStorage} />get/set son síncronos para que la tabla pinte las columnas correctas en el primer frame. Para respaldarlo con un store asíncrono, hidrata un caché por adelantado (desde un valor renderizado en el servidor o un fetch único), haz que get lea ese caché y deja que set dispare la escritura en segundo plano — como arriba.
Imágenes optimizadas (ListImage)
Para tablas/tarjetas densas llenas de miniaturas, <ListImage> reserva su caja (sin layout shift), hace lazy-load y decodificación asíncrona, muestra un placeholder shimmer y cae en un fallback ante errores:
import { ListImage } from 'listkit'
{ key: 'photo', header: '', exportable: false,
render: p => <ListImage src={p.photo} alt={p.name} width={40} height={40} /> }En Next.js, inyecta el componente optimizado — React puro cae a <img>:
import Image from 'next/image'
;<ListImage as={Image} src={src} alt={alt} width={48} height={48} />La compresión del lado del cliente pertenece al momento de subida (reducir antes de almacenar), no al renderizado — descargar una imagen completa solo para recomprimirla en JS hace el render más lento, no más rápido. Lazy-loading + optimización del framework es lo que acelera las listas con muchas imágenes.
Orden inicial (defaultSort)
defineListConfig<Order>({
id: 'orders',
defaultSort: { field: 'placedAt', dir: 'desc' },
table: { columns: [{ key: 'placedAt', header: 'Fecha', sortable: true }] },
})La lista abre ordenada, el encabezado muestra la flecha y desde ahí el usuario cicla el orden. Un sort ya presente en la URL gana, y limpiar el orden no se vuelve a aplicar hasta la siguiente carga. buildListQuery también lo aplica en servidor, así el seed de SSR coincide con la primera query del cliente.
Subpath Exports
| Ruta de importación | Contenido |
|---|---|
listkit | ListView, defineListConfig, ListKitProvider, ListSkeleton, invalidateListCache, adapters, hooks, primitives, types |
listkit/next | useNextRouterAdapter, NextListView |
listkit/react-router | useReactRouterAdapter |
listkit/adapters | memoryAdapter, fetchAdapter, serverActionAdapter, createDexieAdapter |
listkit/server | buildListQuery, loadInitialList, defineListConfig — seguro para RSC (sin React/DOM) |
listkit/query | parseListkitQuery, parseSelectionDescriptor, filtersById, getString/getBoolean/getStringArray/getDateRange/getNumberRange/getText, paginate — parsear un request a ListQuery y leer sus filtros |
listkit/sql | executeSqlList, buildSqlFilter, buildSearch, buildOrderBy, sqlPaginate, textCondition, sqlFieldMapFromFilters — fragmentos Postgres + ejecutor (inyección de pool, sin driver) |
listkit/mongo | buildMongoQuery, buildMongoFilter, buildMongoSort, mongoPaginate, combineFilters, escapeRegex, mongoFieldMapFromFilters, filterConfigToMongoFieldMaps — objetos de query de MongoDB (sin driver) |
listkit/mongoose | executePaginatedListkitQuery, executeAggregateListkitQuery, castFilterToSchema, resolveSelectionFilter — corre la query de página en Mongoose (peer dep mongoose opcional, type-only) |
listkit/react-query | useReactQueryListData, invalidateList, listQueryKey — respalda listas con TanStack Query |
listkit/tailwind.css | Registro de fuente Tailwind v4 |
Licencia
MIT © Ricardo Tapia