| // Copyright 2017 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import 'chrome://resources/cr_elements/cr_dialog/cr_dialog.js'; |
| |
| import {CrDialogElement} from 'chrome://resources/cr_elements/cr_dialog/cr_dialog.js'; |
| import {assert} from 'chrome://resources/js/assert_ts.js'; |
| |
| /** |
| * Manages focus restoration for modal dialogs. After the final dialog in a |
| * stack is closed, restores focus to the element which was focused when the |
| * first dialog was opened. |
| */ |
| export class DialogFocusManager { |
| private previousFocusElement_: HTMLElement|null = null; |
| private dialogs_: Set<HTMLDialogElement|CrDialogElement> = new Set(); |
| |
| showDialog(dialog: (HTMLDialogElement|CrDialogElement), showFn?: () => void) { |
| if (!showFn) { |
| showFn = function() { |
| dialog.showModal(); |
| }; |
| } |
| |
| // Update the focus if there are no open dialogs or if this is the only |
| // dialog and it's getting reshown. |
| if (!this.dialogs_.size || |
| (this.dialogs_.has(dialog) && this.dialogs_.size === 1)) { |
| this.updatePreviousFocus_(); |
| } |
| |
| if (!this.dialogs_.has(dialog)) { |
| dialog.addEventListener('close', this.getCloseListener_(dialog)); |
| this.dialogs_.add(dialog); |
| } |
| |
| showFn(); |
| } |
| |
| /** |
| * @return True if the document currently has an open dialog. |
| */ |
| hasOpenDialog(): boolean { |
| return this.dialogs_.size > 0; |
| } |
| |
| /** |
| * Clears the stored focus element, so that focus does not restore when all |
| * dialogs are closed. |
| */ |
| clearFocus() { |
| this.previousFocusElement_ = null; |
| } |
| |
| private updatePreviousFocus_() { |
| this.previousFocusElement_ = this.getFocusedElement_(); |
| } |
| |
| private getFocusedElement_(): HTMLElement { |
| let focus = document.activeElement as HTMLElement; |
| while (focus.shadowRoot && focus.shadowRoot!.activeElement) { |
| focus = focus.shadowRoot!.activeElement as HTMLElement; |
| } |
| |
| return focus; |
| } |
| |
| private getCloseListener_(dialog: (HTMLDialogElement|CrDialogElement)): |
| ((p1: Event) => void) { |
| const closeListener = (_e: Event) => { |
| // If the dialog is open, then it got reshown immediately and we |
| // shouldn't clear it until it is closed again. |
| if (dialog.open) { |
| return; |
| } |
| |
| assert(this.dialogs_.delete(dialog)); |
| // Focus the originally focused element if there are no more dialogs. |
| if (!this.hasOpenDialog() && this.previousFocusElement_) { |
| this.previousFocusElement_.focus(); |
| } |
| |
| dialog.removeEventListener('close', closeListener); |
| }; |
| |
| return closeListener; |
| } |
| |
| static getInstance(): DialogFocusManager { |
| return instance || (instance = new DialogFocusManager()); |
| } |
| |
| static setInstance(obj: DialogFocusManager|null) { |
| instance = obj; |
| } |
| } |
| |
| let instance: DialogFocusManager|null = null; |