From 5e2debb589db2a044fd6de4e4570e03077b4edbf Mon Sep 17 00:00:00 2001 From: jcorrea97 Date: Tue, 31 Oct 2023 10:20:58 -0300 Subject: [PATCH] =?UTF-8?q?feat(combo):=20implementa=20defini=C3=A7=C3=B5e?= =?UTF-8?q?s=20do=20AnimaliaDS?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implementa definições do AnimaliaDS no Breadcrumb fixes DTHFUI-7500 --- .../po-combo-literals-default.interface.ts | 12 +- .../interfaces/po-combo-literals.interface.ts | 3 + .../po-field/po-combo/po-combo.component.html | 33 +++-- .../po-combo/po-combo.component.spec.ts | 126 ++++++++++++++---- .../po-field/po-combo/po-combo.component.ts | 84 +++++++++--- .../po-listbox/po-listbox.component.html | 2 +- .../po-listbox/po-listbox.component.spec.ts | 2 +- 7 files changed, 206 insertions(+), 56 deletions(-) diff --git a/projects/ui/src/lib/components/po-field/po-combo/interfaces/po-combo-literals-default.interface.ts b/projects/ui/src/lib/components/po-field/po-combo/interfaces/po-combo-literals-default.interface.ts index e2136b4f7..fa2c5847f 100644 --- a/projects/ui/src/lib/components/po-field/po-combo/interfaces/po-combo-literals-default.interface.ts +++ b/projects/ui/src/lib/components/po-field/po-combo/interfaces/po-combo-literals-default.interface.ts @@ -3,18 +3,22 @@ import { PoComboLiterals } from './po-combo-literals.interface'; export const poComboLiteralsDefault = { en: { noData: 'No data found', - chooseOption: 'Choose an option' + chooseOption: 'Choose an option', + clear: 'Clear' }, es: { noData: 'Datos no encontrados', - chooseOption: 'Elija una opción' + chooseOption: 'Elija una opción', + clear: 'limpia' }, pt: { noData: 'Nenhum dado encontrado', - chooseOption: 'Escolha uma opção' + chooseOption: 'Escolha uma opção', + clear: 'Apagar' }, ru: { noData: 'Данные не найдены', - chooseOption: 'Выберите опцию' + chooseOption: 'Выберите опцию', + clear: 'чистый' } }; diff --git a/projects/ui/src/lib/components/po-field/po-combo/interfaces/po-combo-literals.interface.ts b/projects/ui/src/lib/components/po-field/po-combo/interfaces/po-combo-literals.interface.ts index d511e0a5e..a6da17580 100644 --- a/projects/ui/src/lib/components/po-field/po-combo/interfaces/po-combo-literals.interface.ts +++ b/projects/ui/src/lib/components/po-field/po-combo/interfaces/po-combo-literals.interface.ts @@ -11,4 +11,7 @@ export interface PoComboLiterals { /** Texto exibido quando o combo estiver vazio. */ chooseOption?: string; + + /** Texto do aria-label do botão de limpar */ + clean?: string; } diff --git a/projects/ui/src/lib/components/po-field/po-combo/po-combo.component.html b/projects/ui/src/lib/components/po-field/po-combo/po-combo.component.html index 7e52cf153..dd3209b90 100644 --- a/projects/ui/src/lib/components/po-field/po-combo/po-combo.component.html +++ b/projects/ui/src/lib/components/po-field/po-combo/po-combo.component.html @@ -6,14 +6,14 @@ [p-required]="required" [p-show-required]="showRequired" > -
+
- - + +
diff --git a/projects/ui/src/lib/components/po-field/po-combo/po-combo.component.spec.ts b/projects/ui/src/lib/components/po-field/po-combo/po-combo.component.spec.ts index d2cab3ad7..b6db51ed3 100644 --- a/projects/ui/src/lib/components/po-field/po-combo/po-combo.component.spec.ts +++ b/projects/ui/src/lib/components/po-field/po-combo/po-combo.component.spec.ts @@ -92,7 +92,7 @@ describe('PoComboComponent:', () => { component.isProcessingValueByTab = false; spyOn(component, 'applyFilter'); component.controlApplyFilter('valor'); - expect(component.applyFilter).toHaveBeenCalledWith('valor', true); + expect(component.applyFilter).toHaveBeenCalledWith('valor', true, undefined); }); it('shouldn`t call apply filter when is processing "getObjectByValue"', () => { @@ -208,9 +208,9 @@ describe('PoComboComponent:', () => { spyOn(component, 'controlComboVisibility'); component.comboOpen = true; component.disabled = false; - - component.toggleComboVisibility(); - expect(component.controlComboVisibility).toHaveBeenCalledWith(false); + fixture.detectChanges(); + component.toggleComboVisibility(false); + expect(component.controlComboVisibility).toHaveBeenCalledWith(false, false, false); }); it('should call applyFilterInFirstClick', () => { @@ -571,12 +571,8 @@ describe('PoComboComponent:', () => { focus: () => {} } }; - component.poListbox = { - setFocus: () => {} - } as any; spyOn(component.contentElement.nativeElement, 'focus'); - spyOn(component.poListbox, 'setFocus'); component.visibleOptions = [{ value: 'item 1', label: 'item 1' }]; component.comboOpen = true; component.changeOnEnter = true; @@ -586,7 +582,6 @@ describe('PoComboComponent:', () => { expect(component.controlComboVisibility).toHaveBeenCalledWith(true); expect(component.isFiltering).toBe(false); - expect(component.poListbox.setFocus).toHaveBeenCalled(); }); it('shouldn`t call `selectNextOption` and call `controlComboVisibility` if `comboOpen` with false', () => { @@ -594,7 +589,6 @@ describe('PoComboComponent:', () => { component.comboOpen = false; component.visibleOptions = [{ value: '1', label: '1' }]; - spyOn(component, 'controlComboVisibility'); component.onKeyDown(event); @@ -683,6 +677,36 @@ describe('PoComboComponent:', () => { expect(component.updateComboList).toHaveBeenCalled(); }); + it('should call `clear` if esc is double clicked', () => { + const event = { ...fakeEvent, keyCode: 27 }; + + component.service = undefined; + component.selectedValue = 'Test1'; + component.comboOpen = true; + component['lastKey'] = 27; + + spyOn(component, 'clear'); + + component.onKeyDown(event); + + expect(component.clear).toHaveBeenCalled(); + }); + + it('should call `onCloseCombo` if esc is clicked', () => { + const event = { ...fakeEvent, keyCode: 27 }; + + component.service = undefined; + component.selectedValue = 'Test1'; + component.comboOpen = true; + component['lastKey'] = 20; + + spyOn(component, 'onCloseCombo'); + + component.onKeyDown(event); + + expect(component.onCloseCombo).toHaveBeenCalled(); + }); + it('shouldn`t call `updateComboList` if itens service is not undefined', () => { const event = { ...fakeEvent, keyCode: 13 }; @@ -949,6 +973,22 @@ describe('PoComboComponent:', () => { expect(component.comboOpen).toBe(true); }); + it('should open the input element', () => { + const focusSpy = spyOn(component.inputEl.nativeElement, 'focus'); + + component['open'](true); + + expect(focusSpy).toHaveBeenCalled(); + }); + + it('should add class to input element when isButton is true', () => { + const addClassSpy = spyOn(component.renderer, 'addClass'); + + component['open'](false, true); + + expect(addClassSpy).toHaveBeenCalledWith(component.inputEl.nativeElement, 'po-combo-input-focus'); + }); + it('open: should set page and options when has inifity scroll', () => { component.infiniteScroll = true; spyOn(component, 'getInputValue').and.returnValue(undefined); @@ -1156,10 +1196,11 @@ describe('PoComboComponent:', () => { it('should show po-clean if `clean` is true and `disabled` is false', () => { component.clean = true; component.disabled = false; + component.inputEl.nativeElement.value = 'Test'; fixture.detectChanges(); - expect(nativeElement.querySelector('po-clean')).toBeTruthy(); + expect(nativeElement.querySelector('.po-combo-clean')).toBeTruthy(); }); it('shouldn`t show po-clean if `clean` is true and `disabled` is true', () => { @@ -1167,7 +1208,7 @@ describe('PoComboComponent:', () => { component.disabled = true; fixture.detectChanges(); - expect(nativeElement.querySelector('po-clean')).toBe(null); + expect(nativeElement.querySelector('.po-combo-clean')).toBe(null); }); it('shouldn`t show po-clean if `clean` is false', () => { @@ -1316,11 +1357,14 @@ describe('PoComboComponent - with service:', () => { it('applyFilter: should call PoComboFilterService.getFilteredData() with param and filterParams', () => { const filterParams = 'filter'; const applyFilterValue = 'value'; + const isArrowDown = true; const param = { property: 'label', value: applyFilterValue }; const fakeThis: any = { controlComboVisibility: () => {}, setOptionsByApplyFilter: () => {}, + focusItem: () => {}, fieldLabel: 'label', + isArrowDown: true, filterParams: filterParams, service: { getFilteredData: () => {} @@ -1332,11 +1376,23 @@ describe('PoComboComponent - with service:', () => { spyOn(fakeThis.service, 'getFilteredData').and.returnValue({ subscribe: callback => callback() }); - component.applyFilter.apply(fakeThis, [applyFilterValue]); + component.applyFilter.apply(fakeThis, [applyFilterValue, isArrowDown]); expect(fakeThis.service.getFilteredData).toHaveBeenCalledWith(param, filterParams); }); + it('applyFilter: should call focusItem if param `isArrowDown` is true', () => { + spyOn(component.service, 'getFilteredData').and.returnValue(of([{ value: 'test' }])); + spyOn(component, 'setOptionsByApplyFilter'); + spyOn(component, 'focusItem'); + + component.defaultService.hasNext = true; + component.applyFilter('test', false, true); + + expect(component.setOptionsByApplyFilter).toHaveBeenCalled(); + expect(component['focusItem']).toHaveBeenCalled(); + }); + it('applyFilter: shouldn´t call PoComboFilterService.getFilteredData() if hasNext is false', () => { const filterParams = 'filter'; const applyFilterValue = 'value'; @@ -1539,49 +1595,60 @@ describe('PoComboComponent - with service:', () => { expect(component['initInputObservable']).not.toHaveBeenCalled(); }); - it(`searchOnEnter: should call 'controlApplyFilter' if has a service, + it(`searchOnEnterOrArrow: should call 'controlApplyFilter' if has a service, not has selectedView and value.length is greater than 'filterMinlength'`, () => { const value = 'newValue'; + const event = { + key: 'Enter' + }; component.selectedView = undefined; component.filterMinlength = 2; spyOn(component, 'controlApplyFilter'); - component.searchOnEnter(value); + component.searchOnEnterOrArrow(event, value); - expect(component.controlApplyFilter).toHaveBeenCalledWith(value); + expect(component.controlApplyFilter).toHaveBeenCalledWith(value, false); }); - it(`searchOnEnter: shouldn't call 'controlApplyFilter' if has a service and has selectedView`, () => { + it(`searchOnEnterOrArrow: shouldn't call 'controlApplyFilter' if has a service and has selectedView`, () => { const value = 'value'; component.selectedView = { label: 'Option 1', value: '1' }; - + const event = { + key: 'Enter' + }; spyOn(component, 'controlApplyFilter'); - component.searchOnEnter(value); + component.searchOnEnterOrArrow(event, value); expect(component.controlApplyFilter).not.toHaveBeenCalled(); }); - it(`searchOnEnter: shouldn't call 'controlApplyFilter' if doesn't have a service`, () => { + it(`searchOnEnterOrArrow: shouldn't call 'controlApplyFilter' if doesn't have a service`, () => { const value = 'value'; component.service = undefined; + const event = { + key: 'Enter' + }; spyOn(component, 'controlApplyFilter'); - component.searchOnEnter(value); + component.searchOnEnterOrArrow(event, value); expect(component.controlApplyFilter).not.toHaveBeenCalled(); }); - it(`searchOnEnter: shouldn't call 'controlApplyFilter' if value.length is less than 'filterMinlength'`, () => { + it(`searchOnEnterOrArrow: shouldn't call 'controlApplyFilter' if value.length is less than 'filterMinlength'`, () => { const value = 'value'; component.selectedView = { label: 'Option 1', value: '1' }; component.filterMinlength = 8; + const event = { + key: 'Enter' + }; spyOn(component, 'controlApplyFilter'); - component.searchOnEnter(value); + component.searchOnEnterOrArrow(event, value); expect(component.controlApplyFilter).not.toHaveBeenCalled(); }); @@ -1719,6 +1786,19 @@ describe('PoComboComponent - with service:', () => { expect(component.visibleOptions).toEqual(mockOptions); }); + + it('should focus on the listbox item when selectedValue is true', fakeAsync(() => { + const listboxItem = document.createElement('div'); + listboxItem.setAttribute('aria-selected', 'true'); + spyOn(document, 'querySelector').and.returnValue(listboxItem); + + const focusSpy = spyOn(listboxItem, 'focus'); + + component.selectedValue = true; + component['focusItem'](); + tick(100); + expect(focusSpy).toHaveBeenCalled(); + })); }); }); diff --git a/projects/ui/src/lib/components/po-field/po-combo/po-combo.component.ts b/projects/ui/src/lib/components/po-field/po-combo/po-combo.component.ts index e14927f42..490063166 100644 --- a/projects/ui/src/lib/components/po-field/po-combo/po-combo.component.ts +++ b/projects/ui/src/lib/components/po-field/po-combo/po-combo.component.ts @@ -122,6 +122,7 @@ export class PoComboComponent extends PoComboBaseComponent implements AfterViewI infiniteLoading: boolean = false; private _isServerSearching: boolean = false; + private lastKey; private clickoutListener: () => void; private eventResizeListener: () => void; @@ -234,8 +235,8 @@ export class PoComboComponent extends PoComboBaseComponent implements AfterViewI if (key === PoKeyCodeEnum.arrowDown) { event.preventDefault(); - if (!this.service) { - this.poListbox?.setFocus(); + if (this.visibleOptions.length) { + this.focusItem(); } this.controlComboVisibility(true); @@ -261,13 +262,33 @@ export class PoComboComponent extends PoComboBaseComponent implements AfterViewI if (key === PoKeyCodeEnum.enter) { this.controlComboVisibility(true); } + + if (key === PoKeyCodeEnum.esc) { + if (key === this.lastKey) { + this.lastKey = ''; + if (this.selectedValue) { + this.clear(null); + this.inputEl.nativeElement.focus(); + } + return; + } else { + this.onCloseCombo(); + } + } + + this.lastKey = event.keyCode; } onKeyUp(event?: any) { const key = event.keyCode || event.which; const inputValue = event.target.value; - const isValidKey = key !== PoKeyCodeEnum.arrowUp && key !== PoKeyCodeEnum.arrowDown && key !== PoKeyCodeEnum.enter; + const isValidKey = + key !== PoKeyCodeEnum.arrowUp && + key !== PoKeyCodeEnum.arrowDown && + key !== PoKeyCodeEnum.enter && + key !== PoKeyCodeEnum.esc && + key !== PoKeyCodeEnum.tab; if (isValidKey) { if (inputValue) { @@ -325,7 +346,7 @@ export class PoComboComponent extends PoComboBaseComponent implements AfterViewI } } - controlApplyFilter(value) { + controlApplyFilter(value, isArrowDown?: boolean) { if ( (!this.isProcessingValueByTab && (!this.selectedOption || value !== this.selectedOption[this.dynamicLabel])) || !this.cache @@ -333,12 +354,12 @@ export class PoComboComponent extends PoComboBaseComponent implements AfterViewI this.defaultService.hasNext = true; this.page = this.setPage(); this.options = []; - this.applyFilter(value, true); + this.applyFilter(value, true, isArrowDown); } this.isProcessingValueByTab = false; } - applyFilter(value: string, reset: boolean = false) { + applyFilter(value: string, reset: boolean = false, isArrowDown?: boolean) { if (this.defaultService.hasNext) { this.controlComboVisibility(false, reset); this.isServerSearching = true; @@ -348,7 +369,12 @@ export class PoComboComponent extends PoComboBaseComponent implements AfterViewI : { property: this.fieldLabel, value }; this.filterSubscription = this.service.getFilteredData(param, this.filterParams).subscribe( - items => this.setOptionsByApplyFilter(value, items, reset), + items => { + this.setOptionsByApplyFilter(value, items, reset); + if (isArrowDown) { + this.focusItem(); + } + }, error => this.onErrorFilteredData() ); } @@ -397,7 +423,7 @@ export class PoComboComponent extends PoComboBaseComponent implements AfterViewI }, this.debounceTime); } - toggleComboVisibility(): void { + toggleComboVisibility(isButton?: boolean): void { if (this.disabled) { return; } @@ -406,7 +432,7 @@ export class PoComboComponent extends PoComboBaseComponent implements AfterViewI this.applyFilterInFirstClick(); } - this.controlComboVisibility(!this.comboOpen); + this.controlComboVisibility(!this.comboOpen, false, isButton); } applyFilterInFirstClick() { @@ -417,8 +443,8 @@ export class PoComboComponent extends PoComboBaseComponent implements AfterViewI } } - controlComboVisibility(toOpen: boolean, reset: boolean = false) { - toOpen ? this.open(reset) : this.close(reset); + controlComboVisibility(toOpen: boolean, reset: boolean = false, isButton?: boolean) { + toOpen ? this.open(reset, isButton) : this.close(reset); } onCloseCombo() { @@ -443,6 +469,7 @@ export class PoComboComponent extends PoComboBaseComponent implements AfterViewI } this.previousSearchValue = this.selectedView[this.dynamicLabel]; + this.inputEl.nativeElement.focus(); } calculateScrollTop(selectedItem, index) { @@ -500,9 +527,14 @@ export class PoComboComponent extends PoComboBaseComponent implements AfterViewI ); // windows menu } - searchOnEnter(value: string) { - if (this.service && !this.selectedView && value.length >= this.filterMinlength) { - this.controlApplyFilter(value); + searchOnEnterOrArrow(event, value: string) { + if ( + (event.key === 'ArrowDown' || event.key === 'Enter') && + this.service && + !this.selectedView && + value.length >= this.filterMinlength + ) { + this.controlApplyFilter(value, event.key === 'ArrowDown'); } } @@ -538,6 +570,8 @@ export class PoComboComponent extends PoComboBaseComponent implements AfterViewI this.removeListeners(); this.isFiltering = false; + + this.renderer.removeClass(this.inputEl.nativeElement, 'po-combo-input-focus'); } private initializeListeners() { @@ -571,7 +605,7 @@ export class PoComboComponent extends PoComboBaseComponent implements AfterViewI this.controlComboVisibility(true); } - private open(reset: boolean) { + private open(reset: boolean, isButton?: boolean) { this.comboOpen = true; if (!reset && this.infiniteScroll) { if (!this.getInputValue()) { @@ -586,7 +620,10 @@ export class PoComboComponent extends PoComboBaseComponent implements AfterViewI this.initializeListeners(); - this.inputEl.nativeElement.focus(); + isButton + ? this.renderer.addClass(this.inputEl.nativeElement, 'po-combo-input-focus') + : this.inputEl.nativeElement.focus(); + this.setContainerPosition(); } @@ -629,4 +666,19 @@ export class PoComboComponent extends PoComboBaseComponent implements AfterViewI private setScrollingControl() { return this.infiniteScroll ? true : false; } + + private focusItem() { + this.poListbox?.listboxItemList?.nativeElement.focus(); + setTimeout(() => { + let item; + if (this.selectedValue) { + item = document.querySelector('.po-listbox-item[aria-selected="true"]'); + } else { + item = document.querySelectorAll('.po-listbox-item')[0] as HTMLElement; + } + this.poListbox?.listboxItemList?.nativeElement.focus(); + + item?.focus(); + }); + } } diff --git a/projects/ui/src/lib/components/po-listbox/po-listbox.component.html b/projects/ui/src/lib/components/po-listbox/po-listbox.component.html index 49ccf6cca..c8d56557d 100644 --- a/projects/ui/src/lib/components/po-listbox/po-listbox.component.html +++ b/projects/ui/src/lib/components/po-listbox/po-listbox.component.html @@ -35,7 +35,7 @@