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; } // ======================