From 43d5dcf1d33db4ecaa6ab74f23b3574508ea4df1 Mon Sep 17 00:00:00 2001 From: Josep Piguillem Date: Thu, 15 Oct 2026 20:25:39 +0200 Subject: [PATCH] Release @pi/ui 0.0.9: a field shows its errors once touched MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit An untouched field with errors was painted red from the start, so a form with required fields opened full of «Campo obligatorio». Text fields, selects, checkboxes, radio groups and file uploads now wait until the field is touched (blur, or the form's submit()). Co-Authored-By: Claude Opus 5.5 --- libs/ui/README.md | 6 ++++++ libs/ui/package.json | 2 +- .../lib/ui/file-upload/file-upload/file-upload.component.ts | 6 +++++- libs/ui/src/lib/ui/form-field/base-form-field.component.ts | 5 ++++- .../ui/src/lib/ui/form-field/checkbox/checkbox.component.ts | 6 +++++- .../lib/ui/form-field/radio-group/radio-group.component.ts | 6 +++++- libs/ui/src/lib/ui/select/base-select.component.ts | 3 ++- 7 files changed, 28 insertions(+), 6 deletions(-) diff --git a/libs/ui/README.md b/libs/ui/README.md index 55acb7d..1c1dd8c 100644 --- a/libs/ui/README.md +++ b/libs/ui/README.md @@ -70,6 +70,12 @@ hecho **no se pueden enlazar a mano** esas entradas en un elemento que ya use `[formField]`: Angular lo rechaza con `NG8022`. Los validadores van en el `form()`, no en la plantilla. +Los errores se ven cuando el campo está **tocado**: al salir de él, o al +enviar con `submit()` de Signal Forms, que marca todo el formulario. Un campo +obligatorio vacío no sale en rojo nada más abrir el formulario. Para volver a +abrir un formulario como nuevo, `f().reset(valor)` pone los valores y quita el +estado de tocado. + Se puede ver funcionando en la app `playground` (`nx serve playground`). ## Theming: tokens CSS diff --git a/libs/ui/package.json b/libs/ui/package.json index dcfbeb0..ad7f5f3 100644 --- a/libs/ui/package.json +++ b/libs/ui/package.json @@ -1,6 +1,6 @@ { "name": "@pi/ui", - "version": "0.0.8", + "version": "0.0.9", "description": "Librería de componentes Angular standalone (botones, campos con Signal Forms, cards, charts, drawer/hub/modal, theming vía tokens CSS...).", "license": "MIT", "generators": "./generators.json", diff --git a/libs/ui/src/lib/ui/file-upload/file-upload/file-upload.component.ts b/libs/ui/src/lib/ui/file-upload/file-upload/file-upload.component.ts index 74abe6d..2f7bd5e 100644 --- a/libs/ui/src/lib/ui/file-upload/file-upload/file-upload.component.ts +++ b/libs/ui/src/lib/ui/file-upload/file-upload/file-upload.component.ts @@ -59,6 +59,7 @@ export class PiFileUploadComponent implements FormValueControl { readonly errors = input([]); readonly disabled = input(false); + readonly touched = input(false); readonly required = input(false); readonly name = input(''); @@ -94,7 +95,10 @@ export class PiFileUploadComponent implements FormValueControl { this.errors().map((error) => error.message ?? error.kind), ); - protected readonly hasError = computed(() => this.errors().length > 0); + /** El error se ve cuando el campo está tocado (al salir de él o al enviar el formulario), no antes. */ + protected readonly hasError = computed( + () => this.touched() && this.errors().length > 0, + ); /* --- Arrastrar y soltar ----------------------------------------------- */ diff --git a/libs/ui/src/lib/ui/form-field/base-form-field.component.ts b/libs/ui/src/lib/ui/form-field/base-form-field.component.ts index d964cd9..11ca419 100644 --- a/libs/ui/src/lib/ui/form-field/base-form-field.component.ts +++ b/libs/ui/src/lib/ui/form-field/base-form-field.component.ts @@ -76,7 +76,10 @@ export abstract class PiBaseFormFieldComponent< this.errors().map((error) => error.message ?? error.kind), ); - protected readonly hasError = computed(() => this.errors().length > 0); + /** El error se ve cuando el campo está tocado (al salir de él o al enviar el formulario), no antes. */ + protected readonly hasError = computed( + () => this.touched() && this.errors().length > 0, + ); /** * `MatInput` normalmente deriva `errorState` de un `NgControl` (`ngModel`, diff --git a/libs/ui/src/lib/ui/form-field/checkbox/checkbox.component.ts b/libs/ui/src/lib/ui/form-field/checkbox/checkbox.component.ts index 831a2c4..55c690a 100644 --- a/libs/ui/src/lib/ui/form-field/checkbox/checkbox.component.ts +++ b/libs/ui/src/lib/ui/form-field/checkbox/checkbox.component.ts @@ -49,6 +49,7 @@ export class PiCheckboxComponent implements FormCheckboxControl { readonly checked = model(false); readonly disabled = input(false); + readonly touched = input(false); readonly required = input(false); readonly name = input(''); readonly errors = input([]); @@ -70,7 +71,10 @@ export class PiCheckboxComponent implements FormCheckboxControl { this.errors().map((error) => error.message ?? error.kind), ); - protected readonly hasError = computed(() => this.errors().length > 0); + /** El error se ve cuando el campo está tocado (al salir de él o al enviar el formulario), no antes. */ + protected readonly hasError = computed( + () => this.touched() && this.errors().length > 0, + ); /** * `name` solo lo rellena la directiva `FormField`; suelto llega vacío. Sin diff --git a/libs/ui/src/lib/ui/form-field/radio-group/radio-group.component.ts b/libs/ui/src/lib/ui/form-field/radio-group/radio-group.component.ts index 22d2a27..001810d 100644 --- a/libs/ui/src/lib/ui/form-field/radio-group/radio-group.component.ts +++ b/libs/ui/src/lib/ui/form-field/radio-group/radio-group.component.ts @@ -57,6 +57,7 @@ export class PiRadioGroupComponent< readonly errors = input([]); readonly disabled = input(false); + readonly touched = input(false); readonly required = input(false); readonly name = input(''); @@ -77,7 +78,10 @@ export class PiRadioGroupComponent< this.errors().map((error) => error.message ?? error.kind), ); - protected readonly hasError = computed(() => this.errors().length > 0); + /** El error se ve cuando el campo está tocado (al salir de él o al enviar el formulario), no antes. */ + protected readonly hasError = computed( + () => this.touched() && this.errors().length > 0, + ); /** Agrupa los ``; debe ser único por grupo. */ private readonly uniqueName = `pi-radio-group-${nextUniqueId++}`; diff --git a/libs/ui/src/lib/ui/select/base-select.component.ts b/libs/ui/src/lib/ui/select/base-select.component.ts index b064dc6..2813e02 100644 --- a/libs/ui/src/lib/ui/select/base-select.component.ts +++ b/libs/ui/src/lib/ui/select/base-select.component.ts @@ -51,8 +51,9 @@ export abstract class PiBaseSelectComponent< /** Se emite cuando el usuario interactúa; `FormField` lo usa para marcar `touched`. */ readonly touch = output(); + /** El error se ve cuando el campo está tocado, no antes. */ protected get hasError(): boolean { - return this.errors().length > 0; + return this.touched() && this.errors().length > 0; } // ======================