src/editor.js
// @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"
}
}