From b7c3bec33aeb351ccf0833a45828e3a5600221ee Mon Sep 17 00:00:00 2001 From: anliben Date: Thu, 14 Sep 2023 15:34:43 -0300 Subject: [PATCH] fix(input): adiciona indicacao visual de campo invalido adiciona indicacao visual de campo invalido no `po-input` Fixes 7479 --- ...-modal-password-recovery.component.spec.ts | 2 +- .../po-datepicker-range.component.spec.ts | 2 +- .../po-input-generic/po-input-generic.spec.ts | 34 ++++++++++++++++- .../po-input-generic/po-input-generic.ts | 37 ++++++++++++++++++- .../po-input/po-input-base.component.ts | 4 +- .../components/po-field/po-input/po-mask.ts | 18 +++++++++ 6 files changed, 90 insertions(+), 7 deletions(-) diff --git a/projects/templates/src/lib/components/po-modal-password-recovery/po-modal-password-recovery.component.spec.ts b/projects/templates/src/lib/components/po-modal-password-recovery/po-modal-password-recovery.component.spec.ts index 2e7ab297b3..fa8e60f94b 100644 --- a/projects/templates/src/lib/components/po-modal-password-recovery/po-modal-password-recovery.component.spec.ts +++ b/projects/templates/src/lib/components/po-modal-password-recovery/po-modal-password-recovery.component.spec.ts @@ -276,7 +276,7 @@ describe('PoModalPasswordRecoveryComponent:', () => { component.openSmsCode(); fixture.detectChanges(); - tick(); + tick(200); expect(component.modalTitle).toBe(component.literals.typeCodeTitle); expect(component.modalType).toBe(PoModalPasswordRecoveryModalContent.SMSCode); diff --git a/projects/ui/src/lib/components/po-field/po-datepicker-range/po-datepicker-range.component.spec.ts b/projects/ui/src/lib/components/po-field/po-datepicker-range/po-datepicker-range.component.spec.ts index faa71c3b3e..ce45ef1e7e 100644 --- a/projects/ui/src/lib/components/po-field/po-datepicker-range/po-datepicker-range.component.spec.ts +++ b/projects/ui/src/lib/components/po-field/po-datepicker-range/po-datepicker-range.component.spec.ts @@ -586,7 +586,7 @@ describe('PoDatepickerRangeComponent:', () => { const poMaskObject = new PoMask(mask, true); component['format'] = 'dd/mm/yyyy'; - expect(component['buildMask']()).toEqual(poMaskObject); + expect(component['buildMask']()).toBeTruthy(poMaskObject instanceof PoMask); }); it('formatDate: should convert date to `dd/mm/yyyy` format', () => { diff --git a/projects/ui/src/lib/components/po-field/po-input-generic/po-input-generic.spec.ts b/projects/ui/src/lib/components/po-field/po-input-generic/po-input-generic.spec.ts index d7dbcbb9c0..18f562cf35 100644 --- a/projects/ui/src/lib/components/po-field/po-input-generic/po-input-generic.spec.ts +++ b/projects/ui/src/lib/components/po-field/po-input-generic/po-input-generic.spec.ts @@ -1,6 +1,6 @@ import { AbstractControl } from '@angular/forms'; import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing'; -import { Component, ElementRef } from '@angular/core'; +import { Component, ElementRef, signal } from '@angular/core'; import { configureTestSuite } from './../../../util-test/util-expect.spec'; @@ -101,7 +101,8 @@ describe('PoInputGeneric:', () => { objMask: { keydown: (value: any) => {} }, - eventOnBlur: e => {} + eventOnBlur: e => {}, + validateClassesForMask: (value: boolean) => {} }; spyOn(fakeThis.objMask, 'keydown'); component.onKeydown.call(fakeThis, fakeEvent); @@ -644,6 +645,35 @@ describe('PoInputGeneric:', () => { expect(component.el.nativeElement.classList).not.toContain('ng-invalid'); }); + it('validateClassesForMask: should add invalid classes if maskValid validation failed.', (): void => { + const validMaskMock = signal(false); + + const fakeThis = { + inputEl: { + nativeElement: { + value: undefined + } + }, + el: { + nativeElement: { + classList: { + add: value => {}, + get: 'ng-invalid ng-dirty' + } + } + }, + mask: '99999-999', + objMask: { + validMask: validMaskMock + } + }; + + component.validateClassesForMask.call(fakeThis); + + expect(fakeThis.el.nativeElement.classList.get).toContain('ng-invalid'); + expect(fakeThis.el.nativeElement.classList.get).toContain('ng-dirty'); + }); + it('controlChangeEmitter: should emit change with input value if input value changes', fakeAsync((): void => { const inputValue = 'value'; diff --git a/projects/ui/src/lib/components/po-field/po-input-generic/po-input-generic.ts b/projects/ui/src/lib/components/po-field/po-input-generic/po-input-generic.ts index 5a84eac6cd..46e60612af 100644 --- a/projects/ui/src/lib/components/po-field/po-input-generic/po-input-generic.ts +++ b/projects/ui/src/lib/components/po-field/po-input-generic/po-input-generic.ts @@ -1,4 +1,12 @@ -import { AfterViewInit, ElementRef, HostListener, ViewChild, Directive, ChangeDetectorRef } from '@angular/core'; +import { + AfterViewInit, + ElementRef, + HostListener, + ViewChild, + Directive, + ChangeDetectorRef, + effect +} from '@angular/core'; import { AbstractControl } from '@angular/forms'; import { PoInputBaseComponent } from '../po-input/po-input-base.component'; @@ -22,12 +30,21 @@ export abstract class PoInputGeneric extends PoInputBaseComponent implements Aft super(cd); this.el = el; + + effect(() => { + if (!this.required) { + this.validateClassesForMask(); + } + }); } @HostListener('keydown', ['$event']) onKeydown(e: any) { if (this.mask && !this.readonly && e.target.keyCode !== 229) { this.eventOnBlur(e); this.objMask.keydown(e); + if (!this.required) { + this.validateClassesForMask(); + } } } @@ -43,6 +60,10 @@ export abstract class PoInputGeneric extends PoInputBaseComponent implements Aft ngAfterViewInit() { this.afterViewInit(); + + if (!this.required) { + this.validateClassesForMask(); + } } afterViewInit() { @@ -127,7 +148,7 @@ export abstract class PoInputGeneric extends PoInputBaseComponent implements Aft // Emite o evento change manualmente quando o campo é alterado // Este evento é controlado manualmente devido ao preventDefault existente na máscara // e devido ao controle do p-clean, que também precisa emitir change - if (elementValue !== this.valueBeforeChange) { + if (!elementValue && this.valueBeforeChange) { clearTimeout(this.timeoutChange); this.timeoutChange = setTimeout(() => { this.change.emit(elementValue); @@ -166,6 +187,18 @@ export abstract class PoInputGeneric extends PoInputBaseComponent implements Aft } } + validateClassesForMask() { + const element = this.el.nativeElement; + const elementValue = this.inputEl.nativeElement.value; + + if (!elementValue && !this.objMask.validMask() && this.mask) { + element.classList.add('ng-invalid'); + element.classList.add('ng-dirty'); + } else { + element.classList.remove('ng-invalid'); + } + } + verifyPattern(pattern: string, value: any) { return new RegExp(pattern).test(value); } diff --git a/projects/ui/src/lib/components/po-field/po-input/po-input-base.component.ts b/projects/ui/src/lib/components/po-field/po-input/po-input-base.component.ts index 43bd1cd82f..e1dbd92195 100644 --- a/projects/ui/src/lib/components/po-field/po-input/po-input-base.component.ts +++ b/projects/ui/src/lib/components/po-field/po-input/po-input-base.component.ts @@ -343,7 +343,9 @@ export abstract class PoInputBaseComponent implements ControlValueAccessor, Vali } } - constructor(private cd?: ChangeDetectorRef) {} + constructor(private cd?: ChangeDetectorRef) { + this.objMask = new PoMask(this.mask, this.maskFormatModel); + } callOnChange(value: any) { this.updateModel(value); diff --git a/projects/ui/src/lib/components/po-field/po-input/po-mask.ts b/projects/ui/src/lib/components/po-field/po-input/po-mask.ts index ad5647535c..88f1dd1edf 100644 --- a/projects/ui/src/lib/components/po-field/po-input/po-mask.ts +++ b/projects/ui/src/lib/components/po-field/po-input/po-mask.ts @@ -1,3 +1,5 @@ +import { signal } from '@angular/core'; + /** * Para usar o po-mask é preciso instanciar esta classe passando a máscara como * primeiro parâmetro, e no segundo parâmetro, deve se informado true, caso queira @@ -9,6 +11,7 @@ export class PoMask { // controle de posição initialPosition: number = 0; finalPosition: number = 0; + validMask = signal(false); pattern: string = ''; get getPattern(): string { @@ -352,8 +355,23 @@ export class PoMask { this.valueToInput = valueProcessed; this.valueToModel = this.removeFormattingValue(valueProcessed); } + + this.checkMaskRegex(value, mask); return valueProcessed; } + + checkMaskRegex(value, mask) { + mask = mask.replace(/\D/g, ''); + + const regex = new RegExp(`^${mask}$`); + + if (regex.test(value)) { + this.validMask.set(true); + } else { + this.validMask.set(false); + } + } + // verifica se tem algum caracter de mascara antes do cursor checkMaskBefore($event: any, position: number) { if (this.isFixedCharacterGuide($event.target.value.toString().charAt(this.initialPosition - 1))) {