| /** | |
| * @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 = []; | |
| } | |
| } |