adopted-ember-addons/ember-keyboard

Provide support for scopes

Opened this issue · 0 comments

Hallo,

I'm currently working on an app that utilizes a custom service for shortcuts. This service includes a scope that indicates the app's current state. It would greatly benefit me to have a centrally managed scope for easier migration. Is there a solution available that I might have missed?

My current ShortcutsService;

import RouterService from '@ember/routing/router-service';
import Service, { inject as service } from '@ember/service';
import { tracked } from '@glimmer/tracking';
import CommandPaletteService from './command-palette';
import EventBusService from './event-bus';
import { ShortCutScope, ShortCut } from '../types/app';

/**
 * ShortcutsService
 */
export default class ShortcutsService extends Service {
  @service declare router: RouterService;
  @service declare eventBus: EventBusService;
  // prettier-ignore
  @service('command-palette') declare commandPaletteService: CommandPaletteService;

  // Defaults
  @tracked scope = ShortCutScope.All;

  // prettier-ignore
  shortcuts: ShortCut[] = [
    {shortcut: 'command+1', scope: ShortCutScope.All, callback: () => this.router.transitionTo('documents') },
    {shortcut: 'command+2', scope: ShortCutScope.All, callback: () => this.router.transitionTo('import') },
    {shortcut: 'command+3', scope: ShortCutScope.All, callback: () => this.router.transitionTo('senders') },
    {shortcut: 'command+,', scope: ShortCutScope.All, callback: () => this.router.transitionTo('settings') },
    {shortcut: 'command+k', scope: ShortCutScope.All, callback: () => this.commandPaletteService.open() },
    {shortcut: 'escape', scope: ShortCutScope.CommandPalette, callback: () => this.commandPaletteService.close() },
    {shortcut: 'arrowup', scope: ShortCutScope.CommandPalette, callback: () => this.commandPaletteService.indexChange(false) },
    {shortcut: 'arrowdown', scope: ShortCutScope.CommandPalette, callback: () => this.commandPaletteService.indexChange(true) },
  ];

  // Id's of input elements that should not be filtered
  excludedInputIds = ['command-palette-input'];

  /**
   * Setup the shortcuts
   * @returns {void}
   */
  setup(): void {
    document.addEventListener('keydown', (event) => {
      this.handleKeyDown(event);
    });
  }

  /**
   * Set the scope
   * @param {ShortCutScope} scope
   */
  setScope(scope: ShortCutScope): void {
    this.scope = scope;
  }

  /**
   * Get the scope
   * @returns {ShortCutScope}
   */
  get getScope(): ShortCutScope {
    return this.scope;
  }

  /**
   * Handle key down event
   * @param {KeyboardEvent} event
   */
  handleKeyDown(event: KeyboardEvent): void {
    if (this.filterEvent(event)) {
      return;
    }

    const shortcut = this.findShortcut(event);

    if (shortcut) {
      event.preventDefault();
      event.stopPropagation();

      shortcut.callback();
    }
  }

  /**
   * Filter the event for input elements
   *
   * @param {KeyboardEvent} event
   */
  filterEvent(event: KeyboardEvent): boolean {
    const target = event.target as HTMLElement;

    if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA') {
      if (this.excludedInputIds.includes(target.id)) {
        return false;
      }

      return true;
    }

    return false;
  }

  /**
   * Find a shortcut
   * @param {KeyboardEvent} event
   * @returns {ShortCut | undefined}
   */
  findShortcut(event: KeyboardEvent): ShortCut | undefined {
    const key = event.key.toLowerCase();
    const isCommand = event.metaKey || event.ctrlKey;
    const shortcut = `${isCommand ? 'command+' : ''}${key}`;

    return this.shortcuts.find((s) => {
      return (
        s.shortcut === shortcut &&
        (s.scope === this.scope || s.scope === ShortCutScope.All)
      );
    });
  }
}

// Don't remove this declaration: this is what enables TypeScript to resolve
// this service using `Owner.lookup('service:shortcuts')`, as well
// as to check when you pass the service name as an argument to the decorator,
// like `@service('shortcuts') declare altName: ShortcutsService;`.
declare module '@ember/service' {
  interface Registry {
    shortcuts: ShortcutsService;
  }
}