mirror of
https://github.com/FoggedLens/iD.git
synced 2026-02-13 09:12:52 +00:00
423 lines
13 KiB
JavaScript
423 lines
13 KiB
JavaScript
import {
|
||
select as d3_select
|
||
} from 'd3-selection';
|
||
|
||
import { utilArrayUniq } from './array';
|
||
|
||
|
||
export function utilKeybinding(namespace) {
|
||
var _keybindings = {};
|
||
|
||
|
||
function testBindings(d3_event, isCapturing) {
|
||
var didMatch = false;
|
||
var bindings = Object.keys(_keybindings).map(function(id) { return _keybindings[id]; });
|
||
var i, binding;
|
||
|
||
// Most key shortcuts will accept either lower or uppercase ('h' or 'H'),
|
||
// so we don't strictly match on the shift key, but we prioritize
|
||
// shifted keybindings first, and fallback to unshifted only if no match.
|
||
// (This lets us differentiate between '←'/'⇧←' or '⌘Z'/'⌘⇧Z')
|
||
|
||
// priority match shifted keybindings first
|
||
for (i = 0; i < bindings.length; i++) {
|
||
binding = bindings[i];
|
||
if (!binding.event.modifiers.shiftKey) continue; // no shift
|
||
if (!!binding.capture !== isCapturing) continue;
|
||
if (matches(d3_event, binding, true)) {
|
||
binding.callback(d3_event);
|
||
didMatch = true;
|
||
|
||
// match a max of one binding per event
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (didMatch) return;
|
||
|
||
// then unshifted keybindings
|
||
for (i = 0; i < bindings.length; i++) {
|
||
binding = bindings[i];
|
||
if (binding.event.modifiers.shiftKey) continue; // shift
|
||
if (!!binding.capture !== isCapturing) continue;
|
||
if (matches(d3_event, binding, false)) {
|
||
binding.callback(d3_event);
|
||
break;
|
||
}
|
||
}
|
||
|
||
|
||
function matches(d3_event, binding, testShift) {
|
||
var event = d3_event;
|
||
var isMatch = false;
|
||
var tryKeyCode = true;
|
||
|
||
// Prefer a match on `KeyboardEvent.key`
|
||
if (event.key !== undefined) {
|
||
tryKeyCode = (event.key.charCodeAt(0) > 255); // outside ISO-Latin-1
|
||
isMatch = true;
|
||
|
||
if (binding.event.key === undefined) {
|
||
isMatch = false;
|
||
} else if (Array.isArray(binding.event.key)) {
|
||
if (binding.event.key.map(function(s) { return s.toLowerCase(); }).indexOf(event.key.toLowerCase()) === -1)
|
||
isMatch = false;
|
||
} else {
|
||
if (event.key.toLowerCase() !== binding.event.key.toLowerCase())
|
||
isMatch = false;
|
||
}
|
||
}
|
||
|
||
// Fallback match on `KeyboardEvent.keyCode`, can happen if:
|
||
// - browser doesn't support `KeyboardEvent.key`
|
||
// - `KeyboardEvent.key` is outside ISO-Latin-1 range (cyrillic?)
|
||
if (!isMatch && tryKeyCode) {
|
||
isMatch = (event.keyCode === binding.event.keyCode);
|
||
}
|
||
|
||
if (!isMatch) return false;
|
||
|
||
// test modifier keys
|
||
if (!(event.ctrlKey && event.altKey)) { // if both are set, assume AltGr and skip it - #4096
|
||
if (event.ctrlKey !== binding.event.modifiers.ctrlKey) return false;
|
||
if (event.altKey !== binding.event.modifiers.altKey) return false;
|
||
}
|
||
if (event.metaKey !== binding.event.modifiers.metaKey) return false;
|
||
if (testShift && event.shiftKey !== binding.event.modifiers.shiftKey) return false;
|
||
|
||
return true;
|
||
}
|
||
}
|
||
|
||
|
||
function capture(d3_event) {
|
||
testBindings(d3_event, true);
|
||
}
|
||
|
||
|
||
function bubble(d3_event) {
|
||
var tagName = d3_select(d3_event.target).node().tagName;
|
||
if (tagName === 'INPUT' || tagName === 'SELECT' || tagName === 'TEXTAREA') {
|
||
return;
|
||
}
|
||
testBindings(d3_event, false);
|
||
}
|
||
|
||
|
||
function keybinding(selection) {
|
||
selection = selection || d3_select(document);
|
||
selection.on('keydown.capture.' + namespace, capture, true);
|
||
selection.on('keydown.bubble.' + namespace, bubble, false);
|
||
return keybinding;
|
||
}
|
||
|
||
// was: keybinding.off()
|
||
keybinding.unbind = function(selection) {
|
||
_keybindings = [];
|
||
selection = selection || d3_select(document);
|
||
selection.on('keydown.capture.' + namespace, null);
|
||
selection.on('keydown.bubble.' + namespace, null);
|
||
return keybinding;
|
||
};
|
||
|
||
|
||
keybinding.clear = function() {
|
||
_keybindings = {};
|
||
return keybinding;
|
||
};
|
||
|
||
|
||
// Remove one or more keycode bindings.
|
||
keybinding.off = function(codes, capture) {
|
||
var arr = utilArrayUniq([].concat(codes));
|
||
|
||
for (var i = 0; i < arr.length; i++) {
|
||
var id = arr[i] + (capture ? '-capture' : '-bubble');
|
||
delete _keybindings[id];
|
||
}
|
||
return keybinding;
|
||
};
|
||
|
||
|
||
// Add one or more keycode bindings.
|
||
keybinding.on = function(codes, callback, capture) {
|
||
if (typeof callback !== 'function') {
|
||
return keybinding.off(codes, capture);
|
||
}
|
||
|
||
var arr = utilArrayUniq([].concat(codes));
|
||
|
||
for (var i = 0; i < arr.length; i++) {
|
||
var id = arr[i] + (capture ? '-capture' : '-bubble');
|
||
var binding = {
|
||
id: id,
|
||
capture: capture,
|
||
callback: callback,
|
||
event: {
|
||
key: undefined, // preferred
|
||
keyCode: 0, // fallback
|
||
modifiers: {
|
||
shiftKey: false,
|
||
ctrlKey: false,
|
||
altKey: false,
|
||
metaKey: false
|
||
}
|
||
}
|
||
};
|
||
|
||
if (_keybindings[id]) {
|
||
console.warn('warning: duplicate keybinding for "' + id + '"'); // eslint-disable-line no-console
|
||
}
|
||
|
||
_keybindings[id] = binding;
|
||
|
||
var matches = arr[i].toLowerCase().match(/(?:(?:[^+⇧⌃⌥⌘])+|[⇧⌃⌥⌘]|\+\+|^\+$)/g);
|
||
for (var j = 0; j < matches.length; j++) {
|
||
// Normalise matching errors
|
||
if (matches[j] === '++') matches[j] = '+';
|
||
|
||
if (matches[j] in utilKeybinding.modifierCodes) {
|
||
var prop = utilKeybinding.modifierProperties[utilKeybinding.modifierCodes[matches[j]]];
|
||
binding.event.modifiers[prop] = true;
|
||
} else {
|
||
binding.event.key = utilKeybinding.keys[matches[j]] || matches[j];
|
||
if (matches[j] in utilKeybinding.keyCodes) {
|
||
binding.event.keyCode = utilKeybinding.keyCodes[matches[j]];
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
return keybinding;
|
||
};
|
||
|
||
|
||
return keybinding;
|
||
}
|
||
|
||
|
||
/*
|
||
* See https://github.com/keithamus/jwerty
|
||
*/
|
||
|
||
utilKeybinding.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
|
||
};
|
||
|
||
utilKeybinding.modifierProperties = {
|
||
16: 'shiftKey',
|
||
17: 'ctrlKey',
|
||
18: 'altKey',
|
||
91: 'metaKey'
|
||
};
|
||
|
||
utilKeybinding.plusKeys = ['plus', 'ffplus', '=', 'ffequals', '≠', '±'];
|
||
utilKeybinding.minusKeys = ['_', '-', 'ffminus', 'dash', '–', '—'];
|
||
|
||
utilKeybinding.keys = {
|
||
// Backspace key, on Mac: ⌫ (Backspace)
|
||
'⌫': 'Backspace', backspace: 'Backspace',
|
||
// Tab Key, on Mac: ⇥ (Tab), on Windows ⇥⇥
|
||
'⇥': 'Tab', '⇆': 'Tab', tab: 'Tab',
|
||
// Return key, ↩
|
||
'↩': 'Enter', '↵': 'Enter', '⏎': 'Enter', 'return': 'Enter', enter: 'Enter', '⌅': 'Enter',
|
||
// Pause/Break key
|
||
'pause': 'Pause', 'pause-break': 'Pause',
|
||
// Caps Lock key, ⇪
|
||
'⇪': 'CapsLock', caps: 'CapsLock', 'caps-lock': 'CapsLock',
|
||
// Escape key, on Mac: ⎋, on Windows: Esc
|
||
'⎋': ['Escape', 'Esc'], escape: ['Escape', 'Esc'], esc: ['Escape', 'Esc'],
|
||
// Space key
|
||
space: [' ', 'Spacebar'],
|
||
// Page-Up key, or pgup, on Mac: ↖
|
||
'↖': 'PageUp', pgup: 'PageUp', 'page-up': 'PageUp',
|
||
// Page-Down key, or pgdown, on Mac: ↘
|
||
'↘': 'PageDown', pgdown: 'PageDown', 'page-down': 'PageDown',
|
||
// END key, on Mac: ⇟
|
||
'⇟': 'End', end: 'End',
|
||
// HOME key, on Mac: ⇞
|
||
'⇞': 'Home', home: 'Home',
|
||
// Insert key, or ins
|
||
ins: 'Insert', insert: 'Insert',
|
||
// Delete key, on Mac: ⌦ (Delete)
|
||
'⌦': ['Delete', 'Del'], del: ['Delete', 'Del'], 'delete': ['Delete', 'Del'],
|
||
// Left Arrow Key, or ←
|
||
'←': ['ArrowLeft', 'Left'], left: ['ArrowLeft', 'Left'], 'arrow-left': ['ArrowLeft', 'Left'],
|
||
// Up Arrow Key, or ↑
|
||
'↑': ['ArrowUp', 'Up'], up: ['ArrowUp', 'Up'], 'arrow-up': ['ArrowUp', 'Up'],
|
||
// Right Arrow Key, or →
|
||
'→': ['ArrowRight', 'Right'], right: ['ArrowRight', 'Right'], 'arrow-right': ['ArrowRight', 'Right'],
|
||
// Up Arrow Key, or ↓
|
||
'↓': ['ArrowDown', 'Down'], down: ['ArrowDown', 'Down'], 'arrow-down': ['ArrowDown', 'Down'],
|
||
// odities, stuff for backward compatibility (browsers and code):
|
||
// Num-Multiply, or *
|
||
'*': ['*', 'Multiply'], star: ['*', 'Multiply'], asterisk: ['*', 'Multiply'], multiply: ['*', 'Multiply'],
|
||
// Num-Plus or +
|
||
'+': ['+', 'Add'], 'plus': ['+', 'Add'],
|
||
// Num-Subtract, or -
|
||
'-': ['-', 'Subtract'], subtract: ['-', 'Subtract'], 'dash': ['-', 'Subtract'],
|
||
// Semicolon
|
||
semicolon: ';',
|
||
// = or equals
|
||
equals: '=',
|
||
// Comma, or ,
|
||
comma: ',',
|
||
// Period, or ., or full-stop
|
||
period: '.', 'full-stop': '.',
|
||
// Slash, or /, or forward-slash
|
||
slash: '/', 'forward-slash': '/',
|
||
// Tick, or `, or back-quote
|
||
tick: '`', 'back-quote': '`',
|
||
// Open bracket, or [
|
||
'open-bracket': '[',
|
||
// Back slash, or \
|
||
'back-slash': '\\',
|
||
// Close backet, or ]
|
||
'close-bracket': ']',
|
||
// Apostrophe, or Quote, or '
|
||
quote: '\'', apostrophe: '\'',
|
||
// NUMPAD 0-9
|
||
'num-0': '0',
|
||
'num-1': '1',
|
||
'num-2': '2',
|
||
'num-3': '3',
|
||
'num-4': '4',
|
||
'num-5': '5',
|
||
'num-6': '6',
|
||
'num-7': '7',
|
||
'num-8': '8',
|
||
'num-9': '9',
|
||
// F1-F25
|
||
f1: 'F1',
|
||
f2: 'F2',
|
||
f3: 'F3',
|
||
f4: 'F4',
|
||
f5: 'F5',
|
||
f6: 'F6',
|
||
f7: 'F7',
|
||
f8: 'F8',
|
||
f9: 'F9',
|
||
f10: 'F10',
|
||
f11: 'F11',
|
||
f12: 'F12',
|
||
f13: 'F13',
|
||
f14: 'F14',
|
||
f15: 'F15',
|
||
f16: 'F16',
|
||
f17: 'F17',
|
||
f18: 'F18',
|
||
f19: 'F19',
|
||
f20: 'F20',
|
||
f21: 'F21',
|
||
f22: 'F22',
|
||
f23: 'F23',
|
||
f24: 'F24',
|
||
f25: 'F25'
|
||
};
|
||
|
||
utilKeybinding.keyCodes = {
|
||
// Backspace key, on Mac: ⌫ (Backspace)
|
||
'⌫': 8, backspace: 8,
|
||
// Tab Key, on Mac: ⇥ (Tab), on Windows ⇥⇥
|
||
'⇥': 9, '⇆': 9, tab: 9,
|
||
// Return key, ↩
|
||
'↩': 13, '↵': 13, '⏎': 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:
|
||
// Firefox Equals
|
||
'ffequals': 61,
|
||
// Num-Multiply, or *
|
||
'*': 106, star: 106, asterisk: 106, multiply: 106,
|
||
// Num-Plus or +
|
||
'+': 107, 'plus': 107,
|
||
// Num-Subtract, or -
|
||
'-': 109, subtract: 109,
|
||
// Firefox Plus
|
||
'ffplus': 171,
|
||
// Firefox Minus
|
||
'ffminus': 173,
|
||
// Semicolon
|
||
';': 186, semicolon: 186,
|
||
// = or equals
|
||
'=': 187, 'equals': 187,
|
||
// Comma, or ,
|
||
',': 188, comma: 188,
|
||
// Dash / Underscore key
|
||
'dash': 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) {
|
||
utilKeybinding.keyCodes['num-' + n] = i;
|
||
++n;
|
||
}
|
||
|
||
// 0-9
|
||
i = 47; n = 0;
|
||
while (++i < 58) {
|
||
utilKeybinding.keyCodes[n] = i;
|
||
++n;
|
||
}
|
||
|
||
// F1-F25
|
||
i = 111; n = 1;
|
||
while (++i < 136) {
|
||
utilKeybinding.keyCodes['f' + n] = i;
|
||
++n;
|
||
}
|
||
|
||
// a-z
|
||
i = 64;
|
||
while (++i < 91) {
|
||
utilKeybinding.keyCodes[String.fromCharCode(i).toLowerCase()] = i;
|
||
}
|