Do you like this project? Please support my Mecha CMS project too. Thank you!

Key 3.1.4

Micro JavaScript hot-key/key-map system.

Result goes here…

# Usage

Browser

<!DOCTYPE html>
<html dir="ltr">
  <head>
    <meta charset="utf-8">
  </head>
  <body>
    <p>
      <textarea></textarea>
    </p>
    <script src="./index.min.js"></script>
    <script>
      const map = new Key;
      const self = document.querySelector('textarea');
      self.addEventListener('blur', e => map.pull()); // Reset!
      self.addEventListener('focus', e => map.pull()); // Reset!
      let wait;
      self.addEventListener('keydown', e => {
          // Make the `Alt`, `Control`, `Meta`, and `Shift` keys sticky (does not require the user to release all keys first to repeat or change the current key combination).
          map[e.altKey ? 'push' : 'pull']('Alt');
          map[e.ctrlKey ? 'push' : 'pull']('Control');
          map[e.metaKey ? 'push' : 'pull']('Meta');
          map[e.shiftKey ? 'push' : 'pull']('Shift');
          // Add the actual key to the queue. Don’t worry, this will not mistakenly add a key that already exists in the queue.
          e.key && map.push(e.key);
          console.log(map.toString());
          wait && clearTimeout(wait);
          wait = setTimeout(() => map.pull(), 1000); // Reset after 1 second idle!
      });
      self.addEventListener('keyup', e => e.key && map.pull(e.key));
    </script>
  </body>
</html>

Note: For better compatibility with mobile devices, it is also a good practice to listen to the beforeinput event to verify the character being typed. Unlike the keydown event, which has the KeyboardEvent.key property that returns the character of the key pressed before it is inserted into the text area, the beforeinput (and input) event has the InputEvent.data property that also returns the character of the key pressed when the InputEvent.inputType value is 'insertText'. For more details on this tweak, please refer to the source code of this page.

Node.js

Functions and methods in this application are mostly native JavaScript and are intended for use by the browser. Node.js doesn’t know about the DOM, so this kind of practice will probably be used more often to build new browser packages than to be used directly in the Node.js server.

CommonJS

const Key = require('@taufik-nurrohman/key').default;

const map = new Key;

ECMAScript

import Key from '@taufik-nurrohman/key';

const map = new Key;

# Tests

# Tweaks

# Constructor

const map = new Key(self);

# Parameters

self

The object that will becomes the this context in the command.

# Methods

Instance Methods

Instance methods are methods available through the results of a Key construct.

map.command()

Checks if current key combination exists.

self.addEventListener('keydown', e => {
    e.key && map.push(e.key);
    let command = map.command();
    if (command) {
        console.log(command);
    }
});

Checks if current key combination is Control-b.

self.addEventListener('keydown', e => {
    e.key && map.push(e.key);
    if (map.command('Control-b')) {
        console.log('Bold!');
    }
});

Example context usage, using @taufik-nurrohman/text-editor application:

import Key from '@taufik-nurrohman/key';
import TextEditor from '@taufik-nurrohman/text-editor';

const editor = new TextEditor(document.querySelector('textarea'));
const map = new Key(editor);

map.commands.bold = function () {
    return this.wrap('<b>', '</b>'), false; // `this` refers to the `editor`
};

map.fire(command, data)

Executes command data returned by map.command() method.

// …
let command = map.command();
if (command) {
    let value = map.fire(command, [e]);
    if (false === value) {
        e.preventDefault();
    } else if (null === value) {
        console.error('Unknown command:', command);
    }
}

map.pull(key)

Removes key from the queue.

self.addEventListener('blur', e => map.pull());
self.addEventListener('focus', e => map.pull());
self.addEventListener('keyup', e => e.key && map.pull(e.key));

map.push(key)

Adds key to the queue.

self.addEventListener('keydown', e => e.key && map.push(e.key));

map.toArray()

Returns the current key combination as array.

self.addEventListener('keydown', e => {
    e.key && map.push(e.key);
    console.info(map.toArray());
});

map.toString()

Returns the current key combination as string.

self.addEventListener('keydown', e => {
    e.key && map.push(e.key);
    console.info(map + "");
    console.info(map.toString());
});

# Properties

Instance Properties

Instance properties are properties available through the results of a Key construct.

map.commands

Returns list of commands to be executed on key combination match.

// Simple function
map.commands.bold = function () {
    // Your `bold` function goes here…
    return false; // Return `false` to tell the event that it has to prevent the default effect
};

// Function with argument(s)
map.commands.insertLink = function (href = 'http://') {
    // Your `insertLink` function goes here…
    // Return `void` will be normalized into `true` by `map.fire()` method.
};

map.keys

Returns list of key combination with its command that will be returned by map.command(). Key names follow the KeyboardEvent.key specification.

// Anonymous function
map.keys['Control-b'] = function () {
    return this.wrap('<b>', '</b>'), false;
};

// Named function without argument(s)
map.keys['Control-b'] = 'bold'; // This will execute `map.commands.bold` command if available

// Named function with argument(s)
map.keys['Control-k'] = ['insertLink', ['http://']]; // This will execute `map.commands.insertLink` command if available

Note: You can also use your own key names, since in most cases they are specified by the KeyboardEvent.key value generated by the keydown event. Here is an example of custom key names, with normalized order for consistency:

// Should work for both `Control-Shift-E` and `Shift-Control-E`, even when `CapsLock` is active.
map.keys['C-S-e'] = function (e) {
    console.log('Focus to the file/folder tree.');
};

self.addEventListener('keydown', function (e) {
    // This sequence ensures that the combination of keys will form `C-A-S-`, or `C-A-`, or `C-S-`, or `A-S-` no matter which key is pressed first.
    map[e.ctrlKey ? 'push' : 'pull']('C').pull('Control'); // Normalize `Control` key to `C`
    map[e.altKey ? 'push' : 'pull']('A').pull('Alt'); // Normalize `Alt` key to `A`
    map[e.shiftKey ? 'push' : 'pull']('S').pull('Shift'); // Normalize `Shift` key to `S`
    // Add only the lower case version of the key, so that the `CapsLock` and `Shift` key states will not affect the results of the key combination.
    e.key && !['Alt', 'Control', 'Shift'].includes(e.key) && map.push(e.key.toLowerCase());
});

self.addEventListener('keyup', function (e) {
    map[e.ctrlKey ? 'push' : 'pull']('C').pull('Control');
    map[e.altKey ? 'push' : 'pull']('A').pull('Alt');
    map[e.shiftKey ? 'push' : 'pull']('S').pull('Shift');
    e.key && map.pull(e.key.toLowerCase());
});

# License

Use it for free, pay if you get paid. So, you’ve just benefited financially after using this project? It’s a good idea to share a little financial support with this open source project too. Your support will motivate me to do any further development, as well as to provide voluntary support to overcome problems related to this project.

Thank you! ❤️