browserkeymap
Advanced tools
Comparing version 1.0.1 to 2.0.0
143
index.js
@@ -91,88 +91,72 @@ (function(mod) { | ||
// :: (Object, ?Object) | ||
// A keymap binds a set of [key names](#keyName) to commands names | ||
// or functions. | ||
// | ||
// Construct a keymap using the bindings in `keys`, whose properties | ||
// should be [key names](#keyName) or space-separated sequences of | ||
// key names. In the second case, the binding will be for a | ||
// multi-stroke key combination. | ||
// | ||
// When `options` has a property `call`, this will be a programmatic | ||
// keymap, meaning that instead of looking keys up in its set of | ||
// bindings, it will pass the key name to `options.call`, and use | ||
// the return value of that calls as the resolved binding. | ||
// | ||
// `options.name` can be used to give the keymap a name, making it | ||
// easier to [remove](#ProseMirror.removeKeymap) from an editor. | ||
function Keymap(keys, options) { | ||
this.options = options || {} | ||
this.bindings = Object.create(null) | ||
if (keys) this.addBindings(keys) | ||
function hasProp(obj, prop) { | ||
return Object.prototype.hasOwnProperty.call(obj, prop) | ||
} | ||
Keymap.prototype = { | ||
normalize: function(name) { | ||
return this.options.multi !== false ? name.split(/ +(?!\'$)/).map(normalizeKeyName) : [normalizeKeyName(name)] | ||
}, | ||
function unusedMulti(bindings, name) { | ||
for (var binding in bindings) | ||
if (binding.length > name && binding.indexOf(name) == 0 && binding.charAt(name.length) == " ") | ||
return false | ||
return true | ||
} | ||
// :: (string, any) | ||
// Add a binding for the given key or key sequence. | ||
addBinding: function(keyname, value) { | ||
var keys = this.normalize(keyname) | ||
for (var i = 0; i < keys.length; i++) { | ||
var name = keys.slice(0, i + 1).join(" ") | ||
var val = i == keys.length - 1 ? value : "..." | ||
var prev = this.bindings[name] | ||
if (!prev) this.bindings[name] = val | ||
else if (prev != val) throw new Error("Inconsistent bindings for " + name) | ||
} | ||
}, | ||
function updateBindings(bindings, base) { | ||
var result = {} | ||
if (base) for (var prop in base) if (hasProp(base, prop)) result[prop] = base[prop] | ||
// :: (Object<any>) | ||
// Add all the bindings in the given object to the keymap. | ||
addBindings: function(bindings) { | ||
for (var keyname in bindings) if (Object.prototype.hasOwnProperty.call(bindings, keyname)) | ||
this.addBinding(keyname, bindings[keyname]) | ||
}, | ||
// :: (string) | ||
// Remove the binding for the given key or key sequence. | ||
removeBinding: function(keyname) { | ||
var keys = this.normalize(keyname) | ||
for (var i = keys.length - 1; i >= 0; i--) { | ||
var name = keys.slice(0, i).join(" ") | ||
var val = this.bindings[name] | ||
if (val == "..." && !this.unusedMulti(name)) | ||
break | ||
else if (val) | ||
delete this.bindings[name] | ||
for (var keyname in bindings) if (hasProp(bindings, keyname)) { | ||
var keys = keyname.split(/ +(?!\'$)/).map(normalizeKeyName) | ||
var value = bindings[keyname] | ||
if (value == null) { | ||
for (var i = keys.length - 1; i >= 0; i--) { | ||
var name = keys.slice(0, i).join(" ") | ||
var old = result[name] | ||
if (old == Keymap.unfinished && !unusedMulti(result, name)) | ||
break | ||
else if (old) | ||
delete result[name] | ||
} | ||
} else { | ||
for (var i = 0; i < keys.length; i++) { | ||
var name = keys.slice(0, i + 1).join(" ") | ||
var val = i == keys.length - 1 ? value : Keymap.unfinished | ||
var prev = result[name] | ||
if (prev && (i < keys.length - 1 || prev == Keymap.unfinished) && prev != val) | ||
throw new Error("Inconsistent bindings for " + name) | ||
result[name] = val | ||
} | ||
} | ||
}, | ||
} | ||
return result | ||
} | ||
unusedMulti: function(name) { | ||
for (var binding in this.bindings) | ||
if (binding.length > name && binding.indexOf(name) == 0 && binding.charAt(name.length) == " ") | ||
return false | ||
return true | ||
}, | ||
// :: (Object<T>) → Keymap<T> | ||
// A keymap binds a set of [key names](#keyName) to values. | ||
// | ||
// Construct a keymap using the given bindings, which should be an | ||
// object whose property names are [key names](#keyName) or | ||
// space-separated sequences of key names. In the second case, the | ||
// binding will be for a multi-stroke key combination. | ||
function Keymap(bindings, base) { | ||
this.bindings = updateBindings(bindings, base) | ||
} | ||
// :: (string, ?any) → any | ||
// Looks up the given key or key sequence in this keymap. Returns | ||
// the value the key is bound to (which may be undefined if it is | ||
// not bound), or the string `"..."` if the key is a prefix of a | ||
// multi-key sequence that is bound by this keymap. | ||
lookup: function(key, context) { | ||
return this.options.call ? this.options.call(key, context) : this.bindings[key] | ||
}, | ||
// :: (Object<?T>) → Keymap | ||
// Create a new keymap by adding bindings from the given object, | ||
// and removing the bindings that the object maps to null. | ||
Keymap.prototype.update = function(bindings) { | ||
return new Keymap(bindings, this.bindings) | ||
} | ||
// :: (any) → ?string | ||
reverseLookup: function(value) { | ||
for (var keyname in this.bindings) | ||
if (this.bindings[keyname] == value) return keyname | ||
}, | ||
constructor: Keymap | ||
// :: (string) → T | ||
// Looks up the given key or key sequence in this keymap. Returns | ||
// the value the key is bound to (which may be undefined if it is | ||
// not bound), or the value `Keymap.unfinished` if the key is a prefix of a | ||
// multi-key sequence that is bound by this keymap. | ||
Keymap.prototype.lookup = function(key) { | ||
return this.bindings[key] | ||
} | ||
Keymap.unfinished = {toString: function() { return "Keymap.unfinished" }} | ||
Keymap.keyName = keyName | ||
@@ -182,3 +166,10 @@ Keymap.isModifierKey = isModifierKey | ||
function ComputedKeymap(f) { this.f = f } | ||
ComputedKeymap.prototype.lookup = function(key, context) { return this.f(key, context) } | ||
// :: ((key: string, context: ?any) → T) → ComputedKeymap<T> | ||
// Construct a 'computed' keymap from a function which takes a key | ||
// name and returns a binding. | ||
Keymap.Computed = ComputedKeymap | ||
return Keymap | ||
}) |
{ | ||
"name": "browserkeymap", | ||
"version": "1.0.1", | ||
"version": "2.0.0", | ||
"description": "Map browser events to key names, and key names to values", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
@@ -16,13 +16,14 @@ # Browser Keymap | ||
* `keydown` events are represented as zero or more modifiers | ||
(`Shift-`, `Cmd-`, `Alt-`, `Ctrl-`, in that order) followed by a | ||
key name. | ||
(`Shift-`, `Cmd-`, `Alt-`, `Ctrl-`) followed by a key name. | ||
* A key name is a capital letter for a letter key, `F` plus a number | ||
for a function key, the symbol typed by the key when shift isn't | ||
held down (one of ``[\]`'*,-./;=``), or one of the names `Alt`, | ||
`Backspace`, `CapsLock`, `Ctrl`, `Delete`, `Down`, `End`, `Enter`, | ||
`Esc`, `Home`, `Insert`, `Left`, `Mod`, `PageDown`, `PageUp`, | ||
`Pause`, `PrintScrn`, `Right`, `Shift`, `Space`, `Tab`, or `Up`. | ||
* A key name is a capital letter for a letter key, a digit for a | ||
number key, `F` plus a number for a function key, the symbol typed | ||
by the key when shift isn't held down (one of ``[\]`'*,-./;=``), or | ||
one of the names `Alt`, `Backspace`, `CapsLock`, `Ctrl`, `Delete`, | ||
`Down`, `End`, `Enter`, `Esc`, `Home`, `Insert`, `Left`, `Mod`, | ||
`PageDown`, `PageUp`, `Pause`, `PrintScrn`, `Right`, `Shift`, | ||
`Space`, `Tab`, or `Up`. | ||
You can get a key name from an event by calling `Keymap.keyName(event)`. | ||
You can get a key name from an event by calling | ||
`Keymap.keyName(event)`. | ||
@@ -56,29 +57,22 @@ You can normalize a key name string (fixing the order of the | ||
You can add and remove bindings on an existing keymap. | ||
You can create a new map from an existing map with its `update` | ||
method: | ||
myMap.addBinding("Alt-F4", handleQuit) | ||
myMap.addBindings({"Enter": something, "Esc": something}) | ||
myMap.removeBinding("Shift-Space") | ||
var newMap = myMap.update({ | ||
"Alt-F4": handleQuit, | ||
"Ctrl-Q": null | ||
}) | ||
So far, this doesn't do anything that a JavaScript object couldn't do. | ||
The constructor accepts a second argument, which is an options object. | ||
These options are supported: | ||
That will create a new map, starting with the bindings in `myMap`, | ||
adding a binding for `Alt-F4`, and removing the binding for `Ctrl-Q`. | ||
* **`multi`**: Boolean, defaults to true. Toggles support | ||
multi-stroke key bindings. Binding names may be space-separated | ||
strings containing multiple key names. The keymap will track which | ||
prefixes are part of a multi-stroke binding, and when such a | ||
prefix is looked up, it will return `"..."`, to indicate to client | ||
code that it should buffer the key name, and, on the next | ||
non-modifier key event, try looking up that key name suffixed by | ||
the next key name (separated by a space). | ||
Multi-stroke keys are supported by providing space-separated names to | ||
a keymap. When a prefix of a multi-stroke key is looked up, the | ||
`lookup` method will return `Keymap.unfinished`. The handler should | ||
then buffer the key name, and on the next key event (possibly with a | ||
timeout to clear buffered keys), try again by prefixing the new key | ||
event's name with the buffered key(s). | ||
* **`call`**: Function, defaults to null. When given, it makes this | ||
keymap a programmatic keymap, meaning the bindings are ignored and | ||
the function that is the value of the `call` option will be | ||
called, with the key name, when a key is looked up, and its return | ||
value returned. | ||
## License | ||
This module is released under an MIT license. |
11416
154
77