From ff15aa8e7b077b86ba05fa1415231f0730011eb9 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 22 Jan 2013 14:02:02 -0500 Subject: [PATCH] Rewrite d3.keybinding MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A keybinding now represents a set of key commands that can be unbound as a set. Multiple keybindings are possible, and, providing a namespace is provided to the constructor, will not conflict with each other. Also, key combination strings such as ⌘+A are now supported. --- js/id/id.js | 32 ++-- js/id/modes/add_area.js | 9 +- js/id/modes/add_line.js | 9 +- js/id/modes/add_point.js | 9 +- js/id/modes/draw_area.js | 21 +-- js/id/modes/draw_line.js | 30 ++-- js/id/modes/select.js | 12 +- js/id/renderer/map.js | 9 - js/lib/d3.keybinding.js | 307 +++++++++++++++++++++------------ test/index.html | 2 + test/lib/happen.js | 27 +-- test/spec/lib/d3.keybinding.js | 55 ++++++ 12 files changed, 329 insertions(+), 193 deletions(-) create mode 100644 test/spec/lib/d3.keybinding.js diff --git a/js/id/id.js b/js/id/id.js index 31ff26836..9d7fb38bf 100644 --- a/js/id/id.js +++ b/js/id/id.js @@ -184,26 +184,18 @@ window.iD = function(container) { map.size(m.size()); }); - map.keybinding() - .on('a', function(evt, mods) { - if (mods) return; - controller.enter(iD.modes.AddArea()); - }) - .on('⌫.prevent_navigation', function(evt, mods) { - evt.preventDefault(); - }) - .on('p', function(evt, mods) { - if (mods) return; - controller.enter(iD.modes.AddPoint()); - }) - .on('l', function(evt, mods) { - if (mods) return; - controller.enter(iD.modes.AddLine()); - }) - .on('z', function(evt, mods) { - if (mods === '⇧⌘' || mods === '⌃⇧') history.redo(); - if (mods === '⌘' || mods === '⌃') history.undo(); - }); + var keybinding = d3.keybinding('main') + .on('P', function() { controller.enter(iD.modes.AddPoint()); }) + .on('L', function() { controller.enter(iD.modes.AddLine()); }) + .on('A', function() { controller.enter(iD.modes.AddArea()); }) + .on('⌘+Z', function() { history.undo(); }) + .on('⌃+Z', function() { history.undo(); }) + .on('⌘+⇧+Z', function() { history.redo(); }) + .on('⌃+⇧+Z', function() { history.redo(); }) + .on('⌫', function(e) { e.preventDefault(); }); + + d3.select(document) + .call(keybinding); var hash = iD.Hash().controller(controller).map(map); diff --git a/js/id/modes/add_area.js b/js/id/modes/add_area.js index a9fcb830e..d3936229e 100644 --- a/js/id/modes/add_area.js +++ b/js/id/modes/add_area.js @@ -6,6 +6,8 @@ iD.modes.AddArea = function() { description: 'Add parks, buildings, lakes, or other areas to the map.' }; + var keybinding = d3.keybinding('add-area'); + mode.enter = function() { var map = mode.map, history = mode.history, @@ -38,9 +40,12 @@ iD.modes.AddArea = function() { controller.enter(iD.modes.DrawArea(way.id)); }); - map.keybinding().on('⎋.addarea', function() { + keybinding.on('⎋', function() { controller.exit(); }); + + d3.select(document) + .call(keybinding); }; mode.exit = function() { @@ -49,7 +54,7 @@ iD.modes.AddArea = function() { }, 1000); mode.map.tail(false); mode.map.surface.on('click.addarea', null); - mode.map.keybinding().on('⎋.addarea', null); + keybinding.off(); }; return mode; diff --git a/js/id/modes/add_line.js b/js/id/modes/add_line.js index b83202097..db4e88e18 100644 --- a/js/id/modes/add_line.js +++ b/js/id/modes/add_line.js @@ -6,6 +6,8 @@ iD.modes.AddLine = function() { description: 'Lines can be highways, streets, pedestrian paths, or even canals.' }; + var keybinding = d3.keybinding('add-line'); + mode.enter = function() { var map = mode.map, node, @@ -60,16 +62,19 @@ iD.modes.AddLine = function() { controller.enter(iD.modes.DrawLine(way.id, direction)); }); - map.keybinding().on('⎋.addline', function() { + keybinding.on('⎋', function() { controller.exit(); }); + + d3.select(document) + .call(keybinding); }; mode.exit = function() { mode.map.dblclickEnable(true); mode.map.tail(false); mode.map.surface.on('click.addline', null); - mode.map.keybinding().on('⎋.addline', null); + keybinding.off(); }; return mode; diff --git a/js/id/modes/add_point.js b/js/id/modes/add_point.js index 82ad283c2..70275d050 100644 --- a/js/id/modes/add_point.js +++ b/js/id/modes/add_point.js @@ -5,6 +5,8 @@ iD.modes.AddPoint = function() { description: 'Restaurants, monuments, and postal boxes are points.' }; + var keybinding = d3.keybinding('add-point'); + mode.enter = function() { var map = mode.map, history = mode.history, @@ -22,15 +24,18 @@ iD.modes.AddPoint = function() { controller.enter(iD.modes.Select(node, true)); }); - map.keybinding().on('⎋.addpoint', function() { + keybinding.on('⎋', function() { controller.exit(); }); + + d3.select(document) + .call(keybinding); }; mode.exit = function() { mode.map.tail(false); mode.map.surface.on('click.addpoint', null); - mode.map.keybinding().on('⎋.addpoint', null); + keybinding.off(); }; return mode; diff --git a/js/id/modes/draw_area.js b/js/id/modes/draw_area.js index 2f1fa6304..2fe02f37c 100644 --- a/js/id/modes/draw_area.js +++ b/js/id/modes/draw_area.js @@ -4,6 +4,8 @@ iD.modes.DrawArea = function(wayId) { id: 'draw-area' }; + var keybinding = d3.keybinding('draw-area'); + mode.enter = function() { var map = mode.map, surface = map.surface, @@ -102,11 +104,14 @@ iD.modes.DrawArea = function(wayId) { .on('mouseover.drawarea', mouseover) .on('click.drawarea', click); - map.keybinding() - .on('⌫.drawarea', backspace) - .on('⌦.drawarea', del) - .on('⎋.drawarea', ret) - .on('↩.drawarea', ret); + keybinding + .on('⌫', backspace) + .on('⌦', del) + .on('⎋', ret) + .on('↩', ret); + + d3.select(document) + .call(keybinding); }; mode.exit = function() { @@ -122,11 +127,7 @@ iD.modes.DrawArea = function(wayId) { .on('mousemove.drawarea', null) .on('click.drawarea', null); - mode.map.keybinding() - .on('⎋.drawarea', null) - .on('⌫.drawarea', null) - .on('⌦.drawarea', null) - .on('↩.drawarea', null); + keybinding.off(); window.setTimeout(function() { mode.map.dblclickEnable(true); diff --git a/js/id/modes/draw_line.js b/js/id/modes/draw_line.js index 8442fbf80..e857bafe4 100644 --- a/js/id/modes/draw_line.js +++ b/js/id/modes/draw_line.js @@ -4,6 +4,8 @@ iD.modes.DrawLine = function(wayId, direction) { id: 'draw-line' }; + var keybinding = d3.keybinding('draw-line'); + mode.enter = function() { var map = mode.map, surface = map.surface, @@ -133,16 +135,19 @@ iD.modes.DrawLine = function(wayId, direction) { .on('mousemove.drawline', mousemove) .on('click.drawline', click); - map.keybinding() - .on('⌫.drawline', backspace) - .on('⌦.drawline', del) - .on('⎋.drawline', ret) - .on('↩.drawline', ret) - .on('z.drawline', function(evt, mods) { - if (mods === '⌘' || mods === '⌃') undo(); - }); + keybinding + .on('⌫', backspace) + .on('⌦', del) + .on('⎋', ret) + .on('↩', ret) + .on('⌘-Z', undo) + .on('⌃-Z', undo); - d3.select('#undo').on('click.drawline', undo); + d3.select(document) + .call(keybinding); + + d3.select('#undo') + .on('click.drawline', undo); }; mode.exit = function() { @@ -159,12 +164,7 @@ iD.modes.DrawLine = function(wayId, direction) { .on('mousemove.drawline', null) .on('click.drawline', null); - mode.map.keybinding() - .on('⌫.drawline', null) - .on('⌦.drawline', null) - .on('⎋.drawline', null) - .on('↩.drawline', null) - .on('z.drawline', null); + keybinding.off(); d3.select('#undo').on('click.drawline', null); diff --git a/js/id/modes/select.js b/js/id/modes/select.js index 0cfd725f7..b735352aa 100644 --- a/js/id/modes/select.js +++ b/js/id/modes/select.js @@ -6,6 +6,7 @@ iD.modes.Select = function(entity, initial) { }; var inspector = iD.ui.inspector().initial(!!initial), + keybinding = d3.keybinding('select'), behaviors; function remove() { @@ -132,10 +133,10 @@ iD.modes.Select = function(entity, initial) { surface.on('click.select', click) .on('dblclick.browse', dblclick); - mode.map.keybinding().on('⌫.select', function(e) { - remove(); - e.preventDefault(); - }); + keybinding.on('⌫', remove); + + d3.select(document) + .call(keybinding); surface.selectAll("*") .filter(function (d) { @@ -166,8 +167,9 @@ iD.modes.Select = function(entity, initial) { var q = iD.util.stringQs(location.hash.substring(1)); location.hash = '#' + iD.util.qsString(_.omit(q, 'id'), true); + keybinding.off(); + surface.on("click.select", null); - mode.map.keybinding().on('⌫.select', null); mode.history.on('change.entity-undone', null); surface.selectAll(".selected") diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js index ee145103f..7e74d7b66 100644 --- a/js/id/renderer/map.js +++ b/js/id/renderer/map.js @@ -2,7 +2,6 @@ iD.Map = function() { var connection, history, dimensions = [], dispatch = d3.dispatch('move', 'drawn'), - keybinding = d3.keybinding(), projection = d3.geo.mercator().scale(1024), roundedProjection = iD.svg.RoundProjection(projection), zoom = d3.behavior.zoom() @@ -49,8 +48,6 @@ iD.Map = function() { supersurface .call(tail); - - d3.select(document).call(keybinding); } function pxCenter() { return [dimensions[0] / 2, dimensions[1] / 2]; } @@ -345,12 +342,6 @@ iD.Map = function() { return map; }; - map.keybinding = function (_) { - if (!arguments.length) return keybinding; - keybinding = _; - return map; - }; - map.background = background; map.projection = projection; map.redraw = redraw; diff --git a/js/lib/d3.keybinding.js b/js/lib/d3.keybinding.js index 01256aa20..e16ade725 100644 --- a/js/lib/d3.keybinding.js +++ b/js/lib/d3.keybinding.js @@ -1,120 +1,197 @@ -d3.keybinding = function() { - // via https://github.com/keithamus/jwerty/ - // and https://github.com/madrobby/keymaster - var _keys = { - // MOD aka toggleable keys - mods: { - // Shift key, ⇧ - '⇧': 16, - // CTRL key, on Mac: ⌃ - '⌃': 17, - // ALT key, on Mac: ⌥ (Alt) - '⌥': 18, - // META, on Mac: ⌘ (CMD), on Windows (Win), on Linux (Super) - '⌘': 91 - }, - // Normal keys - keys: { - // Backspace key, on Mac: ⌫ (Backspace) - '⌫': 8, backspace: 8, - // Tab Key, on Mac: ⇥ (Tab), on Windows ⇥⇥ - '⇥': 9, '⇆': 9, tab: 9, - // Return key, ↩ - '↩': 13, 'return': 13, enter: 13, '⌅': 13, - // Pause/Break key - 'pause': 19, 'pause-break': 19, - // Caps Lock key, ⇪ - '⇪': 20, caps: 20, 'caps-lock': 20, - // Escape key, on Mac: ⎋, on Windows: Esc - '⎋': 27, escape: 27, esc: 27, - // Space key - space: 32, - // Page-Up key, or pgup, on Mac: ↖ - '↖': 33, pgup: 33, 'page-up': 33, - // Page-Down key, or pgdown, on Mac: ↘ - '↘': 34, pgdown: 34, 'page-down': 34, - // END key, on Mac: ⇟ - '⇟': 35, end: 35, - // HOME key, on Mac: ⇞ - '⇞': 36, home: 36, - // Insert key, or ins - ins: 45, insert: 45, - // Delete key, on Mac: ⌦ (Delete) - '⌦': 46, del: 46, 'delete': 46, - // Left Arrow Key, or ← - '←': 37, left: 37, 'arrow-left': 37, - // Up Arrow Key, or ↑ - '↑': 38, up: 38, 'arrow-up': 38, - // Right Arrow Key, or → - '→': 39, right: 39, 'arrow-right': 39, - // Up Arrow Key, or ↓ - '↓': 40, down: 40, 'arrow-down': 40, - // odities, printing characters that come out wrong: - // Num-Multiply, or * - '*': 106, star: 106, asterisk: 106, multiply: 106, - // Num-Plus or + - '+': 107, 'plus': 107, - // Num-Subtract, or - - '-': 109, subtract: 109, - // Semicolon - ';': 186, semicolon:186, - // = or equals - '=': 187, 'equals': 187, - // Comma, or , - ',': 188, comma: 188, - //'-': 189, //??? - // Period, or ., or full-stop - '.': 190, period: 190, 'full-stop': 190, - // Slash, or /, or forward-slash - '/': 191, slash: 191, 'forward-slash': 191, - // Tick, or `, or back-quote - '`': 192, tick: 192, 'back-quote': 192, - // Open bracket, or [ - '[': 219, 'open-bracket': 219, - // Back slash, or \ - '\\': 220, 'back-slash': 220, - // Close backet, or ] - ']': 221, 'close-bracket': 221, - // Apostraphe, or Quote, or ' - '\'': 222, quote: 222, apostraphe: 222 +/* + * This code is licensed under the MIT license. + * + * Copyright © 2013, iD authors. + * + * Portions copyright © 2011, Keith Cirkel + * See https://github.com/keithamus/jwerty + * + */ +d3.keybinding = function(namespace) { + var bindings = []; + + function matches(binding, event) { + for (var p in binding.event) { + if (event[p] != binding.event[p]) + return false; } - }; - // To minimise code bloat, add all of the NUMPAD 0-9 keys in a loop - var i = 95, n = 0; - while (++i < 106) _keys.keys['num-' + n] = i; ++n; - // To minimise code bloat, add all of the top row 0-9 keys in a loop - i = 47, n = 0; - while (++i < 58) _keys.keys[n] = i; ++n; - // To minimise code bloat, add all of the F1-F25 keys in a loop - i = 111, n = 1; - while (++i < 136) _keys.keys['f' + n] = i; ++n; - // To minimise code bloat, add all of the letters of the alphabet in a loop - i = 64; - while(++i < 91) _keys.keys[String.fromCharCode(i).toLowerCase()] = i; - var pairs = d3.entries(_keys.keys), - event = d3.dispatch.apply(d3, d3.keys(_keys.keys)); - - function keys(selection) { - selection.on('keydown', function () { - var tagName = d3.select(d3.event.target).node().tagName; - if (tagName == 'INPUT' || tagName == 'SELECT' || tagName == 'TEXTAREA') { - return; - } - - var modifiers = ''; - if (d3.event.shiftKey) modifiers += '⇧'; - if (d3.event.ctrlKey) modifiers += '⌃'; - if (d3.event.altKey) modifiers += '⌥'; - if (d3.event.metaKey) modifiers += '⌘'; - - pairs.filter(function(d) { - return d.value === d3.event.keyCode; - }).forEach(function(d) { - event[d.key](d3.event, modifiers); - }); - }); + return (!binding.capture) === (event.eventPhase !== Event.CAPTURING_PHASE); } - return d3.rebind(keys, event, 'on'); + function capture() { + for (var i = 0; i < bindings.length; i++) { + var binding = bindings[i]; + if (matches(binding, d3.event)) { + binding.callback(); + } + } + } + + function bubble() { + var tagName = d3.select(d3.event.target).node().tagName; + if (tagName == 'INPUT' || tagName == 'SELECT' || tagName == 'TEXTAREA') { + return; + } + capture(); + } + + function keybinding(selection) { + selection = selection || d3.select(document); + selection.on('keydown.capture' + namespace, capture, true); + selection.on('keydown.bubble' + namespace, bubble, false); + return keybinding; + } + + keybinding.off = function(selection) { + selection = selection || d3.select(document); + selection.on('keydown.capture' + namespace, null); + selection.on('keydown.bubble' + namespace, null); + return keybinding; + }; + + keybinding.on = function(code, callback, capture) { + var binding = { + event: { + keyCode: 0, + shiftKey: false, + ctrlKey: false, + altKey: false, + metaKey: false + }, + capture: capture, + callback: callback + }; + + code = code.toLowerCase().match(/(?:(?:[^+])+|\+\+|^\+$)/g); + + for (var i = 0; i < code.length; i++) { + // Normalise matching errors + if (code[i] === '++') code[i] = '+'; + + if (code[i] in d3.keybinding.modifierCodes) { + binding.event[d3.keybinding.modifierProperties[d3.keybinding.modifierCodes[code[i]]]] = true; + } else if (code[i] in d3.keybinding.keyCodes) { + binding.event.keyCode = d3.keybinding.keyCodes[code[i]]; + } + } + + bindings.push(binding); + + return keybinding; + }; + + return keybinding; }; + +(function () { + d3.keybinding.modifierCodes = { + // Shift key, ⇧ + '⇧': 16, shift: 16, + // CTRL key, on Mac: ⌃ + '⌃': 17, ctrl: 17, + // ALT key, on Mac: ⌥ (Alt) + '⌥': 18, alt: 18, option: 18, + // META, on Mac: ⌘ (CMD), on Windows (Win), on Linux (Super) + '⌘': 91, meta: 91, cmd: 91, 'super': 91, win: 91 + }; + + d3.keybinding.modifierProperties = { + 16: 'shiftKey', + 17: 'ctrlKey', + 18: 'altKey', + 91: 'metaKey' + }; + + d3.keybinding.keyCodes = { + // Backspace key, on Mac: ⌫ (Backspace) + '⌫': 8, backspace: 8, + // Tab Key, on Mac: ⇥ (Tab), on Windows ⇥⇥ + '⇥': 9, '⇆': 9, tab: 9, + // Return key, ↩ + '↩': 13, 'return': 13, enter: 13, '⌅': 13, + // Pause/Break key + 'pause': 19, 'pause-break': 19, + // Caps Lock key, ⇪ + '⇪': 20, caps: 20, 'caps-lock': 20, + // Escape key, on Mac: ⎋, on Windows: Esc + '⎋': 27, escape: 27, esc: 27, + // Space key + space: 32, + // Page-Up key, or pgup, on Mac: ↖ + '↖': 33, pgup: 33, 'page-up': 33, + // Page-Down key, or pgdown, on Mac: ↘ + '↘': 34, pgdown: 34, 'page-down': 34, + // END key, on Mac: ⇟ + '⇟': 35, end: 35, + // HOME key, on Mac: ⇞ + '⇞': 36, home: 36, + // Insert key, or ins + ins: 45, insert: 45, + // Delete key, on Mac: ⌦ (Delete) + '⌦': 46, del: 46, 'delete': 46, + // Left Arrow Key, or ← + '←': 37, left: 37, 'arrow-left': 37, + // Up Arrow Key, or ↑ + '↑': 38, up: 38, 'arrow-up': 38, + // Right Arrow Key, or → + '→': 39, right: 39, 'arrow-right': 39, + // Up Arrow Key, or ↓ + '↓': 40, down: 40, 'arrow-down': 40, + // odities, printing characters that come out wrong: + // Num-Multiply, or * + '*': 106, star: 106, asterisk: 106, multiply: 106, + // Num-Plus or + + '+': 107, 'plus': 107, + // Num-Subtract, or - + '-': 109, subtract: 109, + // Semicolon + ';': 186, semicolon:186, + // = or equals + '=': 187, 'equals': 187, + // Comma, or , + ',': 188, comma: 188, + //'-': 189, //??? + // Period, or ., or full-stop + '.': 190, period: 190, 'full-stop': 190, + // Slash, or /, or forward-slash + '/': 191, slash: 191, 'forward-slash': 191, + // Tick, or `, or back-quote + '`': 192, tick: 192, 'back-quote': 192, + // Open bracket, or [ + '[': 219, 'open-bracket': 219, + // Back slash, or \ + '\\': 220, 'back-slash': 220, + // Close backet, or ] + ']': 221, 'close-bracket': 221, + // Apostrophe, or Quote, or ' + '\'': 222, quote: 222, apostrophe: 222 + }; + + // NUMPAD 0-9 + var i = 95, n = 0; + while (++i < 106) { + d3.keybinding.keyCodes['num-' + n] = i; + ++n; + } + + // 0-9 + i = 47; n = 0; + while (++i < 58) { + d3.keybinding.keyCodes[n] = i; + ++n; + } + + // F1-F25 + i = 111; n = 1; + while (++i < 136) { + d3.keybinding.keyCodes['f' + n] = i; + ++n; + } + + // a-z + i = 64; + while (++i < 91) { + d3.keybinding.keyCodes[String.fromCharCode(i).toLowerCase()] = i; + } +})(); diff --git a/test/index.html b/test/index.html index 425258d20..4099f2786 100644 --- a/test/index.html +++ b/test/index.html @@ -124,6 +124,8 @@ + + diff --git a/test/lib/happen.js b/test/lib/happen.js index 7c46d238d..8777d883a 100644 --- a/test/lib/happen.js +++ b/test/lib/happen.js @@ -19,10 +19,10 @@ evt = new Event(o.type); evt.keyCode = o.keyCode || 0; evt.charCode = o.charCode || 0; - evt.shift = o.shift || false; - evt.meta = o.meta || false; - evt.ctrl = o.ctrl || false; - evt.alt = o.alt || false; + evt.shiftKey = o.shiftKey || false; + evt.metaKey = o.metaKey || false; + evt.ctrlKey = o.ctrlKey || false; + evt.altKey = o.altKey || false; } else { evt = document.createEvent('KeyboardEvent'); // https://developer.mozilla.org/en/DOM/event.initKeyEvent @@ -33,10 +33,10 @@ true, // in boolean canBubbleArg, true, // in boolean cancelableArg, null, // in nsIDOMAbstractView viewArg, Specifies UIEvent.view. This value may be null. - o.ctrl || false, // in boolean ctrlKeyArg, - o.alt || false, // in boolean altKeyArg, - o.shift || false, // in boolean shiftKeyArg, - o.meta || false, // in boolean metaKeyArg, + o.ctrlKey || false, // in boolean ctrlKeyArg, + o.altKey || false, // in boolean altKeyArg, + o.shiftKey || false, // in boolean shiftKeyArg, + o.metaKey || false, // in boolean metaKeyArg, o.keyCode || 0, // in unsigned long keyCodeArg, o.charCode || 0 // in unsigned long charCodeArg); ); @@ -53,10 +53,10 @@ o.screenY || 0, // screenY o.clientX || 0, // clientX o.clientY || 0, // clientY - o.ctrl || 0, // ctrl - o.alt || false, // alt - o.shift || false, // shift - o.meta || false, // meta + o.ctrlKey || 0, // ctrl + o.altKey || false, // alt + o.shiftKey || false, // shift + o.metaKey || false, // meta o.button || false, // mouse button null // relatedTarget ); @@ -65,7 +65,8 @@ x.dispatchEvent(evt); }; - var shortcuts = ['click', 'mousedown', 'mouseup', 'mousemove', 'keydown', 'keyup', 'keypress'], + var shortcuts = ['click', 'mousedown', 'mouseup', 'mousemove', + 'mouseover', 'mouseout', 'keydown', 'keyup', 'keypress'], s, i = 0; while (s = shortcuts[i++]) { diff --git a/test/spec/lib/d3.keybinding.js b/test/spec/lib/d3.keybinding.js new file mode 100644 index 000000000..23e1a0e0c --- /dev/null +++ b/test/spec/lib/d3.keybinding.js @@ -0,0 +1,55 @@ +describe("d3.keybinding", function() { + var keybinding, spy, input; + + beforeEach(function () { + keybinding = d3.keybinding('keybinding-test'); + spy = sinon.spy(); + input = d3.select('body') + .append('input'); + }); + + afterEach(function () { + keybinding.off(d3.select(document)); + input.remove(); + }); + + describe("#on", function () { + it("returns self", function () { + expect(keybinding.on('a', spy)).to.equal(keybinding); + }); + + it("adds a binding for the specified bare key", function () { + d3.select(document).call(keybinding.on('A', spy)); + + happen.keydown(document, {keyCode: 65, metaKey: true}); + expect(spy).not.to.have.been.called; + + happen.keydown(document, {keyCode: 65}); + expect(spy).to.have.been.called; + }); + + it("adds a binding for the specified key combination", function () { + d3.select(document).call(keybinding.on('⌘+A', spy)); + + happen.keydown(document, {keyCode: 65}); + expect(spy).not.to.have.been.called; + + happen.keydown(document, {keyCode: 65, metaKey: true}); + expect(spy).to.have.been.called; + }); + + it("does not dispatch when focus is in input elements by default", function () { + d3.select(document).call(keybinding.on('A', spy)); + + happen.keydown(input.node(), {keyCode: 65}); + expect(spy).not.to.have.been.called; + }); + + it("dispatches when focus is in input elements when the capture flag was passed", function () { + d3.select(document).call(keybinding.on('A', spy, true)); + + happen.keydown(input.node(), {keyCode: 65}); + expect(spy).to.have.been.called; + }); + }); +});