<co-form> (Web Component)
<co-form> es el Web Component oficial. Mismo motor que el composable Vue, pero accesible desde cualquier framework o desde HTML vanilla.
Setup mínimo
Sección titulada «Setup mínimo»<!-- Tokens y Web Components Cobalt --><link rel="stylesheet" href="https://unpkg.com/@prolibu-suite/cobalt-tokens/build/css/tokens.css">
<script type="module"> import { defineCustomElements as defineCoreElements } from '@prolibu-suite/cobalt-core/loader'; import { defineCustomElements as defineFormElements } from '@prolibu-suite/cobalt-form/loader';
defineCoreElements(window); defineFormElements(window);</script>
<!-- Form en HTML --><co-form locale="es" layout="grid" schema='{ "email": { "type": "String", "required": true, "format": "email", "label": "Email" }, "password": { "type": "String", "required": true, "minLength": 8, "label": "Contraseña" } }'></co-form>Atributos
Sección titulada «Atributos»| Atributo | Tipo | Default | Descripción |
|---|---|---|---|
schema | JSON string | — | Schema del form. Requerido. |
initial-values | JSON string | — | Valores iniciales (mergeados con defaults del schema) |
locale | 'es' | 'en' | 'pt-BR' | 'en' | Idioma de mensajes de error |
layout | 'grid' | 'stack' | 'grid' | Layout de campos |
exclude-fields | string CSV | — | Lista separada por comas de campos a ocultar |
disabled | boolean | false | Deshabilita todos los campos |
read-only | boolean | false | Modo lectura |
Properties (solo vía JS)
Sección titulada «Properties (solo vía JS)»| Property | Tipo | Descripción |
|---|---|---|
schemaInstance | FormSchemaLike | Schema pre-construido (alternativa a schema) |
refResolver | RefResolver | Función para resolver opciones de campos ref |
const form = document.querySelector('co-form');
form.refResolver = async ({ model, query, page, pageSize = 20, ids }) => { const params = new URLSearchParams({ q: query, page, pageSize }); if (ids) params.set('ids', ids.join(','));
const res = await fetch(`/api/${model.toLowerCase()}?${params}`); const { data, total } = await res.json();
return { options: data.map((item) => ({ value: item._id, label: item.name, })), total, hasMore: page * pageSize < total, };};Métodos
Sección titulada «Métodos»Todos los métodos son async:
const form = document.querySelector('co-form');
await form.setValue('email', 'foo@bar.com');const email = await form.getValue('email');await form.touch('password');Eventos
Sección titulada «Eventos»form.addEventListener('coSubmit', (e) => { console.log('values:', e.detail.values);});
form.addEventListener('coChange', (e) => { console.log(`${e.detail.name} →`, e.detail.value);});
form.addEventListener('coValidate', (e) => { console.log('isValid:', e.detail.isValid); console.log('errors:', e.detail.errors);});| Evento | detail |
|---|---|
coSubmit | { values: Record<string, any> } |
coChange | { name: string; value: any } |
coValidate | { isValid: boolean; errors: Record<string, string[]> } |
| Slot | Contenido |
|---|---|
header | Aparece arriba de los campos |
footer | Aparece abajo (por defecto: botón submit) |
field:<name> | Override de un campo específico |
<co-form schema="..." locale="es"> <h2 slot="header">Nuevo usuario</h2>
<div slot="footer"> <co-button label="Cancelar" variant="ghost"></co-button> <co-button label="Crear" variant="primary"></co-button> </div></co-form>Ejemplo completo (vanilla HTML)
Sección titulada «Ejemplo completo (vanilla HTML)»<!DOCTYPE html><html lang="es"><head> <meta charset="UTF-8"> <link rel="stylesheet" href="https://unpkg.com/@prolibu-suite/cobalt-tokens/build/css/tokens.css"></head><body> <co-form id="signup" locale="es" layout="grid"></co-form>
<script type="module"> import { defineCustomElements as defineCoreElements } from '@prolibu-suite/cobalt-core/loader'; import { defineCustomElements as defineFormElements } from '@prolibu-suite/cobalt-form/loader';
defineCoreElements(window); defineFormElements(window);
const form = document.getElementById('signup');
form.schemaInstance = { firstName: { type: 'String', required: true, label: 'Nombre' }, email: { type: 'String', required: true, format: 'email', label: 'Email' }, password: { type: 'String', required: true, minLength: 8, label: 'Contraseña' }, newsletter: { type: 'Boolean', default: true, label: 'Newsletter' }, };
form.addEventListener('coSubmit', async (e) => { const { values } = e.detail; const res = await fetch('/api/signup', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(values), }); if (res.ok) alert('Cuenta creada!'); }); </script></body></html>Integración con React
Sección titulada «Integración con React»import { useEffect, useRef } from 'react';
export function CoFormReact({ schema, initialValues, locale = 'es', onSubmit }) { const ref = useRef(null);
useEffect(() => { const el = ref.current; el.schemaInstance = schema; if (initialValues) el.initialValues = JSON.stringify(initialValues);
const handler = (e) => onSubmit?.(e.detail.values); el.addEventListener('coSubmit', handler); return () => el.removeEventListener('coSubmit', handler); }, [schema, initialValues, onSubmit]);
return <co-form ref={ref} locale={locale} layout="grid" />;}Y en TypeScript necesitas declarar el tag:
declare module 'react' { namespace JSX { interface IntrinsicElements { 'co-form': React.DetailedHTMLProps< React.HTMLAttributes<HTMLElement> & { schema?: string; locale?: string; layout?: string; }, HTMLElement >; } }}Diferencias con el composable Vue
Sección titulada «Diferencias con el composable Vue»| Aspecto | useForm + CoFormRenderer | <co-form> |
|---|---|---|
| Bundle size adicional | + form-vue (~5KB) | + cobalt-form (~8KB) |
| Reactivity nativa | Sí (refs Vue) | Vía eventos DOM |
| Slots / overrides | Sí, field:<name> | Sí, field:<name> |
| State accesible | Refs computados | form.getValue() (async) |
| SSR | Limitado | No (custom elements son cliente-only) |
| Mejor para… | Apps Vue | HTML, otros frameworks, CDN |