From e941d54e54cee74383fdb4af83c558c350a203b8 Mon Sep 17 00:00:00 2001 From: "tomas.kral" Date: Tue, 6 Dec 2016 12:34:33 +0100 Subject: [PATCH] Introduces: - close title - ARIA role dialog, modal-body & heading - trapping focus within the modal - returning focus to the opening element --- elements/modal/Modal.tsx | 87 ++++++++++++++++++++++++++---------- elements/modal/demo/Demo.tsx | 4 +- 2 files changed, 66 insertions(+), 25 deletions(-) diff --git a/elements/modal/Modal.tsx b/elements/modal/Modal.tsx index 43fd4e1..c149432 100644 --- a/elements/modal/Modal.tsx +++ b/elements/modal/Modal.tsx @@ -5,6 +5,7 @@ import { Card } from '../card/Card'; interface ModalProps { isOpen?: boolean, + closeTitle: string, onModalClose?: Function, } export class Modal extends Component { @@ -14,62 +15,102 @@ export class Modal extends Component { return { isOpen: prop.boolean({ attribute: true - }) + }), + closeTitle: prop.string() } } isOpen = false; + private closeTitle = "close"; private modalElement: HTMLDivElement; + private lastActiveElement: HTMLElement; + private handleEsc(evt:KeyboardEvent){ if ( evt.which === 27 ) { this.handleModalClose() } } private handleModalClose(){ - this.isOpen = !this.isOpen; + this.isOpen = false; emit(this,'modalClose') } + private focusModal() { + this.modalElement.focus(); + } + + private handleDocumentFocus(event: FocusEvent) { + if (this.modalElement && !this.modalElement.contains(event.target as Node)) { + event.stopImmediatePropagation(); + this.focusModal(); + } + } + + private preventModalBlur() { + document.addEventListener("focus", this.handleDocumentFocus.bind(this), true); + } + private allowModalBlur() { + document.removeEventListener("focus", this.handleDocumentFocus.bind(this), true); + } + connectedCallback(){ super.connectedCallback(); this.handleEsc = this.handleEsc.bind(this); this.handleModalClose = this.handleModalClose.bind(this); + this.focusModal = this.focusModal.bind(this); } renderCallback() { - const {isOpen} = this; + const {isOpen, closeTitle} = this; return [ , - isOpen &&
, + isOpen && +
, isOpen &&
this.modalElement=_ref} - tabIndex={0} + tabIndex={-1} class="o-modal" + role="dialog" + aria-labelledby="modal-heading" + aria-describedby="modal-body" onKeydown={this.handleEsc} > - - -
- -
-
- -
-
- -
-
+ + + + +
+ +
+
] } renderedCallback() { if ( this.isOpen ) { - this.modalElement.focus(); + this.lastActiveElement = document.activeElement as HTMLElement; + + this.focusModal(); + + this.preventModalBlur(); + } else { + this.allowModalBlur(); + + if (this.lastActiveElement) { + this.lastActiveElement.focus(); + } } } } -customElements.define( Modal.is, Modal ) \ No newline at end of file +customElements.define( Modal.is, Modal ) diff --git a/elements/modal/demo/Demo.tsx b/elements/modal/demo/Demo.tsx index b749715..cb865b8 100644 --- a/elements/modal/demo/Demo.tsx +++ b/elements/modal/demo/Demo.tsx @@ -28,8 +28,8 @@ export class Demo extends Component { return [ , , - - Modal heading + + Modal heading This is the modal body