accessible-dialog.directive.ts 1.4 KB

123456789
  1. import { AfterViewInit, Directive, ElementRef, HostListener, inject, OnDestroy, output } from '@angular/core';
  2. @Directive({selector:'[appAccessibleDialog]',standalone:true})
  3. export class AccessibleDialogDirective implements AfterViewInit,OnDestroy {
  4. readonly dialogEscape=output<void>();private readonly element:HTMLElement=inject<ElementRef<HTMLElement>>(ElementRef).nativeElement;private readonly previous=document.activeElement as HTMLElement|null;
  5. private controls():HTMLElement[]{return Array.from(this.element.querySelectorAll<HTMLElement>('button:not([disabled]),a[href],input:not([disabled]),select:not([disabled]),textarea:not([disabled]),[tabindex="0"]')).filter(x=>x.getClientRects().length>0);}
  6. ngAfterViewInit():void {this.element.tabIndex=-1;queueMicrotask(()=>{if(this.element.isConnected)(this.controls()[0]||this.element).focus();});}
  7. @HostListener('keydown',['$event']) onKey(event:KeyboardEvent):void {if(event.key==='Escape'){event.preventDefault();event.stopPropagation();this.dialogEscape.emit();}if(event.key==='Tab'){const items=this.controls(),first=items[0],last=items[items.length-1];if(!first){event.preventDefault();this.element.focus();}else if(event.shiftKey&&(document.activeElement===first||document.activeElement===this.element)){event.preventDefault();last.focus();}else if(!event.shiftKey&&document.activeElement===last){event.preventDefault();first.focus();}}}
  8. ngOnDestroy():void{if(this.previous?.isConnected)this.previous.focus();}
  9. }