diff --git a/projects/ui/src/lib/components/po-field/po-field.module.ts b/projects/ui/src/lib/components/po-field/po-field.module.ts index 992cb84b0f..4c6188fade 100644 --- a/projects/ui/src/lib/components/po-field/po-field.module.ts +++ b/projects/ui/src/lib/components/po-field/po-field.module.ts @@ -27,7 +27,6 @@ import { PoListBoxModule } from '../po-listbox/po-listbox.module'; import { PoComboComponent } from './po-combo/po-combo.component'; import { PoComboOptionTemplateDirective } from './po-combo/po-combo-option-template/po-combo-option-template.directive'; import { PoMultiselectOptionTemplateDirective } from './po-multiselect/po-multiselect-option-template/po-multiselect-option-template.directive'; -import { PoDatepickerComponent } from './po-datepicker/po-datepicker.component'; import { PoDatepickerRangeComponent } from './po-datepicker-range/po-datepicker-range.component'; import { PoDecimalComponent } from './po-decimal/po-decimal.component'; import { PoEmailComponent } from './po-email/po-email.component'; @@ -36,7 +35,6 @@ import { PoLookupComponent } from './po-lookup/po-lookup.component'; import { PoLookupModalComponent } from './po-lookup/po-lookup-modal/po-lookup-modal.component'; import { PoMultiselectDropdownComponent } from './po-multiselect/po-multiselect-dropdown/po-multiselect-dropdown.component'; import { PoMultiselectComponent } from './po-multiselect/po-multiselect.component'; -import { PoMultiselectSearchComponent } from './po-multiselect/po-multiselect-search/po-multiselect-search.component'; import { PoRichTextBodyComponent } from './po-rich-text/po-rich-text-body/po-rich-text-body.component'; import { PoRichTextComponent } from './po-rich-text/po-rich-text.component'; import { PoRichTextImageModalComponent } from './po-rich-text/po-rich-text-image-modal/po-rich-text-image-modal.component'; @@ -57,6 +55,7 @@ import { PoUrlComponent } from './po-url/po-url.component'; import { PoCheckboxModule } from './po-checkbox/po-checkbox.module'; import { PoSwitchModule } from './po-switch/po-switch.module'; import { PoLabelModule } from '../po-label'; +import { PoTagComponent, PoTagModule } from '../po-tag'; /** * @description @@ -98,7 +97,8 @@ import { PoLabelModule } from '../po-label'; PoRadioModule, PoLabelModule, PoListBoxModule, - PoSwitchModule + PoSwitchModule, + PoTagModule ], exports: [ PoCheckboxGroupModule, @@ -127,7 +127,8 @@ import { PoLabelModule } from '../po-label'; PoCheckboxModule, PoRadioModule, PoLabelModule, - PoSwitchModule + PoSwitchModule, + PoTagModule ], declarations: [ PoComboComponent, @@ -142,7 +143,6 @@ import { PoLabelModule } from '../po-label'; PoLookupModalComponent, PoMultiselectComponent, PoMultiselectDropdownComponent, - PoMultiselectSearchComponent, PoNumberComponent, PoPasswordComponent, PoRichTextBodyComponent, diff --git a/projects/ui/src/lib/components/po-field/po-multiselect/po-multiselect-base.component.ts b/projects/ui/src/lib/components/po-field/po-multiselect/po-multiselect-base.component.ts index 0550df5078..a834dda66a 100644 --- a/projects/ui/src/lib/components/po-field/po-multiselect/po-multiselect-base.component.ts +++ b/projects/ui/src/lib/components/po-field/po-multiselect/po-multiselect-base.component.ts @@ -29,22 +29,26 @@ export const poMultiselectLiteralsDefault = { en: { noData: 'No data found', placeholderSearch: 'Search', - selectAll: 'Select all' + selectAll: 'Select all', + selectItem: 'Select items' }, es: { noData: 'Datos no encontrados', placeholderSearch: 'Busca', - selectAll: 'Seleccionar todo' + selectAll: 'Seleccionar todo', + selectItem: 'Seleccionar items' }, pt: { noData: 'Nenhum dado encontrado', placeholderSearch: 'Buscar', - selectAll: 'Selecionar todos' + selectAll: 'Selecionar todos', + selectItem: 'Selecionar itens' }, ru: { noData: 'Данные не найдены', placeholderSearch: 'искать', - selectAll: 'Выбрать все' + selectAll: 'Выбрать все', + selectItem: 'Выбрать элементы' } }; @@ -262,7 +266,9 @@ export abstract class PoMultiselectBaseComponent implements ControlValueAccessor * ``` * const customLiterals: PoMultiselectLiterals = { * noData: 'Nenhum dado encontrado', - * placeholderSearch: 'Buscar' + * placeholderSearch: 'Buscar', + * selectAll: 'Select all', + * selectItem: 'Select items' * }; * ``` * @@ -296,6 +302,7 @@ export abstract class PoMultiselectBaseComponent implements ControlValueAccessor this._literals = poMultiselectLiteralsDefault[this.language]; } } + get literals() { return this._literals || poMultiselectLiteralsDefault[this.language]; } diff --git a/projects/ui/src/lib/components/po-field/po-multiselect/po-multiselect-dropdown/po-multiselect-dropdown.component.html b/projects/ui/src/lib/components/po-field/po-multiselect/po-multiselect-dropdown/po-multiselect-dropdown.component.html index 0cd990e7f1..c9c68f84f5 100644 --- a/projects/ui/src/lib/components/po-field/po-multiselect/po-multiselect-dropdown/po-multiselect-dropdown.component.html +++ b/projects/ui/src/lib/components/po-field/po-multiselect/po-multiselect-dropdown/po-multiselect-dropdown.component.html @@ -20,6 +20,7 @@ (p-change)="clickItem($event)" (p-change-all)="onClickSelectAll()" (p-change-search)="callChangeSearch($event)" + (p-close)="closeDropdown.emit()" > diff --git a/projects/ui/src/lib/components/po-field/po-multiselect/po-multiselect-dropdown/po-multiselect-dropdown.component.spec.ts b/projects/ui/src/lib/components/po-field/po-multiselect/po-multiselect-dropdown/po-multiselect-dropdown.component.spec.ts index 93eb63cfc4..e9f71b2188 100644 --- a/projects/ui/src/lib/components/po-field/po-multiselect/po-multiselect-dropdown/po-multiselect-dropdown.component.spec.ts +++ b/projects/ui/src/lib/components/po-field/po-multiselect/po-multiselect-dropdown/po-multiselect-dropdown.component.spec.ts @@ -5,7 +5,6 @@ import { configureTestSuite } from './../../../../util-test/util-expect.spec'; import { PoMultiselectDropdownComponent } from './po-multiselect-dropdown.component'; import { poMultiselectLiteralsDefault } from '../po-multiselect-base.component'; -import { PoMultiselectSearchComponent } from './../po-multiselect-search/po-multiselect-search.component'; describe('PoMultiselectDropdownComponent:', () => { let component: PoMultiselectDropdownComponent; @@ -13,7 +12,7 @@ describe('PoMultiselectDropdownComponent:', () => { configureTestSuite(() => { TestBed.configureTestingModule({ - declarations: [PoMultiselectDropdownComponent, PoMultiselectSearchComponent] + declarations: [PoMultiselectDropdownComponent] }); }); @@ -292,13 +291,5 @@ describe('PoMultiselectDropdownComponent:', () => { expect(fixture.nativeElement.querySelector('.po-listbox-container-no-data')).toBeNull(); }); - - it('shouldn`t show `po-multiselect-search` if `hideSearch` is `true`', () => { - component.hideSearch = true; - - fixture.detectChanges(); - - expect(fixture.debugElement.query(By.css('po-multiselect-search'))).toEqual(null); - }); }); }); diff --git a/projects/ui/src/lib/components/po-field/po-multiselect/po-multiselect-dropdown/po-multiselect-dropdown.component.ts b/projects/ui/src/lib/components/po-field/po-multiselect/po-multiselect-dropdown/po-multiselect-dropdown.component.ts index 8e06a5ee54..310d3ed2b8 100644 --- a/projects/ui/src/lib/components/po-field/po-multiselect/po-multiselect-dropdown/po-multiselect-dropdown.component.ts +++ b/projects/ui/src/lib/components/po-field/po-multiselect/po-multiselect-dropdown/po-multiselect-dropdown.component.ts @@ -4,7 +4,6 @@ import { Component, ElementRef, EventEmitter, - HostListener, Input, Output, ViewChild, @@ -13,7 +12,6 @@ import { import { PoMultiselectLiterals } from '../../index'; import { PoMultiselectOption } from '../po-multiselect-option.interface'; -import { PoMultiselectSearchComponent } from './../po-multiselect-search/po-multiselect-search.component'; import { PoListBoxComponent } from './../../../po-listbox/po-listbox.component'; /** diff --git a/projects/ui/src/lib/components/po-field/po-multiselect/po-multiselect-literals.interface.ts b/projects/ui/src/lib/components/po-field/po-multiselect/po-multiselect-literals.interface.ts index a38a5b9930..5d7da73264 100644 --- a/projects/ui/src/lib/components/po-field/po-multiselect/po-multiselect-literals.interface.ts +++ b/projects/ui/src/lib/components/po-field/po-multiselect/po-multiselect-literals.interface.ts @@ -14,4 +14,7 @@ export interface PoMultiselectLiterals { /** Texto exibido no botão de selecionar todos. */ selectAll?: string; + + /** Texto exibido na propriedade placeholder. */ + selectItem?: string; } diff --git a/projects/ui/src/lib/components/po-field/po-multiselect/po-multiselect-search/po-multiselect-search.component.html b/projects/ui/src/lib/components/po-field/po-multiselect/po-multiselect-search/po-multiselect-search.component.html deleted file mode 100644 index c746ffaee0..0000000000 --- a/projects/ui/src/lib/components/po-field/po-multiselect/po-multiselect-search/po-multiselect-search.component.html +++ /dev/null @@ -1,13 +0,0 @@ - diff --git a/projects/ui/src/lib/components/po-field/po-multiselect/po-multiselect-search/po-multiselect-search.component.spec.ts b/projects/ui/src/lib/components/po-field/po-multiselect/po-multiselect-search/po-multiselect-search.component.spec.ts deleted file mode 100644 index edb1dfb2bc..0000000000 --- a/projects/ui/src/lib/components/po-field/po-multiselect/po-multiselect-search/po-multiselect-search.component.spec.ts +++ /dev/null @@ -1,99 +0,0 @@ -import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; - -import { expectPropertiesValues } from '../../../../util-test/util-expect.spec'; -import { poLocaleDefault } from '../../../../services/po-language/po-language.constant'; - -import { poMultiselectLiteralsDefault } from '../po-multiselect-base.component'; -import { PoMultiselectSearchComponent } from './po-multiselect-search.component'; - -describe('PoMultiselectSearchComponent:', () => { - let component: PoMultiselectSearchComponent; - let fixture: ComponentFixture; - - beforeEach( - waitForAsync(() => { - TestBed.configureTestingModule({ - declarations: [PoMultiselectSearchComponent] - }).compileComponents(); - }) - ); - - beforeEach(() => { - fixture = TestBed.createComponent(PoMultiselectSearchComponent); - component = fixture.componentInstance; - - component.literals = poMultiselectLiteralsDefault[poLocaleDefault]; - }); - - it('should be created', () => { - expect(component).toBeTruthy(); - }); - - it('should emit onChange', () => { - spyOn(component.change, 'emit'); - component.onChange({}); - expect(component.change.emit).toHaveBeenCalled(); - }); - - it('should set focus on input', () => { - component.setFocus(); - expect(document.activeElement.tagName.toLowerCase()).toBe('input'); - }); - - it('should clean input', () => { - component.inputElement.nativeElement.value = 'abc'; - component.clean(); - expect(component.inputElement.nativeElement.value).toBe(''); - }); - - describe('Properties:', () => { - it('p-placeholder: should update property with default placeholder if is setted with invalid values', () => { - const invalidValues = [undefined, 1, {}, [], true, false]; - const defaultPlaceholderSearch = poMultiselectLiteralsDefault.pt.placeholderSearch; - - expectPropertiesValues(component, 'placeholder', invalidValues, defaultPlaceholderSearch); - }); - - it('p-placeholder: should be in portuguese if literals is setted with `pt`', () => { - component.placeholder = ''; - - expect(component.placeholder).toBe(poMultiselectLiteralsDefault.pt.placeholderSearch); - }); - - it('p-placeholder: should be in english if literals is setted with `en`', () => { - component.literals = poMultiselectLiteralsDefault.en; - - component.placeholder = ''; - - expect(component.placeholder).toBe(poMultiselectLiteralsDefault.en.placeholderSearch); - }); - - it('p-placeholder: should be in spanish if literals is setted with `es`', () => { - component.literals = poMultiselectLiteralsDefault.es; - - component.placeholder = ''; - - expect(component.placeholder).toBe(poMultiselectLiteralsDefault.es.placeholderSearch); - }); - - it('p-placeholder: should be poMultiselectLiteralsDefault.placeholderSearch if _placeholder is null', () => { - component.literals = poMultiselectLiteralsDefault.es; - - component['_placeholder'] = null; - - expect(component.placeholder).toBe(poMultiselectLiteralsDefault.es.placeholderSearch); - }); - - it('p-placeholder: should return the "placeholderString"', () => { - const placeholderString = 'placeholder test'; - - expectPropertiesValues(component, 'placeholder', placeholderString, placeholderString); - }); - - it('inputValue: should be the same value as nativeElement.value', () => { - component.inputElement.nativeElement.value = 'test'; - - expect(component.inputValue).toBe('test'); - }); - }); -}); diff --git a/projects/ui/src/lib/components/po-field/po-multiselect/po-multiselect-search/po-multiselect-search.component.ts b/projects/ui/src/lib/components/po-field/po-multiselect/po-multiselect-search/po-multiselect-search.component.ts deleted file mode 100644 index 2d2a59a772..0000000000 --- a/projects/ui/src/lib/components/po-field/po-multiselect/po-multiselect-search/po-multiselect-search.component.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - ElementRef, - EventEmitter, - Input, - Output, - ViewChild -} from '@angular/core'; - -import { isTypeof } from '../../../../utils/util'; - -import { PoMultiselectLiterals } from '../../index'; - -/** - * @docsPrivate - * - * @description - * - * Componente de pesquisa que será criado dentro do dropdown do `po-multiselect`. - */ -@Component({ - selector: 'po-multiselect-search', - templateUrl: './po-multiselect-search.component.html', - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class PoMultiselectSearchComponent { - @ViewChild('inputElement', { read: ElementRef, static: true }) inputElement: ElementRef; - - /** Propriedade que recebe as literais definidas no `po-multiselect`. */ - @Input('p-literals') literals?: PoMultiselectLiterals; - - @Input('p-field-value') fieldValue: string; - - /** Evento que será disparado a cada tecla digitada no campo de busca. */ - @Output('p-change') change = new EventEmitter(); - - private _placeholder?: string; - - constructor(private cd: ChangeDetectorRef) {} - - /** - * @optional - * - * @description - * - * Placeholder do campo de pesquisa. - * - * > Caso o mesmo não seja informado, o valor padrão será traduzido com base no idioma do navegador (pt, es e en). - * - * @default `Buscar` - */ - @Input('p-placeholder') set placeholder(placeholder: string) { - this._placeholder = placeholder && isTypeof(placeholder, 'string') ? placeholder : this.literals.placeholderSearch; - } - - get placeholder() { - return this._placeholder || this.literals.placeholderSearch; - } - - get inputValue() { - return this.inputElement.nativeElement.value; - } - - onChange(event) { - this.change.emit({ event: event, [this.fieldValue]: this.inputElement.nativeElement.value }); - } - - setFocus() { - this.inputElement.nativeElement.focus(); - } - - clean() { - this.inputElement.nativeElement.value = ''; - this.cd.markForCheck(); - } -} diff --git a/projects/ui/src/lib/components/po-field/po-multiselect/po-multiselect.component.html b/projects/ui/src/lib/components/po-field/po-multiselect/po-multiselect.component.html index c43fdcb495..b1dc9be6a1 100644 --- a/projects/ui/src/lib/components/po-field/po-multiselect/po-multiselect.component.html +++ b/projects/ui/src/lib/components/po-field/po-multiselect/po-multiselect.component.html @@ -1,40 +1,44 @@ -
+
- - {{ placeholder }} + + {{ placeholder ? placeholder : literals.selectItem }} - - + [p-disabled]="disabled" + (p-close)="closeDisclaimer(disclaimer[fieldValue])" + (p-click)="closeDisclaimer(disclaimer[fieldValue])" + >
diff --git a/projects/ui/src/lib/components/po-field/po-multiselect/po-multiselect.component.spec.ts b/projects/ui/src/lib/components/po-field/po-multiselect/po-multiselect.component.spec.ts index 7d026a6b37..e576d94218 100644 --- a/projects/ui/src/lib/components/po-field/po-multiselect/po-multiselect.component.spec.ts +++ b/projects/ui/src/lib/components/po-field/po-multiselect/po-multiselect.component.spec.ts @@ -11,10 +11,11 @@ import { PoFieldContainerComponent } from '../po-field-container/po-field-contai import { PoMultiselectBaseComponent } from '../po-multiselect/po-multiselect-base.component'; import { PoMultiselectComponent } from './po-multiselect.component'; import { PoMultiselectDropdownComponent } from './po-multiselect-dropdown/po-multiselect-dropdown.component'; -import { PoMultiselectSearchComponent } from './po-multiselect-search/po-multiselect-search.component'; import { PoMultiselectFilter } from './po-multiselect-filter.interface'; import { PoMultiselectOption } from './po-multiselect-option.interface'; import { PoMultiselectFilterService } from './po-multiselect-filter.service'; +import { PoKeyCodeEnum } from '../../../enums/po-key-code.enum'; +import { Renderer2 } from '@angular/core'; const poMultiselectFilterServiceStub: PoMultiselectFilter = { getFilteredData: function (params: { property: string; value: string }): Observable> { @@ -32,10 +33,21 @@ describe('PoMultiselectComponent:', () => { let multiSelectService: PoMultiselectFilterService; let httpMock: HttpTestingController; + let disclaimers; + let index; + let renderer: Renderer2; const mockURL = 'rest/tecnologies'; beforeEach(async () => { + disclaimers = document.createElement('div'); + disclaimers.innerHTML = ` +
+
+
+ `; + index = 2; + await TestBed.configureTestingModule({ imports: [HttpClientTestingModule], declarations: [ @@ -43,14 +55,14 @@ describe('PoMultiselectComponent:', () => { PoFieldContainerComponent, PoMultiselectComponent, PoMultiselectDropdownComponent, - PoMultiselectSearchComponent, PoFieldContainerBottomComponent ], - providers: [HttpClient, HttpHandler, PoMultiselectFilterService] + providers: [HttpClient, HttpHandler, Renderer2, PoMultiselectFilterService] }).compileComponents(); fixture = TestBed.createComponent(PoMultiselectComponent); component = fixture.componentInstance; + renderer = TestBed.inject(Renderer2); fnAdjustContainerPosition = component['adjustContainerPosition']; component['adjustContainerPosition'] = () => {}; @@ -113,8 +125,8 @@ describe('PoMultiselectComponent:', () => { component.calculateVisibleItems.call(fakeThis); - expect(fakeThis.visibleDisclaimers.length).toBe(2); - expect(fakeThis.visibleDisclaimers[1].value).toBe(''); + expect(fakeThis.visibleDisclaimers.length).toBe(1); + expect(fakeThis.visibleDisclaimers[1]).toBe(undefined); expect(fakeThis.isCalculateVisibleItems).toBeFalsy(); }); @@ -147,37 +159,77 @@ describe('PoMultiselectComponent:', () => { expect(component.debounceResize).toHaveBeenCalled(); }); - it('should call controlDropdownVisibility arrow to up is pressed', () => { - const fakeEvent = { - preventDefault: () => true, - keyCode: 40 - }; + it('shouuld call focusPreviousDisclaimer', () => { + component['focusPreviousDisclaimer'](disclaimers.children, index); + + const focusedElement = disclaimers.children[index - 1] as HTMLElement; + + expect(disclaimers.contains(focusedElement)).toBe(true); + }); + + it('should call focusNextDisclaimer', () => { + spyOn(component.inputElement.nativeElement, 'focus'); + + component['focusNextDisclaimer'](disclaimers.children, index); + expect(component.inputElement.nativeElement.focus).toHaveBeenCalled(); + }); + + it('should call focusNextDisclaimer when index equals 1', () => { + const fakeEvent = { focus: jasmine.createSpy('focus') }; + spyOn(disclaimers.children[1 + 1], 'querySelector').and.returnValue(fakeEvent); + + component['focusNextDisclaimer'](disclaimers.children, 1); + + expect((disclaimers.children[1] as HTMLElement).outerHTML).toBe('
'); + }); + + it('should call addKeyListener if keyCode entries [37, 39, 40]', () => { + spyOn(component, 'focusPreviousDisclaimer' as any); + spyOn(component, 'focusNextDisclaimer' as any); spyOn(component, 'controlDropdownVisibility'); - component.onKeyDown(fakeEvent); + + const spanElement = document.createElement('span'); + + const eventLeft = new KeyboardEvent('keydown', { keyCode: PoKeyCodeEnum.left }); + component['addKeyListener'](spanElement, disclaimers, index, eventLeft); + spanElement.dispatchEvent(eventLeft); + + const eventRight = new KeyboardEvent('keydown', { keyCode: PoKeyCodeEnum.right }); + component['addKeyListener'](spanElement, disclaimers, index, eventRight); + spanElement.dispatchEvent(eventRight); + + const eventArrowDown = new KeyboardEvent('keydown', { keyCode: PoKeyCodeEnum.arrowDown }); + component['addKeyListener'](spanElement, disclaimers, index, eventArrowDown); + spanElement.dispatchEvent(eventArrowDown); + + expect(component['focusPreviousDisclaimer']).toHaveBeenCalledWith(disclaimers, index); + expect(component['focusNextDisclaimer']).toHaveBeenCalledWith(disclaimers, index); expect(component.controlDropdownVisibility).toHaveBeenCalledWith(true); }); - it('should call controlDropdownVisibility tab is pressed', () => { - const fakeEvent = { - preventDefault: () => true, - keyCode: 9 - }; - + it('should call preventDefault and controlDropdownVisibility(false) when keyCode esc is pressed', () => { + const event = { preventDefault: jasmine.createSpy(), keyCode: 27 }; spyOn(component, 'controlDropdownVisibility'); - component.onKeyDown(fakeEvent); + + component.onKeyDown(event); + + expect(event.preventDefault).toHaveBeenCalled(); expect(component.controlDropdownVisibility).toHaveBeenCalledWith(false); }); - it('shouldn`t call controlDropdownVisibility when another key is pressed', () => { + it('should call controlDropdownVisibility arrow to down is pressed', () => { const fakeEvent = { preventDefault: () => true, - keyCode: 1 + keyCode: 40, + setFocusOnDropdownListbox: () => {} }; - spyOn(component, 'controlDropdownVisibility'); - component.onKeyDown(fakeEvent); - expect(component.controlDropdownVisibility).not.toHaveBeenCalled(); + spyOn(fakeEvent, 'preventDefault'); + const onKeyDown = component.onKeyDown(fakeEvent); + + expect(onKeyDown).toBeUndefined(); + expect(component.controlDropdownVisibility).toHaveBeenCalledWith(true); }); it('should call controlDropdownVisibility when enabled', () => { @@ -226,6 +278,80 @@ describe('PoMultiselectComponent:', () => { expect(component.controlDropdownVisibility).not.toHaveBeenCalled(); }); + it('should call addKeyListener for PoKeyCodeEnum.left or PoKeyCodeEnum.right', () => { + const event = new KeyboardEvent('keydown', { keyCode: PoKeyCodeEnum.left }); + const tagRemovable = document.createElement('span'); + tagRemovable.setAttribute('class', 'po-disclaimer-remove'); + + const disclaimer = document.createElement('span'); + disclaimer.setAttribute('class', 'po-disclaimer'); + disclaimer.appendChild(tagRemovable); + + const disclaimersFake = [disclaimer, disclaimer]; + spyOn(component, 'addKeyListener' as any); + spyOn(component['el'].nativeElement, 'querySelectorAll').and.returnValue(disclaimersFake); + + component.onKeyDown(event); + + expect(component['addKeyListener']).toHaveBeenCalled(); + }); + + it('should return when event keyCode is PoKeyCodeEnum.tab and visibleDisclaimers.length > 1', () => { + const event = new KeyboardEvent('keydown', { keyCode: PoKeyCodeEnum.tab }); + const tagRemovable = document.createElement('span'); + tagRemovable.setAttribute('class', 'po-disclaimer-remove'); + + component.visibleDisclaimers = [tagRemovable, tagRemovable]; + + component.onKeyDown(event); + + expect(component.visibleDisclaimers.length).toEqual(2); + }); + + it('should return when event keyCode is PoKeyCodeEnum.tab and visibleDisclaimers.length < 1', () => { + const event = new KeyboardEvent('keydown', { keyCode: PoKeyCodeEnum.tab }); + component.visibleDisclaimers = []; + + component.onKeyDown(event); + + expect(component.visibleDisclaimers.length).toEqual(0); + }); + + it('should call preventDefault and controlDropdownVisibility(true) when keyCode space is pressed', () => { + const event = { preventDefault: jasmine.createSpy(), keyCode: 32 }; + spyOn(component, 'controlDropdownVisibility'); + + component.onKeyDown(event); + + expect(event.preventDefault).toHaveBeenCalled(); + expect(component.controlDropdownVisibility).toHaveBeenCalledWith(true); + }); + + it('should call focus and controlDropdownVisibility(true) when keyCode enter is pressed', () => { + const event = { preventDefault: jasmine.createSpy(), keyCode: 13 }; + component.visibleDisclaimers = []; + spyOn(component, 'controlDropdownVisibility'); + spyOn(component, 'focus'); + + component.onKeyDown(event); + + expect(component.controlDropdownVisibility).toHaveBeenCalledWith(true); + expect(component.focus).toHaveBeenCalled(); + }); + + it('shouldn`t call focus and controlDropdownVisibility(true) when keyCode enter is pressed', () => { + const event = { preventDefault: jasmine.createSpy(), keyCode: 13 }; + const tagRemovable = document.createElement('span'); + tagRemovable.setAttribute('class', 'po-disclaimer-remove'); + component.visibleDisclaimers = [tagRemovable, tagRemovable]; + spyOn(component, 'controlDropdownVisibility'); + + component.onKeyDown(event); + + expect(event.preventDefault).toHaveBeenCalled(); + expect(component.controlDropdownVisibility).toHaveBeenCalledWith(true); + }); + it('should call dropdown.scrollTo', () => { component.options = [ { label: 'label', value: 1 }, diff --git a/projects/ui/src/lib/components/po-field/po-multiselect/po-multiselect.component.ts b/projects/ui/src/lib/components/po-field/po-multiselect/po-multiselect.component.ts index 6c41a5ed95..86eca3e2bc 100644 --- a/projects/ui/src/lib/components/po-field/po-multiselect/po-multiselect.component.ts +++ b/projects/ui/src/lib/components/po-field/po-multiselect/po-multiselect.component.ts @@ -30,6 +30,8 @@ import { PoMultiselectOptionTemplateDirective } from './po-multiselect-option-te const poMultiselectContainerOffset = 8; const poMultiselectContainerPositionDefault = 'bottom'; +const poMultiselectInputPaddingRight = 52; +const poMultiselectSpaceBetweenDisclaimers = 8; /* istanbul ignore next */ const providers = [ @@ -115,7 +117,7 @@ export class PoMultiselectComponent positionDisclaimerExtra; timeoutResize; visibleElement = false; - + initializedOnKeyDown: boolean = false; private isCalculateVisibleItems: boolean = true; private cacheOptions: Array; @@ -135,6 +137,72 @@ export class PoMultiselectComponent this.focus(); } this.initialized = true; + this.observerMutationTag(); + } + + observerMutationTag() { + const observer = new MutationObserver(mutations => { + mutations.forEach(mutation => { + if (mutation.addedNodes && mutation.addedNodes.length > 0) { + this.addKeyListener(); + } + }); + }); + + const config = { childList: true, subtree: true }; + const targetNode = this.el.nativeElement; + + observer.observe(targetNode, config); + } + + isElementFocused: boolean = false; + + onKeyDownDropdown(event: KeyboardEvent, index: number) { + if (event.key === 'Escape') { + event.preventDefault(); + this.controlDropdownVisibility(false); + this.inputElement.nativeElement.focus(); + } + } + + addKeyListener() { + const tagRemoveElements = this.el.nativeElement.querySelectorAll('.po-tag-remove'); + tagRemoveElements.forEach((tagRemoveElement, index) => { + this.renderer.listen(tagRemoveElement, 'focus', () => { + this.isElementFocused = true; + }); + + this.renderer.listen(tagRemoveElement, 'blur', () => { + this.isElementFocused = false; + }); + + this.renderer.listen(tagRemoveElement, 'keydown', (event: KeyboardEvent) => { + if (event.key === 'Enter') { + console.log(event); + console.log(typeof event); + event.preventDefault(); + event.stopPropagation(); + + tagRemoveElements[index + 1]?.focus(); + + if (index === 0 && this.visibleDisclaimers.length > 1) { + tagRemoveElements[index + 1]?.focus(); + } else if (tagRemoveElements.lenght - 1) { + tagRemoveElements[index - 1]?.focus(); + } + } + + if (event.key === 'ArrowLeft') { + if (index > 0) { + tagRemoveElements[index - 1].focus(); + } + } else if (event.key === 'ArrowRight') { + if (index < tagRemoveElements.length - 1) { + tagRemoveElements[index + 1].focus(); + } + } + }); + }); } ngOnChanges(changes: SimpleChanges) { @@ -183,18 +251,18 @@ export class PoMultiselectComponent } getInputWidth() { - return this.el.nativeElement.querySelector('.po-input').offsetWidth - 40; + return this.el.nativeElement.querySelector('.po-multiselect-input').offsetWidth - poMultiselectInputPaddingRight; } getDisclaimersWidth() { - const disclaimers = this.el.nativeElement.querySelectorAll('po-disclaimer'); + const disclaimers = this.el.nativeElement.querySelectorAll('po-tag'); return Array.from(disclaimers).map(disclaimer => disclaimer['offsetWidth']); } calculateVisibleItems() { const disclaimersWidth = this.getDisclaimersWidth(); const inputWidth = this.getInputWidth(); - const extraDisclaimerSize = 38; + const extraDisclaimerSize = 44; const disclaimersVisible = disclaimersWidth[0]; this.visibleDisclaimers = []; @@ -203,7 +271,7 @@ export class PoMultiselectComponent let sum = 0; let i = 0; for (i = 0; i < this.selectedOptions.length; i++) { - sum += disclaimersWidth[i]; + sum += disclaimersWidth[i] + poMultiselectSpaceBetweenDisclaimers; this.visibleDisclaimers.push(this.selectedOptions[i]); if (sum > inputWidth) { @@ -271,14 +339,31 @@ export class PoMultiselectComponent } onKeyDown(event?: any) { - if (event.keyCode === PoKeyCodeEnum.arrowUp || event.keyCode === PoKeyCodeEnum.arrowDown) { + if ( + (event.keyCode === PoKeyCodeEnum.tab && this.visibleDisclaimers.length > 1) || + (event.keyCode === PoKeyCodeEnum.tab && this.visibleDisclaimers.length < 1) + ) { + return; + } + + if (event.keyCode === PoKeyCodeEnum.arrowDown) { event.preventDefault(); this.controlDropdownVisibility(true); + this.focusItem(); return; } - if (event.keyCode === PoKeyCodeEnum.tab) { - this.controlDropdownVisibility(false); + if (event.keyCode === PoKeyCodeEnum.enter) { + if (!this.isElementFocused) { + event.preventDefault(); + this.controlDropdownVisibility(true); + return; + } + } + + if (event.keyCode === PoKeyCodeEnum.space) { + event.preventDefault(); + this.controlDropdownVisibility(true); } } @@ -334,11 +419,21 @@ export class PoMultiselectComponent } closeDisclaimer(value) { - const index = this.selectedOptions.findIndex(option => option[this.fieldValue] === value); - this.selectedOptions.splice(index, 1); - - this.updateVisibleItems(); - this.callOnChange(this.selectedOptions); + // criar funcao que se value estiver vazio remover de visubleOptionsDropdown todos os que nao estao em visibleDisclaimers + if (!value) { + for (let option of this.visibleDisclaimers) { + if (!this.selectedOptions.includes(option)) { + this.selectedOptions.splice(this.selectedOptions.indexOf(option), 1); + this.updateVisibleItems(); + this.callOnChange(this.selectedOptions); + } + } + } else { + const index = this.selectedOptions.findIndex(option => option[this.fieldValue] === value); + this.selectedOptions.splice(index, 1); + this.updateVisibleItems(); + this.callOnChange(this.selectedOptions); + } } wasClickedOnToggle(event: MouseEvent): void { @@ -365,6 +460,20 @@ export class PoMultiselectComponent ); } + private focusItem() { + this.dropdown.listbox.listboxItemList.nativeElement.focus(); + setTimeout(() => { + let item; + if (this.selectedOptions) { + item = document.querySelector('.po-listbox-item[aria-selected="true"]'); + } else { + item = document.querySelectorAll('.po-listbox-item')[0] as HTMLElement; + } + this.dropdown.listbox.listboxItemList.nativeElement.focus(); + item?.focus(); + }); + } + private applyFilterInFirstClick() { if (this.isFirstFilter) { this.isServerSearching = true; diff --git a/projects/ui/src/lib/components/po-listbox/po-item-list/po-item-list.component.html b/projects/ui/src/lib/components/po-listbox/po-item-list/po-item-list.component.html index 403e8ffd51..06727c39f1 100644 --- a/projects/ui/src/lib/components/po-listbox/po-item-list/po-item-list.component.html +++ b/projects/ui/src/lib/components/po-listbox/po-item-list/po-item-list.component.html @@ -13,6 +13,7 @@ {{ label }}
+
+
{{ tagOrientation ? label + ':' : label }}
-
+
155; + return this.poTag.nativeElement.offsetWidth > 155; } setAriaLabel() { diff --git a/projects/ui/src/lib/enums/po-key-code.enum.ts b/projects/ui/src/lib/enums/po-key-code.enum.ts index ac81f42a7e..be21916969 100644 --- a/projects/ui/src/lib/enums/po-key-code.enum.ts +++ b/projects/ui/src/lib/enums/po-key-code.enum.ts @@ -34,5 +34,11 @@ export enum PoKeyCodeEnum { space = 32, /** Tab */ - tab = 9 + tab = 9, + + /** Tela Arrow Left */ + left = 37, + + /** Tela Arrow Right */ + right = 39 }