From d83d14796ca29a6089fd8d91e9344273516a3b5e Mon Sep 17 00:00:00 2001 From: Blackbaud-PatrickOFriel Date: Tue, 18 Jul 2017 12:12:34 -0400 Subject: [PATCH 1/9] Handle tabbing and escape press in modals --- .../modal/modal-component-adapter.service.ts | 61 +++++++++++++++++++ src/modules/modal/modal-host.service.ts | 4 ++ src/modules/modal/modal.component.html | 3 + src/modules/modal/modal.component.ts | 46 +++++++++++++- 4 files changed, 112 insertions(+), 2 deletions(-) diff --git a/src/modules/modal/modal-component-adapter.service.ts b/src/modules/modal/modal-component-adapter.service.ts index fc39fa6f5..8f7eb4440 100644 --- a/src/modules/modal/modal-component-adapter.service.ts +++ b/src/modules/modal/modal-component-adapter.service.ts @@ -2,6 +2,11 @@ import { Injectable, ElementRef } from '@angular/core'; +/* tslint:disable */ +let tabbableSelector = 'a[href], area[href], input:not([disabled]):not([tabindex=\'-1\']), ' + + 'button:not([disabled]):not([tabindex=\'-1\']),select:not([disabled]):not([tabindex=\'-1\']), textarea:not([disabled]):not([tabindex=\'-1\']), ' + + 'iframe, object, embed, *[tabindex]:not([tabindex=\'-1\']), *[contenteditable=true]'; +/* tslint:enable */ @Injectable() export class SkyModalComponentAdapterService { @@ -31,4 +36,60 @@ export class SkyModalComponentAdapterService { } } + + public loadFocusElementList(modalEl: ElementRef): Array { + let elements: Array + = Array.prototype.slice.call(modalEl.nativeElement.querySelectorAll(tabbableSelector)); + + return elements ? elements.filter((element) => { + return this.isVisible(element); + }): elements; + } + + public isFocusInFirstItem(event: KeyboardEvent, list: Array): boolean { + return list.length > 0 && (event.target || event.srcElement) === list[0]; + } + + public isFocusInLastItem(event: KeyboardEvent, list: Array): boolean { + return list.length > 0 && (event.target || event.srcElement) === list[list.length -1]; + } + + public focusLastElement(list: Array): boolean { + if (list.length > 0) { + list[list.length - 1].focus(); + return true; + } + return false; + } + + public focusFirstElement(list: Array): boolean { + if (list.length > 0) { + list[0].focus(); + return true; + } + return false; + } + + public modalOpened(modalEl: ElementRef): void { + // debugger; + if (!(document.activeElement && modalEl.nativeElement.contains(document.activeElement))) { + let inputWithAutofocus = modalEl.nativeElement.querySelector('[autofocus]'); + + if (inputWithAutofocus) { + inputWithAutofocus.focus(); + } else { + // setTimeout(() => { + let focusEl: HTMLElement = modalEl.nativeElement.querySelector('.sky-modal-dialog'); + focusEl.focus(); + // }, 1000); + + } + } + } + + private isVisible(element: HTMLElement) { + return !!(element.offsetWidth || + element.offsetHeight || + element.getClientRects().length); + } } diff --git a/src/modules/modal/modal-host.service.ts b/src/modules/modal/modal-host.service.ts index 715ec0a94..0f4544feb 100644 --- a/src/modules/modal/modal-host.service.ts +++ b/src/modules/modal/modal-host.service.ts @@ -19,6 +19,10 @@ export class SkyModalHostService { return SkyModalHostService.BASE_Z_INDEX + SkyModalHostService.modalHosts.length * 10; } + public static get topModal(): SkyModalHostService { + return SkyModalHostService.modalHosts[SkyModalHostService.modalHosts.length - 1]; + } + private static modalHosts: SkyModalHostService[] = []; public close = new EventEmitter(); diff --git a/src/modules/modal/modal.component.html b/src/modules/modal/modal.component.html index bf86f716a..08e3bc8f0 100644 --- a/src/modules/modal/modal.component.html +++ b/src/modules/modal/modal.component.html @@ -5,6 +5,9 @@