Skip to content

Commit

Permalink
Handle Close Modal with Escape in IE and Edge
Browse files Browse the repository at this point in the history
  • Loading branch information
AronssonFredrik committed Dec 10, 2020
1 parent aa52124 commit b85bdde
Showing 1 changed file with 104 additions and 100 deletions.
204 changes: 104 additions & 100 deletions projects/komponentkartan/src/lib/controls/modal/modal.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,117 +8,121 @@ import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';

@Component({
selector: 'vgr-modal',
templateUrl: './modal.component.html',
styleUrls: ['./modal.component.scss']
selector: 'vgr-modal',
templateUrl: './modal.component.html',
styleUrls: ['./modal.component.scss']
})

export class ModalPlaceholderComponent implements AfterViewChecked, OnDestroy {
@Output() outsideClick = new EventEmitter();

isOpen: boolean;
elementId: string;
title: string;
modalInitialized: boolean;
lastFocusedElement: any;
firstTabStop: any;
lastTabStop: any;
focusableElements: any;
private ngUnsubscribe = new Subject();

// A list of elements that can recieve focus
focusableElementsString = 'a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), iframe, object, embed, [tabindex="0"], [contenteditable]';
@ContentChildren(forwardRef(() => ButtonComponent), { read: ElementRef, descendants: true }) buttonComponents: QueryList<ElementRef>;

constructor(
private modalService: ModalService, private elementRef: ElementRef, private renderer: Renderer2) {

this.isOpen = false;
this.modalService.modalOpened$
.pipe(takeUntil(this.ngUnsubscribe))
.subscribe(elementId => {
this.modalInitialized = false;
this.elementId = elementId;
this.openModal();
});

this.modalService.modalClosed$
.pipe(takeUntil(this.ngUnsubscribe))
.subscribe(elementId => {
this.elementId = elementId;
this.closeModal();
});

this.modalService.modalUpdate$
.pipe(takeUntil(this.ngUnsubscribe))
.subscribe(elementId => {
this.elementId = elementId;
this.initFocusableElements();
});
}
@Output() outsideClick = new EventEmitter();

isOpen: boolean;
elementId: string;
title: string;
modalInitialized: boolean;
lastFocusedElement: any;
firstTabStop: any;
lastTabStop: any;
focusableElements: any;
private ngUnsubscribe = new Subject();

// A list of elements that can recieve focus
focusableElementsString = 'a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), iframe, object, embed, [tabindex="0"], [contenteditable]';
@ContentChildren(forwardRef(() => ButtonComponent), { read: ElementRef, descendants: true }) buttonComponents: QueryList<ElementRef>;

constructor(
private modalService: ModalService, private elementRef: ElementRef, private renderer: Renderer2) {

this.isOpen = false;
this.modalService.modalOpened$
.pipe(takeUntil(this.ngUnsubscribe))
.subscribe(elementId => {
this.modalInitialized = false;
this.elementId = elementId;
this.openModal();
});

this.modalService.modalClosed$
.pipe(takeUntil(this.ngUnsubscribe))
.subscribe(elementId => {
this.elementId = elementId;
this.closeModal();
});

this.modalService.modalUpdate$
.pipe(takeUntil(this.ngUnsubscribe))
.subscribe(elementId => {
this.elementId = elementId;
this.initFocusableElements();
});
}

ngOnDestroy() {
this.ngUnsubscribe.next();
this.ngUnsubscribe.complete();
}
ngOnDestroy() {
this.ngUnsubscribe.next();
this.ngUnsubscribe.complete();
}

ngAfterViewChecked() {
if (!this.modalInitialized && this.buttonComponents && this.buttonComponents.length > 0) {
this.initFocusableElements();
this.modalInitialized = true;
}
ngAfterViewChecked() {
if (!this.modalInitialized && this.buttonComponents && this.buttonComponents.length > 0) {
this.initFocusableElements();
this.modalInitialized = true;
}
}

initFocusableElements() {
// Had to put this in a SetTimeout since the QuerySelector returned old objects from the last opened dialog otherwise
setTimeout(() => {
const focusableNodes: NodeList = this.elementRef.nativeElement.querySelectorAll(this.focusableElementsString);
if (focusableNodes.length === 0) {
return false;
}
this.focusableElements = Array.from(focusableNodes);

this.firstTabStop = this.focusableElements[0];
this.lastTabStop = this.focusableElements[this.focusableElements.length - 1];

// Set focus default button if one is defined
const defaultButtonComponent = this.buttonComponents && this.buttonComponents.find(x => x.nativeElement.getAttribute('default') === 'true');
if (defaultButtonComponent) {
const spanElement = defaultButtonComponent.nativeElement.children[0];
if (spanElement) {
// wait one lifecycle and set focus on the button element wrapped insde the span
Promise.resolve(null).then(() => {
spanElement.children[0].focus();
});
}
} else {
this.firstTabStop.focus();
}
}, 10);
}
initFocusableElements() {
// Had to put this in a SetTimeout since the QuerySelector returned old objects from the last opened dialog otherwise
setTimeout(() => {
const focusableNodes: NodeList = this.elementRef.nativeElement.querySelectorAll(this.focusableElementsString);
if (focusableNodes.length === 0) {
return false;
}
this.focusableElements = Array.from(focusableNodes);

this.firstTabStop = this.focusableElements[0];
this.lastTabStop = this.focusableElements[this.focusableElements.length - 1];

// Set focus default button if one is defined
const defaultButtonComponent = this.buttonComponents && this.buttonComponents.find(x => x.nativeElement.getAttribute('default') === 'true');
if (defaultButtonComponent) {
const spanElement = defaultButtonComponent.nativeElement.children[0];
if (spanElement) {
// wait one lifecycle and set focus on the button element wrapped insde the span
Promise.resolve(null).then(() => {
spanElement.children[0].focus();
});
}
} else {
this.firstTabStop.focus();
}
}, 10);
}

private openModal() {
this.isOpen = true;
this.renderer.addClass(document.querySelector(`#${this.elementId}`), 'vgr-modal--open');
this.renderer.addClass(document.body, 'modal--open');
}
private openModal() {
this.isOpen = true;
this.renderer.addClass(document.querySelector(`#${this.elementId}`), 'vgr-modal--open');
this.renderer.addClass(document.body, 'modal--open');
}

private closeModal() {
this.isOpen = false;
this.renderer.removeClass(document.querySelector(`#${this.elementId}`), 'vgr-modal--open');
this.renderer.removeClass(document.body, 'modal--open');
}
private closeModal() {
this.isOpen = false;
this.renderer.removeClass(document.querySelector(`#${this.elementId}`), 'vgr-modal--open');
this.renderer.removeClass(document.body, 'modal--open');
}

onKeyDown(e: KeyboardEvent) {
switch (e.key) {
case 'Tab':
this.handleTabPress(e);
break;
case 'Escape':
this.modalService.closeDialog(this.elementId);
break;
}
onKeyDown(e: KeyboardEvent) {
switch (e.key) {
case 'Tab':
this.handleTabPress(e);
break;
case 'Escape':
this.modalService.closeDialog(this.elementId);
break;
// Supporting IE and Edge
case 'Esc':
this.modalService.closeDialog(this.elementId);
break;
}
}

onOutsideClick(e: MouseEvent) {
// Is event bubbling; Ignore
Expand Down

0 comments on commit b85bdde

Please sign in to comment.