在模态内捕获焦点,使 ADA 在 Angular 10 中兼容

Trapping focus inside modal making ADA compliant in Angular 10

我想办法使我现有的模式弹出窗口与 ADA 兼容,以便键盘 Tab 的焦点保留在这个使用用户定义的模式容器下 handleTabKeyFocus()。但是 querySelector 选择器总是 returns null with querySelectorAll 结果是

ERROR TypeError: Cannot read property 'querySelectorAll' of null

<!-- Modal Popup for Candidate Information -->
<ng-template #candidateInfoTemplate>
    <div class="modal-header" id="xc">
        <h5 class="modal-title float-left"> <b>Candidate Information </b></h5>
        <button type="button" class="close pull-right" aria-label="Close" (click)="closeModalWithFocus(candidateDetailTCInfo.examUserID)" title="CLOSE">
            <span aria-hidden="true">x</span>
        </button>
    </div>
    <!-- Modal body -->
    <div class="modal-body backgroundColorWhite" id="modalBody">
        <div class="row mb-1 backgroundColorgray" *ngIf="!disableColumn">
            <div class="col-sm-5">
                Candidate Id
            </div>
            <div class="col-sm-1">
                :
            </div>
            <div class="col-sm-6">
                {{candidateDetailTCInfo?.loginName}}
            </div>
        </div>
        <div class="clearfix"></div>
        <!-- Modal Footer-->
        <div style="text-align:center" ModFocus>
            <button title="{{ResourceKeys.sbclose}}" type="button" class="btn btn-light btn_mng1 mr5" aria-label="Close" (click)="closeModalWithFocus(candidateDetailTCInfo.examUserID)">
                {{ResourceKeys.sbclose}}
            </button>
        </div>
    </div>
</ng-template>

@HostListener('document:keydown', ['$event'])
handleTabKeyFocus(e) {
    if (e.keyCode === 9) {
        let focusable = document.querySelector('#candidateInfoTemplate').querySelectorAll('input,button,select,textarea,a,[tabindex]:not([tabindex="-1"])');
        // It returns null
        if (focusable.length) {
            let first = focusable[0];
            let last = focusable[focusable.length - 1];
            let shift = e.shiftKey;
            if (shift) {
                if (e.target === first) { // shift-tab pressed on first input in dialog
                    (last as HTMLElement).focus();
                    e.preventDefault();
                }
            } else {
                if (e.target === last) { // tab pressed on last input in dialog
                    (first as HTMLElement).focus();
                    e.preventDefault();
                }
            }
        }
    }
}

<div (click)="opencandidateDetailsModal(candidateInfoTemplate, detail)" >
    <a href="#" onclick="return false;" [attr.id]="'candidateNameFocus_' + detail.examUserID" > {{detail.candidateName}} </a>
</div>

opencandidateDetailsModal(template: TemplateRef<any>, candObj: any) {
    try {
        this.getCandidateDetailTestInfo(candObj);
        this.modalRef = this.modalService.show(template, this.modalPopupconfig);
    } catch (error) {
        console.log('Method: opencandidateDetailsModal', error);
    }
}

closeModalWithFocus(examUserID: any) {
    try {
        this.modalRef.hide();
        const candidateFocusElement: HTMLElement = (<HTMLElement>document.getElementById('candidateNameFocus_' + examUserID));
        if (candidateFocusElement !== null) {
            candidateFocusElement?.focus();
        }
        const previousActiveElement = document.activeElement;
        if (document.body.contains(previousActiveElement)) {
            (previousActiveElement as HTMLElement)?.focus();
        }
    } catch (error) {
        console.log('Method: closeModalWithFocus', error);
    }
}

内置 ngx-bootstrap 使用给定 id 的 ng-template 作为 candidateInfoTemplate

let focusable = document.querySelector('#candidateInfoTemplate').querySelectorAll('input,button,select,textarea,a,[tabindex]:not([tabindex="-1"])');

在打开模态弹出窗口之前发生键盘事件时抛出错误 Cannot read property 'querySelectorAll' of null

我们如何在 ngx-bootstrap 的模态弹出窗口中保持焦点?...

#candidateInfoTemplate 不是 id,它是对 <ng-template> 的引用。您可以使用 @ViewChild 装饰器访问它。

但是对于上述问题,您可以通过在带有 id

<ng-template> 标签内添加额外的 <div> 来解决这个问题
<ng-template #candidateInfoTemplate>
    <div id="candidateInfoTemplate">
    ...
    </div>
</ng-template>

通过指定包含在 <div> 标签中的 id 属性解决了

使用 ElementRef 涉及安全风险:允许直接访问 DOM 会使您的应用程序更容易受到 XSS 攻击。 所以使用 <HTMLElement>document.querySelector() 而不是 this.elementRef.nativeElement.querySelector()

some.html

<!-- Modal Popup for Candidate Information -->
<ng-template #candidateInfoTemplate>
    <div id="candidateInfoDialog" aria-modal="true">
    </div>
</ng-template>


<!-- Modal Popup for Confirmation -->
<ng-template #confirmationTemplate>
    <div id="confirmationDialog" aria-modal="true">
    </div>
</ng-template>

some.component.ts

import { BsModalRef, BsModalService } from 'ngx-bootstrap/modal';

@Component({
    providers: [BsModalService]
})

export class SomeComponent implements OnInit {
    modalRef: BsModalRef;
    config = {
        backdrop: true,
        ignoreBackdropClick: true,
        hasAllCheckBox: false,
        hasFilter: false,
        hasCollapseExpand: true,
        decoupleChildFromParent: true,
        maxHeight: 558
    };

    constructor( private modalService: BsModalService) { }

    @HostListener('document:keydown', ['$event'])
        handleAllModalDialogs(event: any) {
            this.handleTabKeyModal(event, '#candidateInfoDialog', 'input,button,select,textarea,a,[tabindex]:not([tabindex="-1"])');
            this.handleTabKeyModal(event, '#confirmationDialog', 'input,button,select,textarea,a,[tabindex]:not([tabindex="-1"])');
        }
    
    handleTabKeyModal(e, modelId: string, tagsList: string) {
        try {
            if (e.keyCode === 9) {
                const focusByID = <HTMLElement>document.querySelector(modelId);
                if(focusByID != null) {
                    const focusable = focusByID.querySelectorAll(tagsList);
                    if (focusable.length) {
                        let first = focusable[0];
                        let last = focusable[focusable.length - 1];
                        let shift = e.shiftKey;
                        if (shift) {
                            if (e.target === first) { // shift-tab pressed on first input in dialog
                                (last as HTMLElement)?.focus();
                                e.preventDefault();
                            }
                        } else {
                            if (e.target === last) { // tab pressed on last input in dialog
                                (first as HTMLElement)?.focus();
                                e.preventDefault();
                            }
                        }
                    }
                }
            }
        } catch (ex) {
            console.log('Method: handleTabKeyModal ' + ex.message)
        } 
    }
}