yuku-t/textcomplete

View on GitHub
src/editor.js

Summary

Maintainability
B
4 hrs
Test Coverage
// @flow
/*eslint no-unused-vars: off*/

import EventEmitter from "eventemitter3"

import { createCustomEvent } from "./utils"
import SearchResult from "./search_result"

/** @typedef */
export type CursorOffset = {
  lineHeight: number,
  top: number,
  left?: number,
  right?: number,
  clientTop?: number
}

type KeyCode = "ESC" | "ENTER" | "UP" | "DOWN" | "OTHER"

/**
 * Abstract class representing a editor target.
 *
 * Editor classes must implement `#applySearchResult`, `#getCursorOffset` and
 * `#getBeforeCursor` methods.
 *
 * Editor classes must invoke `#emitMoveEvent`, `#emitEnterEvent`,
 * `#emitChangeEvent` and `#emitEscEvent` at proper timing.
 *
 * @abstract
 */
export default class Editor extends EventEmitter {
  /**
   * It is called when associated textcomplete object is destroyed.
   *
   * @return {this}
   */
  destroy() {
    return this
  }

  /**
   * It is called when a search result is selected by a user.
   */
  applySearchResult(_: SearchResult): void {
    throw new Error("Not implemented.")
  }

  /**
   * The input cursor's absolute coordinates from the window's left
   * top corner.
   */
  getCursorOffset(): CursorOffset {
    throw new Error("Not implemented.")
  }

  /**
   * Editor string value from head to cursor.
   * Returns null if selection type is range not cursor.
   */
  getBeforeCursor(): ?string {
    throw new Error("Not implemented.")
  }

  /**
   * Emit a move event, which moves active dropdown element.
   * Child class must call this method at proper timing with proper parameter.
   *
   * @see {@link Textarea} for live example.
   */
  emitMoveEvent(code: "UP" | "DOWN"): CustomEvent {
    const moveEvent = createCustomEvent("move", {
      cancelable: true,
      detail: {
        code: code,
      },
    })
    this.emit("move", moveEvent)
    return moveEvent
  }

  /**
   * Emit a enter event, which selects current search result.
   * Child class must call this method at proper timing.
   *
   * @see {@link Textarea} for live example.
   */
  emitEnterEvent(): CustomEvent {
    const enterEvent = createCustomEvent("enter", { cancelable: true })
    this.emit("enter", enterEvent)
    return enterEvent
  }

  /**
   * Emit a change event, which triggers auto completion.
   * Child class must call this method at proper timing.
   *
   * @see {@link Textarea} for live example.
   */
  emitChangeEvent(): CustomEvent {
    const changeEvent = createCustomEvent("change", {
      detail: {
        beforeCursor: this.getBeforeCursor(),
      },
    })
    this.emit("change", changeEvent)
    return changeEvent
  }

  /**
   * Emit a esc event, which hides dropdown element.
   * Child class must call this method at proper timing.
   *
   * @see {@link Textarea} for live example.
   */
  emitEscEvent(): CustomEvent {
    const escEvent = createCustomEvent("esc", { cancelable: true })
    this.emit("esc", escEvent)
    return escEvent
  }

  /**
   * Helper method for parsing KeyboardEvent.
   *
   * @see {@link Textarea} for live example.
   */
  getCode(e: KeyboardEvent): KeyCode {
    return e.keyCode === 9
      ? "ENTER" // tab
      : e.keyCode === 13
        ? "ENTER" // enter
        : e.keyCode === 27
          ? "ESC" // esc
          : e.keyCode === 38
            ? "UP" // up
            : e.keyCode === 40
              ? "DOWN" // down
              : e.keyCode === 78 && e.ctrlKey
                ? "DOWN" // ctrl-n
                : e.keyCode === 80 && e.ctrlKey
                  ? "UP" // ctrl-p
                  : "OTHER"
  }
}