| // Copyright 2018 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| /** |
| * @fileoverview Listens for a find keyboard shortcut (i.e. Ctrl/Cmd+f) |
| * and keeps track of an stack of potential listeners. Only the listener at the |
| * top of the stack will be notified that a find shortcut has been invoked. |
| */ |
| |
| const FindShortcutManager = (() => { |
| /** |
| * Stack of listeners. Only the top listener will handle the shortcut. |
| * @type {!Array} |
| */ |
| const listeners = []; |
| |
| /** |
| * Tracks if any modal context is open in settings. This assumes only one |
| * modal can be open at a time. The modals that are being tracked include |
| * cr-dialog and cr-drawer. |
| * @type {boolean} |
| */ |
| let modalContextOpen = false; |
| |
| const shortcut = |
| new cr.ui.KeyboardShortcutList(cr.isMac ? 'meta|f' : 'ctrl|f'); |
| |
| window.addEventListener('keydown', e => { |
| if (e.defaultPrevented || listeners.length == 0 || |
| !shortcut.matchesEvent(e)) { |
| return; |
| } |
| |
| const focusIndex = |
| listeners.findIndex(listener => listener.searchInputHasFocus()); |
| // If no listener has focus or the first (outer-most) listener has focus, |
| // try the last (inner-most) listener. |
| // If a listener has a search input with focus, the next listener that |
| // should be called is the right before it in |listeners| such that the |
| // goes from inner-most to outer-most. |
| const index = focusIndex <= 0 ? listeners.length - 1 : focusIndex - 1; |
| if (listeners[index].handleFindShortcut(modalContextOpen)) { |
| e.preventDefault(); |
| } |
| }); |
| |
| window.addEventListener('cr-dialog-open', () => { |
| modalContextOpen = true; |
| }); |
| |
| window.addEventListener('cr-drawer-opened', () => { |
| modalContextOpen = true; |
| }); |
| |
| window.addEventListener('close', e => { |
| if (['CR-DIALOG', 'CR-DRAWER'].includes(e.composedPath()[0].nodeName)) { |
| modalContextOpen = false; |
| } |
| }); |
| |
| return Object.freeze({listeners: listeners}); |
| })(); |
| |
| /** |
| * Used to determine how to handle find shortcut invocations. |
| * @polymerBehavior |
| */ |
| const FindShortcutBehavior = { |
| /** |
| * @type {boolean} |
| * @protected |
| */ |
| findShortcutListenOnAttach: true, |
| |
| attached: function() { |
| if (this.findShortcutListenOnAttach) { |
| this.becomeActiveFindShortcutListener(); |
| } |
| }, |
| |
| detached: function() { |
| if (this.findShortcutListenOnAttach) { |
| this.removeSelfAsFindShortcutListener(); |
| } |
| }, |
| |
| becomeActiveFindShortcutListener: function() { |
| const listeners = FindShortcutManager.listeners; |
| assert(!listeners.includes(this), 'Already listening for find shortcuts.'); |
| listeners.push(this); |
| }, |
| |
| /** |
| * If handled, return true. |
| * @param {boolean} modalContextOpen |
| * @return {boolean} |
| */ |
| handleFindShortcut: function(modalContextOpen) { |
| assertNotReached(); |
| }, |
| |
| removeSelfAsFindShortcutListener: function() { |
| const listeners = FindShortcutManager.listeners; |
| const index = listeners.indexOf(this); |
| assert(listeners.includes(this), 'Find shortcut listener not found.'); |
| listeners.splice(index, 1); |
| }, |
| |
| /** @return {boolean} */ |
| searchInputHasFocus: function() { |
| assertNotReached(); |
| }, |
| }; |