@ryusei/code
Version:
<div align="center"> <a href="https://code.ryuseijs.com"> <img alt="RyuseiCode" src="https://code.ryuseijs.com/images/svg/logo.svg" width="70"> </a>
137 lines (117 loc) • 3.88 kB
HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Shinonome</title>
<link href="../../../../dist/css/themes/ryuseicode-shinonome.min.css" rel="stylesheet">
</head>
<body style="margin: 1em">
<pre id="code">
import { Elements, EventBusEvent, KeyMatcher } from ' /code';
import { Component } from '../../classes/Component/Component';
import { EVENT_KEYDOWN, EVENT_KEYMAP } from '../../constants/events';
import { KEYMAP, MODIFIER_KEYS } from '../../constants/keymap';
import { assign, forOwn, includes, isArray, isMac, isString, matchesKey, normalizeKey, toArray } from '../../utils';
/**
* The component for detecting keyboard shortcuts and distributing them as internal events.
*
* @since 0.1.0
*/
export class Keymap extends Component {
/**
* Stores the target keys.
*/
protected keys: string[] = [];
/**
* The collection of shortcuts.
*/
protected keymap: Record<string, KeyMatcher| KeyMatcher[] | null | false>;
/**
* Initializes the component.
*
* @param elements - A collection of essential elements.
*/
mount( elements: Elements ): void {
super.mount( elements );
this.keymap = assign( {}, KEYMAP, this.options.keymap );
forOwn( this.keymap, matchers => {
if ( matchers ) {
this.keys.push( ...toArray( matchers, true ).map( matcher => {
return matcher[ 0 ].toUpperCase();
} ) );
}
} );
this.on( EVENT_KEYDOWN, this.onKeydown, this, 0 );
}
/**
* Called when any key is pressed.
*
* @param e - An EventBusEvent object.
* @param ke - A KeyboardEvent object.
*/
protected onKeydown( e: EventBusEvent, ke: KeyboardEvent ): void {
if ( ! this.Editor.readOnly ) {
if ( includes( this.keys, normalizeKey( ke.key ).toUpperCase() ) ) {
const action = this.find( ke );
if ( action ) {
this.emit( `${ EVENT_KEYMAP }:${ action }`, ke, action );
}
}
}
}
/**
* Finds the shortcut action from keymap definition.
*
* @param e - A KeyboardEvent object.
*
* @return A found action.
*/
protected find( e: KeyboardEvent ): string {
let action = '';
forOwn( this.keymap, ( matchers, id ) => {
if ( this.matches( e, id ) ) {
action = id;
return false;
}
} );
return action;
}
/**
* Checks if the keyboard event matches keys of the provided ID or not.
*
* @param e - A KeyboardEvent object.
* @param id - An ID.
*
* @return `true` if the keyboard event matches keys of the ID, or otherwise `false`.
*/
matches( e: KeyboardEvent, id: string ): boolean {
const matchers = this.keymap[ id ];
return matchers && matchesKey( e, matchers );
}
/**
* Builds a shortcut that describes keys of the provided keymap ID or a KeyMatcher object.
* For example, `undo` or `[ 'Z', true ]` will be `Ctrl + Z`.
*
* @param id - An ID in a keymap or a KeyMatcher object.
*
* @return A built shortcut as a string.
*/
getShortcut( id: string | KeyMatcher ): string {
const matchers = isString( id ) ? this.keymap[ id ] : id;
if ( matchers ) {
const matcher = isArray( matchers[ 0 ] ) ? matchers[ 0 ] : matchers as KeyMatcher;
if ( matcher ) {
const modifiers = MODIFIER_KEYS[ isMac() ? 'mac' : 'default' ];
const keys = matcher.slice( 1 ).map( ( use, index ) => use && modifiers[ index ] ).filter( Boolean );
return keys.concat( matcher[ 0 ] ).join( '+' );
}
}
return '';
}
}</pre>
<script src="../../../../dist/js/ryuseicode-complete.min.js"></script>
<script>
new RyuseiCode( { language: 'typescript' } ).apply( '#code' );
</script>
</body>
</html>