@antv/x6
Version:
JavaScript diagramming library that uses SVG and HTML for rendering.
178 lines (149 loc) • 4.44 kB
text/typescript
import Mousetrap from 'mousetrap'
import { Dom, FunctionExt } from '../util'
import { Disposable, IDisablable } from '../common'
import { Graph } from './graph'
export class Keyboard extends Disposable implements IDisablable {
public readonly target: HTMLElement | Document
public readonly container: HTMLElement
public readonly mousetrap: Mousetrap.MousetrapInstance
protected get graph() {
return this.options.graph
}
constructor(public readonly options: Keyboard.Options) {
super()
const scroller = this.graph.scroller.widget
this.container = scroller ? scroller.container : this.graph.container
if (options.global) {
this.target = document
} else {
this.target = this.container
if (!this.disabled) {
// ensure the container focusable
this.target.setAttribute('tabindex', '-1')
}
// change to mouseup event,prevent page stalling caused by focus
this.graph.on('cell:mouseup', this.focus, this)
this.graph.on('blank:mouseup', this.focus, this)
}
this.mousetrap = Keyboard.createMousetrap(this)
}
get disabled() {
return this.options.enabled !== true
}
enable() {
if (this.disabled) {
this.options.enabled = true
this.graph.options.keyboard.enabled = true
if (this.target instanceof HTMLElement) {
this.target.setAttribute('tabindex', '-1')
}
}
}
disable() {
if (!this.disabled) {
this.options.enabled = false
this.graph.options.keyboard.enabled = false
if (this.target instanceof HTMLElement) {
this.target.removeAttribute('tabindex')
}
}
}
on(
keys: string | string[],
callback: Keyboard.Handler,
action?: Keyboard.Action,
) {
this.mousetrap.bind(this.getKeys(keys), callback, action)
}
off(keys: string | string[], action?: Keyboard.Action) {
this.mousetrap.unbind(this.getKeys(keys), action)
}
private focus() {
const target = this.target as HTMLElement
target.focus({
preventScroll: true,
})
}
private getKeys(keys: string | string[]) {
return (Array.isArray(keys) ? keys : [keys]).map((key) =>
this.formatkey(key),
)
}
protected formatkey(key: string) {
const formated = key
.toLowerCase()
.replace(/\s/g, '')
.replace('delete', 'del')
.replace('cmd', 'command')
const formatFn = this.options.format
if (formatFn) {
return FunctionExt.call(formatFn, this.graph, formated)
}
return formated
}
protected isGraphEvent(e: KeyboardEvent) {
const target = (e.srcElement || e.target) as Element
if (target) {
if (target === this.target || target === document.body) {
return true
}
return Dom.contains(this.container, target)
}
return false
}
isEnabledForEvent(e: KeyboardEvent) {
const allowed = !this.disabled && this.isGraphEvent(e)
if (allowed) {
const target = e.target as Element
const tagName = target && target.tagName.toLowerCase()
const code = e.keyCode || e.which
if (tagName === 'input' && (code === 8 || code === 46)) {
return false
}
if (this.options.guard) {
return FunctionExt.call(this.options.guard, this.graph, e)
}
}
return allowed
}
.dispose()
dispose() {
this.mousetrap.reset()
}
}
export namespace Keyboard {
export type Action = 'keypress' | 'keydown' | 'keyup'
export type Handler = (e: KeyboardEvent) => void
export interface Options {
graph: Graph
enabled?: boolean
/**
* Specifies if keyboard event should bind on docuemnt or on container.
*
* Default is `false` that will bind keyboard event on the container.
*/
global?: boolean
format?: (this: Graph, key: string) => string
guard?: (this: Graph, e: KeyboardEvent) => boolean
}
}
export namespace Keyboard {
export function createMousetrap(keyboard: Keyboard) {
const mousetrap = new Mousetrap(keyboard.target as Element)
const stopCallback = mousetrap.stopCallback
mousetrap.stopCallback = (
e: KeyboardEvent,
elem: HTMLElement,
combo: string,
) => {
if (keyboard.isEnabledForEvent(e)) {
if (stopCallback) {
return stopCallback.call(mousetrap, e, elem, combo)
}
return false
}
return true
}
return mousetrap
}
}