diff --git a/alfa-client/libs/design-system/src/lib/popup/popup-list-item/popup-list-item.component.ts b/alfa-client/libs/design-system/src/lib/popup/popup-list-item/popup-list-item.component.ts index 19593d3502fbc3ff3392d125c86e67893f809e4e..3bc88c4c30db6091ccf72a4e3658972fe4f117b6 100644 --- a/alfa-client/libs/design-system/src/lib/popup/popup-list-item/popup-list-item.component.ts +++ b/alfa-client/libs/design-system/src/lib/popup/popup-list-item/popup-list-item.component.ts @@ -6,7 +6,7 @@ import { Component, EventEmitter, Input, Output } from '@angular/core'; standalone: true, imports: [CommonModule], template: `<button - class="flex min-h-12 w-full items-center gap-4 border-2 border-transparent px-4 py-3 text-start outline-none hover:border-primary focus:border-focus" + class="flex min-h-12 w-full items-center gap-4 border-2 border-transparent bg-whitetext px-4 py-3 text-start outline-none hover:border-primary focus-visible:border-focus" role="listitem" (click)="itemClicked.emit()" > diff --git a/alfa-client/libs/design-system/src/lib/popup/popup/popup.component.spec.ts b/alfa-client/libs/design-system/src/lib/popup/popup/popup.component.spec.ts index f2a64dec5c9d60e36c292b55c6ae4a1b8d414f16..8d03abd239d9a8054e0efed06259e41bffb8d516 100644 --- a/alfa-client/libs/design-system/src/lib/popup/popup/popup.component.spec.ts +++ b/alfa-client/libs/design-system/src/lib/popup/popup/popup.component.spec.ts @@ -1,6 +1,5 @@ import { getElementFromFixture } from '@alfa-client/test-utils'; -import { ElementRef } from '@angular/core'; -import { ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; import { getDataTestIdOf } from 'libs/tech-shared/test/data-test'; import { PopupComponent } from './popup.component'; @@ -23,27 +22,27 @@ describe('PopupComponent', () => { expect(component).toBeTruthy(); }); - describe('toggleShowPopupList', () => { + describe('togglePopup', () => { it('should change false to true', () => { - component.showPopupList = false; + component.isPopupOpen = false; - component.toggleShowPopupList(); + component.togglePopup(); - expect(component.showPopupList).toBe(true); + expect(component.isPopupOpen).toBe(true); }); it('should change true to false', () => { - component.showPopupList = true; + component.isPopupOpen = true; - component.toggleShowPopupList(); + component.togglePopup(); - expect(component.showPopupList).toBe(false); + expect(component.isPopupOpen).toBe(false); }); }); describe('aria-expanded', () => { - it('should be true if popup is opened', () => { - component.showPopupList = true; + it('should be true if popup is open', () => { + component.isPopupOpen = true; fixture.detectChanges(); const buttonElement: HTMLElement = getElementFromFixture(fixture, popupButton); @@ -52,7 +51,7 @@ describe('PopupComponent', () => { }); it('should be false if popup is closed', () => { - component.showPopupList = false; + component.isPopupOpen = false; fixture.detectChanges(); const buttonElement: HTMLElement = getElementFromFixture(fixture, popupButton); @@ -61,81 +60,6 @@ describe('PopupComponent', () => { }); }); - describe('toggleClicked', () => { - beforeEach(async () => { - component.showPopupList = true; - fixture.detectChanges(); - await fixture.whenStable(); - jest.spyOn(component.popupListRef.nativeElement, 'focus'); - }); - - it('should toggle popup list', () => { - component.toggleShowPopupList = jest.fn(); - - component.toggleClicked(); - - expect(component.toggleShowPopupList).toHaveBeenCalled(); - }); - - it('should focus popup list if it is shown', fakeAsync(() => { - component.showPopupList = false; - - component.toggleClicked(); - tick(); - - expect(component.popupListRef.nativeElement.focus).toHaveBeenCalled(); - })); - - it('should not focus popup list if it is hidden', fakeAsync(() => { - component.showPopupList = true; - - component.toggleClicked(); - tick(); - - expect(component.popupListRef.nativeElement.focus).not.toHaveBeenCalled(); - })); - }); - - describe('isTabKey', () => { - it('should return true', () => { - const tabKeyEvent: KeyboardEvent = { ...new KeyboardEvent('tab'), key: 'Tab' }; - - const result: boolean = component.isTabKey(tabKeyEvent); - - expect(result).toBe(true); - }); - - it('should return false', () => { - const keyEvent: KeyboardEvent = new KeyboardEvent('whatever'); - - const result: boolean = component.isTabKey(keyEvent); - - expect(result).toBe(false); - }); - }); - - describe('isShiftTabKey', () => { - it('should return true', () => { - const shiftTabKeyEvent: KeyboardEvent = { - ...new KeyboardEvent('tab'), - key: 'Tab', - shiftKey: true, - }; - - const result: boolean = component.isShiftTabKey(shiftTabKeyEvent); - - expect(result).toBe(true); - }); - - it('should return false', () => { - const tabKeyEvent: KeyboardEvent = { ...new KeyboardEvent('tab'), key: 'Tab' }; - - const result: boolean = component.isShiftTabKey(tabKeyEvent); - - expect(result).toBe(false); - }); - }); - describe('isEscapeKey', () => { it('should return true', () => { const escapeKeyEvent: KeyboardEvent = { @@ -157,430 +81,131 @@ describe('PopupComponent', () => { }); }); - describe('hidePopupListAndFocusButton', () => { + describe('closePopupAndFocusButton', () => { beforeEach(() => { - jest.spyOn(component.popupButtonRef.nativeElement, 'focus'); + component.isPopupOpen = true; + jest.spyOn(component.buttonRef.nativeElement, 'focus'); }); - it('should hide popup list', () => { - component.showPopupList = true; + it('should close popup', () => { + component.closePopupAndFocusButton(); - component.hidePopupListAndFocusButton(); - - expect(component.showPopupList).toBe(false); + expect(component.isPopupOpen).toBe(false); }); it('should focus button', () => { - component.showPopupList = true; - - component.hidePopupListAndFocusButton(); + component.closePopupAndFocusButton(); - expect(component.popupButtonRef.nativeElement.focus).toHaveBeenCalled(); + expect(component.buttonRef.nativeElement.focus).toHaveBeenCalled(); }); }); - describe('isPopupListHidden', () => { + describe('isPopupClosed', () => { it('should return true', () => { - component.showPopupList = false; + component.isPopupOpen = false; - const result: boolean = component.isPopupListHidden(); + const result: boolean = component.isPopupClosed(); expect(result).toBe(true); }); it('should return false', () => { - component.showPopupList = true; - - const result: boolean = component.isPopupListHidden(); - - expect(result).toBe(false); - }); - }); - - describe('isFirstItemFocused', () => { - const document: Document = new Document(); - const firstElement: HTMLElement = document.createElement('a'); - const lastElement: HTMLElement = document.createElement('b'); - const element: HTMLElement = document.createElement('div'); - element.appendChild(firstElement); - element.appendChild(lastElement); - const popupList: ElementRef<HTMLElement> = { - nativeElement: element, - }; - - it('should return true', () => { - const documentWithFirstFocused = { ...document, activeElement: firstElement }; - - const result: boolean = component.isFirstItemFocused(popupList, documentWithFirstFocused); - - expect(result).toBe(true); - }); - - it('should return false if popup list is undefined', () => { - const documentWithNoFocused: Document = new Document(); - - const result: boolean = component.isFirstItemFocused(undefined, documentWithNoFocused); - - expect(result).toBe(false); - }); - - it('should return false if first element is not focused', () => { - const documentWithLastFocused = { ...document, activeElement: lastElement }; - - const result: boolean = component.isFirstItemFocused(popupList, documentWithLastFocused); - - expect(result).toBe(false); - }); - }); - - describe('isLastItemFocused', () => { - const document: Document = new Document(); - const firstElement: HTMLElement = document.createElement('a'); - const lastElement: HTMLElement = document.createElement('b'); - const element: HTMLElement = document.createElement('div'); - element.appendChild(firstElement); - element.appendChild(lastElement); - const popupList: ElementRef<HTMLElement> = { - nativeElement: element, - }; - - it('should return true', () => { - const documentWithLastFocused = { ...document, activeElement: lastElement }; - - const result: boolean = component.isLastItemFocused(popupList, documentWithLastFocused); - - expect(result).toBe(true); - }); - - it('should return false if popup list is undefined', () => { - const documentWithNoFocused: Document = new Document(); - - const result: boolean = component.isLastItemFocused(undefined, documentWithNoFocused); - - expect(result).toBe(false); - }); - - it('should return false if last element is not focused', () => { - const documentWithFirstFocused = { ...document, activeElement: firstElement }; - - const result: boolean = component.isLastItemFocused(popupList, documentWithFirstFocused); - - expect(result).toBe(false); - }); - }); - - describe('isPopupListFocused', () => { - let document: Document = new Document(); - const element: HTMLElement = document.createElement('div'); - const popupList: ElementRef<HTMLElement> = { - nativeElement: element, - }; - - it('should return true', () => { - const documentWithListFocused = { ...document, activeElement: element }; - - const result: boolean = component.isPopupListFocused(popupList, documentWithListFocused); - - expect(result).toBe(true); - }); - - it('should return false if popup list is undefined', () => { - const documentWithNoFocused: Document = new Document(); - - const result: boolean = component.isPopupListFocused(undefined, documentWithNoFocused); - - expect(result).toBe(false); - }); - - it('should return false if popup list is not focused', () => { - const documentWithNoFocused: Document = new Document(); + component.isPopupOpen = true; - const result: boolean = component.isPopupListFocused(popupList, documentWithNoFocused); + const result: boolean = component.isPopupClosed(); expect(result).toBe(false); }); }); - describe('focusFirstItem', () => { - const document: Document = new Document(); - const listElement: HTMLElement = document.createElement('ul'); - - const firstElement: HTMLElement = document.createElement('a'); - const lastElement: HTMLElement = document.createElement('b'); - const buttonElement: HTMLElement = document.createElement('button'); - buttonElement.focus = jest.fn(); - firstElement.appendChild(buttonElement); - - listElement.appendChild(firstElement); - listElement.appendChild(lastElement); - const popupList: ElementRef<HTMLElement> = { - nativeElement: listElement, - }; - - it('should get focusable element', () => { - component.getFocusableElement = jest.fn(); - - component.focusFirstItem(popupList); - - expect(component.getFocusableElement).toHaveBeenCalled(); - }); - - it('should focus first child', fakeAsync(() => { - component.focusFirstItem(popupList); - tick(); - - expect(buttonElement.focus).toHaveBeenCalled(); - })); - }); - - describe('focusLastItem', () => { - const document: Document = new Document(); - const listElement: HTMLElement = document.createElement('ul'); - - const firstElement: HTMLElement = document.createElement('a'); - const lastElement: HTMLElement = document.createElement('b'); - const buttonElement: HTMLElement = document.createElement('button'); - buttonElement.focus = jest.fn(); - lastElement.appendChild(buttonElement); - - listElement.appendChild(firstElement); - listElement.appendChild(lastElement); - const popupList: ElementRef<HTMLElement> = { - nativeElement: listElement, - }; - - it('should get focusable element', () => { - component.getFocusableElement = jest.fn(); - - component.focusLastItem(popupList); - - expect(component.getFocusableElement).toHaveBeenCalled(); - }); - - it('should focus first child', fakeAsync(() => { - component.focusLastItem(popupList); - tick(); - - expect(buttonElement.focus).toHaveBeenCalled(); - })); - }); - - describe('shouldFocusFirstItem', () => { - const keyboardEvent = new KeyboardEvent('e'); - - it('should return true if is tab key and last item is focused', () => { - component.isTabKey = jest.fn().mockReturnValue(true); - component.isLastItemFocused = jest.fn().mockReturnValue(true); - - const result: boolean = component.shouldFocusFirstItem(keyboardEvent); - - expect(result).toBe(true); - }); - - it('should return false', () => { - const result: boolean = component.shouldFocusFirstItem(keyboardEvent); + describe('onKeydownHandler', () => { + const e: KeyboardEvent = new KeyboardEvent('test'); - expect(result).toBe(false); + beforeEach(() => { + component.closePopupAndFocusButton = jest.fn(); + component.isEscapeKey = jest.fn(); }); - }); - - describe('shouldFocusLastItem', () => { - const keyboardEvent = new KeyboardEvent('e'); - it('should return true if is shift tab key and first item is focused', () => { - component.isShiftTabKey = jest.fn().mockReturnValue(true); - component.isFirstItemFocused = jest.fn().mockReturnValue(true); + describe('popup is closed', () => { + beforeEach(() => { + component.isPopupClosed = jest.fn().mockReturnValue(true); + }); - const result: boolean = component.shouldFocusLastItem(keyboardEvent); + it('should not check for escape key', () => { + component.onKeydownHandler(e); - expect(result).toBe(true); + expect(component.isEscapeKey).not.toHaveBeenCalled(); + }); }); - it('should return true if is shift tab key and popup list is focused', () => { - component.isShiftTabKey = jest.fn().mockReturnValue(true); - component.isPopupListFocused = jest.fn().mockReturnValue(true); + describe('popup is open', () => { + beforeEach(() => { + component.isPopupClosed = jest.fn().mockReturnValue(false); + }); - const result: boolean = component.shouldFocusLastItem(keyboardEvent); + it('should check for escape key', () => { + component.onKeydownHandler(e); - expect(result).toBe(true); - }); + expect(component.isEscapeKey).toHaveBeenCalled(); + }); - it('should return false', () => { - const result: boolean = component.shouldFocusLastItem(keyboardEvent); + it('should handle escape key', () => { + component.isEscapeKey = jest.fn().mockReturnValue(true); - expect(result).toBe(false); - }); - }); + component.onKeydownHandler(e); - describe('getFocusableElement', () => { - const document: Document = new Document(); - const element: HTMLElement = document.createElement('div'); - const buttonElement = document.createElement('button'); - element.appendChild(buttonElement); + expect(component.closePopupAndFocusButton).toHaveBeenCalled(); + }); - it('should return focusable element', () => { - const result: HTMLElement = component.getFocusableElement(element); + it('should not handle escape key', () => { + component.onKeydownHandler(e); - expect(result).toBe(buttonElement); + expect(component.closePopupAndFocusButton).not.toHaveBeenCalled(); + }); }); }); - describe('onClickHandler', () => { const e: MouseEvent = new MouseEvent('test'); beforeEach(() => { - component.hidePopupListAndFocusButton = jest.fn(); - }); - - it('should not handle click if popup is hidden', () => { - component.isPopupListHidden = jest.fn().mockReturnValue(true); - - component.onClickHandler(e); - - expect(component.hidePopupListAndFocusButton).not.toHaveBeenCalled(); - }); - - it('should hide popup and focus button if clicked outside of button', () => { - component.isPopupListHidden = jest.fn().mockReturnValue(false); - - component.onClickHandler(e); - - expect(component.hidePopupListAndFocusButton).toHaveBeenCalled(); - }); - - it('should not handle click if clicked inside of button', () => { - component.isPopupListHidden = jest.fn().mockReturnValue(false); - component.popupButtonRef.nativeElement.contains = jest.fn().mockReturnValue(true); - - component.onClickHandler(e); - - expect(component.hidePopupListAndFocusButton).not.toHaveBeenCalled(); - }); - }); - - describe('onKeydownHandler', () => { - const keyboardEvent: KeyboardEvent = new KeyboardEvent('e'); - - beforeEach(() => { - component.isPopupListHidden = jest.fn(); - component.isTabKey = jest.fn(); - component.isShiftTabKey = jest.fn(); - component.isEscapeKey = jest.fn(); - component.isFirstItemFocused = jest.fn(); - component.isLastItemFocused = jest.fn(); - component.focusFirstItem = jest.fn(); - component.focusLastItem = jest.fn(); - component.isPopupListFocused = jest.fn(); - component.shouldFocusFirstItem = jest.fn(); - component.shouldFocusLastItem = jest.fn(); - component.hidePopupListAndFocusButton = jest.fn(); - }); - - it('should check for hidden popup list', () => { - component.onKeydownHandler(keyboardEvent); - - expect(component.isPopupListHidden).toHaveBeenCalled(); + component.closePopupAndFocusButton = jest.fn(); + component.buttonRef.nativeElement.contains = jest.fn(); }); - describe('popup list is hidden', () => { + describe('popup is closed', () => { beforeEach(() => { - component.isPopupListHidden = jest.fn().mockReturnValue(true); - }); - - it('should not focus first item', () => { - component.onKeydownHandler(keyboardEvent); - - expect(component.focusFirstItem).not.toHaveBeenCalled(); - }); - - it('should not focus last item', () => { - component.onKeydownHandler(keyboardEvent); - - expect(component.focusLastItem).not.toHaveBeenCalled(); + component.isPopupClosed = jest.fn().mockReturnValue(true); }); - it('should ignore escape key handling', () => { - component.onKeydownHandler(keyboardEvent); + it('should not check for button containing event target', () => { + component.onClickHandler(e); - expect(component.hidePopupListAndFocusButton).not.toHaveBeenCalled(); + expect(component.buttonRef.nativeElement.contains).not.toHaveBeenCalled(); }); }); - describe('popup list is visible', () => { + describe('popup is open', () => { beforeEach(() => { - component.isPopupListHidden = jest.fn().mockReturnValue(false); - }); - - it('should check if should focus first item', () => { - component.onKeydownHandler(keyboardEvent); - - expect(component.shouldFocusFirstItem).toHaveBeenCalled(); + component.isPopupClosed = jest.fn().mockReturnValue(false); }); - it('should focus first item', () => { - component.shouldFocusFirstItem = jest.fn().mockReturnValue(true); - - component.onKeydownHandler(keyboardEvent); + it('should check for button containing event target', () => { + component.onClickHandler(e); - expect(component.focusFirstItem).toHaveBeenCalled(); + expect(component.buttonRef.nativeElement.contains).toHaveBeenCalled(); }); - it('should not focus first item', () => { - component.onKeydownHandler(keyboardEvent); + it('should handle click', () => { + component.onClickHandler(e); - expect(component.focusFirstItem).not.toHaveBeenCalled(); + expect(component.closePopupAndFocusButton).toHaveBeenCalled(); }); - describe('should not focus first item', () => { - beforeEach(() => { - component.shouldFocusFirstItem = jest.fn().mockReturnValue(false); - }); - - it('should check if should focus last item', () => { - component.onKeydownHandler(keyboardEvent); - - expect(component.shouldFocusLastItem).toHaveBeenCalled(); - }); - - it('should focus last item', () => { - component.shouldFocusLastItem = jest.fn().mockReturnValue(true); - - component.onKeydownHandler(keyboardEvent); - - expect(component.focusLastItem).toHaveBeenCalled(); - }); - - it('should not focus last item', () => { - component.onKeydownHandler(keyboardEvent); - - expect(component.focusLastItem).not.toHaveBeenCalled(); - }); - - describe('should not focus last item', () => { - beforeEach(() => { - component.shouldFocusFirstItem = jest.fn().mockReturnValue(false); - component.shouldFocusLastItem = jest.fn().mockReturnValue(false); - }); - - it('should check for escape key', () => { - component.onKeydownHandler(keyboardEvent); - - expect(component.isEscapeKey).toHaveBeenCalled(); - }); - - it('should hide popup list', () => { - component.isEscapeKey = jest.fn().mockReturnValue(true); - - component.onKeydownHandler(keyboardEvent); - - expect(component.hidePopupListAndFocusButton).toHaveBeenCalled(); - }); + it('should not handle click', () => { + component.buttonRef.nativeElement.contains = jest.fn().mockReturnValue(true); - it('should not hide popup list', () => { - component.onKeydownHandler(keyboardEvent); + component.onClickHandler(e); - expect(component.hidePopupListAndFocusButton).not.toHaveBeenCalled(); - }); - }); + expect(component.closePopupAndFocusButton).not.toHaveBeenCalled(); }); }); }); diff --git a/alfa-client/libs/design-system/src/lib/popup/popup/popup.component.ts b/alfa-client/libs/design-system/src/lib/popup/popup/popup.component.ts index da4cb9da07265f7e0b37d5f5d5efa7b3b2def16e..a4901d657b0d4d7246dcff422553a17fa60a31df 100644 --- a/alfa-client/libs/design-system/src/lib/popup/popup/popup.component.ts +++ b/alfa-client/libs/design-system/src/lib/popup/popup/popup.component.ts @@ -1,33 +1,33 @@ +import { CdkTrapFocus } from '@angular/cdk/a11y'; import { CommonModule } from '@angular/common'; import { Component, ElementRef, HostListener, Input, ViewChild } from '@angular/core'; -import { first, last } from 'lodash-es'; import { twMerge } from 'tailwind-merge'; @Component({ selector: 'ods-popup', standalone: true, - imports: [CommonModule], + imports: [CommonModule, CdkTrapFocus], template: `<div class="relative w-fit"> <button class="w-fit outline-2 outline-offset-2 outline-focus" [ngClass]="[twMerge('w-fit outline-2 outline-offset-2 outline-focus', buttonClass)]" - (click)="toggleClicked()" - [attr.aria-expanded]="showPopupList" + (click)="togglePopup()" + [attr.aria-expanded]="isPopupOpen" aria-haspopup="true" [attr.aria-label]="label" data-test-id="popup-button" - #popupButton + #button > <ng-content select="[button]" /> </button> <ul - *ngIf="showPopupList" - class="absolute max-h-120 min-w-44 max-w-80 animate-fadeIn overflow-y-auto rounded shadow-lg shadow-grayborder focus:outline-none" + *ngIf="isPopupOpen" + class="max-h-120 animate-fadeIn absolute min-w-44 max-w-80 overflow-y-auto rounded shadow-lg shadow-grayborder focus:outline-none" [ngClass]="alignTo === 'left' ? 'right-0' : 'left-0'" role="dialog" aria-modal="true" - tabindex="0" - #popupList + cdkTrapFocus + [cdkTrapFocusAutoCapture]="true" > <ng-content /> </ul> @@ -38,102 +38,39 @@ export class PopupComponent { @Input() label: string = ''; @Input() buttonClass: string = ''; - constructor(public ref: ElementRef) {} - - showPopupList: boolean = false; + isPopupOpen: boolean = false; twMerge = twMerge; - @ViewChild('popupList') popupListRef: ElementRef<HTMLUListElement>; - @ViewChild('popupButton') popupButtonRef: ElementRef<HTMLButtonElement>; + @ViewChild('button') buttonRef: ElementRef<HTMLButtonElement>; @HostListener('document:keydown', ['$event']) onKeydownHandler(e: KeyboardEvent): void { - if (this.isPopupListHidden()) return; - if (this.shouldFocusFirstItem(e)) this.focusFirstItem(this.popupListRef); - if (this.shouldFocusLastItem(e)) this.focusLastItem(this.popupListRef); - if (this.isEscapeKey(e)) this.hidePopupListAndFocusButton(); + if (this.isPopupClosed()) return; + if (this.isEscapeKey(e)) this.closePopupAndFocusButton(); } @HostListener('document:click', ['$event']) onClickHandler(e: MouseEvent): void { - if (this.isPopupListHidden()) return; - if (!this.popupButtonRef.nativeElement.contains(e.target as HTMLElement)) { - this.hidePopupListAndFocusButton(); - } - } - - toggleShowPopupList(): void { - this.showPopupList = !this.showPopupList; - } - - hidePopupListAndFocusButton(): void { - this.showPopupList = false; - this.popupButtonRef.nativeElement.focus(); - } - - toggleClicked(): void { - this.toggleShowPopupList(); - if (this.showPopupList) { - setTimeout(() => this.popupListRef.nativeElement.focus()); + if (this.isPopupClosed()) return; + if (!this.buttonRef.nativeElement.contains(e.target as HTMLElement)) { + this.closePopupAndFocusButton(); } } - isTabKey(e: KeyboardEvent): boolean { - return e.key === 'Tab' && !e.shiftKey; + togglePopup(): void { + this.isPopupOpen = !this.isPopupOpen; } - isShiftTabKey(e: KeyboardEvent): boolean { - return e.key === 'Tab' && Boolean(e.shiftKey); + closePopupAndFocusButton(): void { + this.isPopupOpen = false; + this.buttonRef.nativeElement.focus(); } isEscapeKey(e: KeyboardEvent): boolean { return e.key === 'Escape'; } - isFirstItemFocused(popupList: ElementRef<HTMLElement>, document: Document): boolean { - if (!popupList) return false; - return popupList.nativeElement.firstChild.contains(document.activeElement); - } - - isPopupListFocused(popupList: ElementRef<HTMLElement>, document: Document): boolean { - if (!popupList) return false; - return popupList.nativeElement.isSameNode(document.activeElement); - } - - isLastItemFocused(popupList: ElementRef<HTMLElement>, document: Document): boolean { - if (!popupList) return false; - return popupList.nativeElement.lastChild.contains(document.activeElement); - } - - isPopupListHidden(): boolean { - return !this.showPopupList; - } - - focusFirstItem(popupList: ElementRef<HTMLElement>): void { - const firstItem: HTMLElement = this.getFocusableElement( - first(popupList.nativeElement.children), - ); - setTimeout(() => firstItem.focus()); - } - - focusLastItem(popupList: ElementRef<HTMLElement>): void { - const lastItem: HTMLElement = this.getFocusableElement(last(popupList.nativeElement.children)); - setTimeout(() => lastItem.focus()); - } - - shouldFocusFirstItem(e: KeyboardEvent): boolean { - return this.isTabKey(e) && this.isLastItemFocused(this.popupListRef, document); - } - - shouldFocusLastItem(e: KeyboardEvent): boolean { - return ( - this.isShiftTabKey(e) && - (this.isFirstItemFocused(this.popupListRef, document) || - this.isPopupListFocused(this.popupListRef, document)) - ); - } - - getFocusableElement(element: Element): HTMLElement { - return first(element.querySelectorAll('a[href], button, [tabindex]')) as HTMLElement; + isPopupClosed(): boolean { + return !this.isPopupOpen; } }