| /** |
| * @license |
| * Copyright 2021 Google LLC |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| import {ReactiveController, ReactiveControllerHost} from 'lit'; |
| import {Binding, ShortcutOptions} from '../../utils/dom-util'; |
| import {shortcutsServiceToken} from '../../services/shortcuts/shortcuts-service'; |
| import {Shortcut} from '../../services/shortcuts/shortcuts-config'; |
| import {resolve} from '../../models/dependency'; |
| |
| export {Shortcut}; |
| interface ShortcutListener { |
| binding: Binding; |
| listener: (e: KeyboardEvent) => void; |
| options?: ShortcutOptions; |
| } |
| |
| interface AbstractListener { |
| shortcut: Shortcut; |
| listener: (e: KeyboardEvent) => void; |
| options?: ShortcutOptions; |
| } |
| |
| type Cleanup = () => void; |
| |
| export class ShortcutController implements ReactiveController { |
| private readonly getShortcutsService = resolve( |
| this.host, |
| shortcutsServiceToken |
| ); |
| |
| private readonly listenersLocal: ShortcutListener[] = []; |
| |
| private readonly listenersGlobal: ShortcutListener[] = []; |
| |
| private readonly listenersAbstract: AbstractListener[] = []; |
| |
| private cleanups: Cleanup[] = []; |
| |
| constructor(private readonly host: ReactiveControllerHost & HTMLElement) { |
| host.addController(this); |
| } |
| |
| // Note that local shortcuts are *not* suppressed when the user has shortcuts |
| // disabled or when the event comes from elements like <input>. So this method |
| // is intended for shortcuts like ESC and Ctrl-ENTER. |
| // Call method in constructor of the component |
| addLocal( |
| binding: Binding, |
| listener: (e: KeyboardEvent) => void, |
| options?: ShortcutOptions |
| ) { |
| this.listenersLocal.push({binding, listener, options}); |
| } |
| |
| // Call method in constructor of the component |
| addGlobal(binding: Binding, listener: (e: KeyboardEvent) => void) { |
| this.listenersGlobal.push({binding, listener}); |
| } |
| |
| /** |
| * `Shortcut` is more abstract than a concrete `Binding`. A `Shortcut` has a |
| * description text and (several) bindings configured in the file |
| * `shortcuts-config.ts`. |
| * |
| * Use this method when you are migrating from Polymer to Lit. Call it for |
| * each entry of keyboardShortcuts(). |
| * |
| * Call method in constructor of the component |
| */ |
| addAbstract( |
| shortcut: Shortcut, |
| listener: (e: KeyboardEvent) => void, |
| options?: ShortcutOptions |
| ) { |
| this.listenersAbstract.push({shortcut, listener, options}); |
| } |
| |
| hostConnected() { |
| const shortcutsService = this.getShortcutsService(); |
| for (const {binding, listener, options} of this.listenersLocal) { |
| const cleanup = shortcutsService.addShortcut( |
| this.host, |
| binding, |
| listener, |
| { |
| shouldSuppress: options?.shouldSuppress ?? false, |
| preventDefault: options?.preventDefault, |
| } |
| ); |
| this.cleanups.push(cleanup); |
| } |
| for (const {shortcut, listener, options} of this.listenersAbstract) { |
| const cleanup = shortcutsService.addShortcutListener( |
| shortcut, |
| listener, |
| options |
| ); |
| this.cleanups.push(cleanup); |
| } |
| for (const {binding, listener} of this.listenersGlobal) { |
| const cleanup = shortcutsService.addShortcut( |
| document.body, |
| binding, |
| listener |
| ); |
| this.cleanups.push(cleanup); |
| } |
| } |
| |
| hostDisconnected() { |
| for (const cleanup of this.cleanups) { |
| cleanup(); |
| } |
| this.cleanups = []; |
| } |
| } |