Campos custom
This content is not available in your language yet.
Cobalt cubre los casos comunes (text, number, select, boolean, date, ref, textarea). Para casos específicos del dominio — firma digital, color picker, file uploader, JSON editor, mapa — necesitas un campo custom.
Hay dos formas de hacerlo, según el caso.
Opción 1: uiCom + componente custom (Vue)
Sección titulada «Opción 1: uiCom + componente custom (Vue)»Declara el componente en el schema con uiCom:
const schema = { signature: { type: 'String', uiCom: 'signature', // string libre que identifica tu componente label: 'Firma', required: true, }, accentColor: { type: 'String', uiCom: 'color-picker', default: '#02a270', label: 'Color de marca', },};Crea el componente custom:
<script setup>import { ref } from 'vue';
const props = defineProps({ field: Object, // FieldDescriptor value: String, errors: Array, // string[] disabled: Boolean,});const emit = defineEmits(['update:value', 'blur']);
const canvas = ref(null);const isEmpty = ref(true);
const clear = () => { canvas.value.clear(); emit('update:value', ''); isEmpty.value = true;};
const save = () => { const dataUrl = canvas.value.toDataURL(); emit('update:value', dataUrl); isEmpty.value = false;};</script>
<template> <div class="signature-field"> <label>{{ field.label }}</label> <SignaturePad ref="canvas" @end="save" /> <co-button label="Limpiar" variant="ghost" size="s" @click="clear" /> <p v-if="errors.length" class="error">{{ errors[0] }}</p> </div></template>Y pásalo a CoFormRenderer vía fieldComponents:
<script setup>import SignatureField from './SignatureField.vue';import ColorPickerField from './ColorPickerField.vue';
const customFields = { 'signature': SignatureField, 'color-picker': ColorPickerField,};</script>
<template> <CoFormRenderer :form="form" :field-components="customFields" /></template>CoFormRenderer ahora usa tu componente cada vez que encuentra un campo con uiCom: 'signature'.
Opción 2: Slot field:<name> (caso único)
Sección titulada «Opción 2: Slot field:<name> (caso único)»Si solo necesitas customizar UN campo en UN form (no reutilizable), usa el slot directamente:
<CoFormRenderer :form="form"> <template #field:signature="{ value, setValue, errors }"> <SignaturePad :value="value" :error="errors[0]" @change="setValue" /> </template></CoFormRenderer>No necesitas uiCom en el schema porque el slot machea por nombre del campo, no por kind.
Cuál usar
Sección titulada «Cuál usar»| Caso | Opción recomendada |
|---|---|
| Componente reutilizable en muchos forms | uiCom + fieldComponents |
| Override puntual de UN campo en UN form | Slot field:<name> |
| Mismo tipo de campo en muchos schemas | uiCom + fieldComponents |
| Diferentes overrides en cada form (ej. password con eye toggle solo aquí) | Slot |
Props y eventos que recibe un campo custom
Sección titulada «Props y eventos que recibe un campo custom»Un componente registrado vía fieldComponents recibe el valor como prop y lo actualiza emitiendo update:value (no hay un prop setValue):
// Propsinterface FieldRendererProps { field: FieldDescriptor; // metadata del campo desde el schema value: any; // valor actual errors: string[]; // errores del campo (vacío mientras no esté touched) disabled: boolean; // disabled global o del campo locale?: string; // locale actual (BCP-47), para campos locale-aware}
// Eventos// emit('update:value', v) ← el setter// emit('blur') ← marca el campo como touchedFieldDescriptor te da acceso a TODOS los atributos del schema:
interface FieldDescriptor { name: string; kind: FieldKind; label: string; placeholder: string; required: boolean; disabled: boolean; hidden: boolean; fullWidth: boolean; helperText: string; originalAttrs: Record<string, any>; // ← TU `uiCom`, `description`, etc. ajvProperty: Record<string, any>;}Si pusiste uiCom: 'signature' en el schema, lo encuentras en field.originalAttrs.uiCom.
Ejemplo: file uploader
Sección titulada «Ejemplo: file uploader»<script setup>import { ref } from 'vue';
const props = defineProps({ field: Object, value: Array, // array de URLs subidas errors: Array, // string[] disabled: Boolean,});const emit = defineEmits(['update:value', 'blur']);
const uploading = ref(false);
const onChange = async (e) => { const files = Array.from(e.target.files); uploading.value = true;
const uploaded = await Promise.all( files.map(async (file) => { const formData = new FormData(); formData.append('file', file); const res = await fetch('/api/upload', { method: 'POST', body: formData }); const { url } = await res.json(); return url; }) );
emit('update:value', [...(props.value || []), ...uploaded]); uploading.value = false;};
const removeAt = (idx) => { emit('update:value', props.value.filter((_, i) => i !== idx));};</script>
<template> <div class="file-field"> <label>{{ field.label }}</label> <input type="file" multiple @change="onChange" :disabled="uploading" /> <ul> <li v-for="(url, i) in value" :key="url"> {{ url }} <button @click="removeAt(i)">x</button> </li> </ul> <p v-if="uploading">Subiendo…</p> <p v-if="errors.length" class="error">{{ errors[0] }}</p> </div></template>Schema:
{ attachments: { type: 'String', // o 'Mixed', solo importa que AJV lo valide uiCom: 'file-upload', multiple: true, label: 'Adjuntos', },}Y en el form:
<CoFormRenderer :form="form" :field-components="{ 'file-upload': FileUploadField }"/>Custom kinds (override del FieldKind built-in)
Sección titulada «Custom kinds (override del FieldKind built-in)»También puedes reemplazar los kinds estándar pasando la misma key como en FieldKind:
const customFields = { 'text': MyFancyTextField, // reemplaza TODOS los text fields 'date': DatePickerWithPresets,};Esto es útil si quieres un look-and-feel propio diferente al de Cobalt en TODA tu app.
Custom fields en Web Component
Sección titulada «Custom fields en Web Component»Para <co-form> (Web Component), los custom fields se pasan vía slots field:<name>:
<co-form schema='...'> <div slot="field:signature"> <signature-pad id="sig"></signature-pad> </div></co-form>
<script> const form = document.querySelector('co-form'); const pad = document.getElementById('sig');
pad.addEventListener('change', async (e) => { await form.setValue('signature', e.detail.dataUrl); });</script>Es más manual que la versión Vue: tú escuchas el evento del componente custom y propagas el valor al form via setValue().