UNPKG

react-force-graph

Version:

React component for 2D, 3D, VR and AR force directed graphs

1,317 lines (1,148 loc) 5.27 MB
// Version 1.47.7 react-force-graph - https://github.com/vasturiano/react-force-graph (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('react')) : typeof define === 'function' && define.amd ? define(['exports', 'react'], factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.ForceGraph = {}, global.React)); })(this, (function (exports, React) { 'use strict'; function _iterableToArrayLimit$o(arr, i) { var _i = null == arr ? null : "undefined" != typeof Symbol && arr[Symbol.iterator] || arr["@@iterator"]; if (null != _i) { var _s, _e, _x, _r, _arr = [], _n = true, _d = false; try { if (_x = (_i = _i.call(arr)).next, 0 === i) { if (Object(_i) !== _i) return; _n = !1; } else for (; !(_n = (_s = _x.call(_i)).done) && (_arr.push(_s.value), _arr.length !== i); _n = !0); } catch (err) { _d = true, _e = err; } finally { try { if (!_n && null != _i.return && (_r = _i.return(), Object(_r) !== _r)) return; } finally { if (_d) throw _e; } } return _arr; } } function _defineProperty$d(obj, key, value) { key = _toPropertyKey$i(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } function _slicedToArray$o(arr, i) { return _arrayWithHoles$o(arr) || _iterableToArrayLimit$o(arr, i) || _unsupportedIterableToArray$q(arr, i) || _nonIterableRest$o(); } function _toConsumableArray$k(arr) { return _arrayWithoutHoles$k(arr) || _iterableToArray$k(arr) || _unsupportedIterableToArray$q(arr) || _nonIterableSpread$k(); } function _arrayWithoutHoles$k(arr) { if (Array.isArray(arr)) return _arrayLikeToArray$q(arr); } function _arrayWithHoles$o(arr) { if (Array.isArray(arr)) return arr; } function _iterableToArray$k(iter) { if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter); } function _unsupportedIterableToArray$q(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray$q(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray$q(o, minLen); } function _arrayLikeToArray$q(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; } function _nonIterableSpread$k() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } function _nonIterableRest$o() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } function _toPrimitive$i(input, hint) { if (typeof input !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint); if (typeof res !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); } function _toPropertyKey$i(arg) { var key = _toPrimitive$i(arg, "string"); return typeof key === "symbol" ? key : String(key); } var omit$3 = function omit(obj, keys) { var keySet = new Set(keys); return Object.assign.apply(Object, [{}].concat(_toConsumableArray$k(Object.entries(obj).filter(function (_ref2) { var _ref3 = _slicedToArray$o(_ref2, 1), key = _ref3[0]; return !keySet.has(key); }).map(function (_ref4) { var _ref5 = _slicedToArray$o(_ref4, 2), key = _ref5[0], val = _ref5[1]; return _defineProperty$d({}, key, val); })))); }; function _arrayLikeToArray$p(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; } function _arrayWithHoles$n(r) { if (Array.isArray(r)) return r; } function _arrayWithoutHoles$j(r) { if (Array.isArray(r)) return _arrayLikeToArray$p(r); } function _iterableToArray$j(r) { if ("undefined" != typeof Symbol && null != r[Symbol.iterator] || null != r["@@iterator"]) return Array.from(r); } function _iterableToArrayLimit$n(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = true, o = false; try { if (i = (t = t.call(r)).next, 0 === l) ; else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = true, n = r; } finally { try { if (!f && null != t.return && (u = t.return(), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } } function _nonIterableRest$n() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } function _nonIterableSpread$j() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } function _slicedToArray$n(r, e) { return _arrayWithHoles$n(r) || _iterableToArrayLimit$n(r, e) || _unsupportedIterableToArray$p(r, e) || _nonIterableRest$n(); } function _toConsumableArray$j(r) { return _arrayWithoutHoles$j(r) || _iterableToArray$j(r) || _unsupportedIterableToArray$p(r) || _nonIterableSpread$j(); } function _unsupportedIterableToArray$p(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray$p(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray$p(r, a) : void 0; } } function index$i (kapsuleComponent) { var _ref = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}, _ref$wrapperElementTy = _ref.wrapperElementType, wrapperElementType = _ref$wrapperElementTy === void 0 ? 'div' : _ref$wrapperElementTy, _ref$nodeMapper = _ref.nodeMapper, nodeMapper = _ref$nodeMapper === void 0 ? function (node) { return node; } : _ref$nodeMapper, _ref$methodNames = _ref.methodNames, methodNames = _ref$methodNames === void 0 ? [] : _ref$methodNames, _ref$initPropNames = _ref.initPropNames, initPropNames = _ref$initPropNames === void 0 ? [] : _ref$initPropNames; return /*#__PURE__*/React.forwardRef(function (props, ref) { var domEl = React.useRef(); // instantiate the inner kapsule component with the defined initPropNames var comp = React.useMemo(function () { var configOptions = Object.fromEntries(initPropNames.filter(function (p) { return props.hasOwnProperty(p); }).map(function (prop) { return [prop, props[prop]]; })); return kapsuleComponent(configOptions); }, []); useEffectOnce$3(function () { comp(nodeMapper(domEl.current)); // mount kapsule synchronously on this element ref, optionally mapped into an object that the kapsule understands }, React.useLayoutEffect); useEffectOnce$3(function () { // invoke destructor on unmount, if it exists return comp._destructor instanceof Function ? comp._destructor : undefined; }); // Call a component method var _call = React.useCallback(function (method) { for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { args[_key - 1] = arguments[_key]; } return comp[method] instanceof Function ? comp[method].apply(comp, args) : undefined; } // method not found , [comp]); // propagate component props that have changed var prevPropsRef = React.useRef({}); Object.keys(omit$3(props, [].concat(_toConsumableArray$j(methodNames), _toConsumableArray$j(initPropNames)))) // initPropNames or methodNames should not be called .filter(function (p) { return prevPropsRef.current[p] !== props[p]; }).forEach(function (p) { return _call(p, props[p]); }); prevPropsRef.current = props; // bind external methods to parent ref React.useImperativeHandle(ref, function () { return Object.fromEntries(methodNames.map(function (method) { return [method, function () { for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { args[_key2] = arguments[_key2]; } return _call.apply(void 0, [method].concat(args)); }]; })); }, [_call]); return /*#__PURE__*/React.createElement(wrapperElementType, { ref: domEl }); }); } // // Handle R18 strict mode double mount at init function useEffectOnce$3(effect) { var useEffectFn = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : React.useEffect; var destroyFunc = React.useRef(); var effectCalled = React.useRef(false); var renderAfterCalled = React.useRef(false); var _useState = React.useState(0), _useState2 = _slicedToArray$n(_useState, 2); _useState2[0]; var setVal = _useState2[1]; if (effectCalled.current) { renderAfterCalled.current = true; } useEffectFn(function () { // only execute the effect first time around if (!effectCalled.current) { destroyFunc.current = effect(); effectCalled.current = true; } // this forces one render after the effect is run setVal(function (val) { return val + 1; }); return function () { // if the comp didn't render since the useEffect was called, // we know it's the dummy React cycle if (!renderAfterCalled.current) return; if (destroyFunc.current) destroyFunc.current(); }; }, []); } function getDefaultExportFromCjs (x) { return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x; } function getAugmentedNamespace(n) { if (n.__esModule) return n; var f = n.default; if (typeof f == "function") { var a = function a () { if (this instanceof a) { return Reflect.construct(f, arguments, this.constructor); } return f.apply(this, arguments); }; a.prototype = f.prototype; } else a = {}; Object.defineProperty(a, '__esModule', {value: true}); Object.keys(n).forEach(function (k) { var d = Object.getOwnPropertyDescriptor(n, k); Object.defineProperty(a, k, d.get ? d : { enumerable: true, get: function () { return n[k]; } }); }); return a; } var aframeExtras = {}; var controls = {}; var checkpointControls; var hasRequiredCheckpointControls; function requireCheckpointControls () { if (hasRequiredCheckpointControls) return checkpointControls; hasRequiredCheckpointControls = 1; const EPS = 0.1; checkpointControls = AFRAME.registerComponent('checkpoint-controls', { schema: { enabled: {default: true}, mode: {default: 'teleport', oneOf: ['teleport', 'animate']}, animateSpeed: {default: 3.0} }, init: function () { this.active = true; this.checkpoint = null; this.isNavMeshConstrained = false; this.offset = new THREE.Vector3(); this.position = new THREE.Vector3(); this.targetPosition = new THREE.Vector3(); }, play: function () { this.active = true; }, pause: function () { this.active = false; }, setCheckpoint: function (checkpoint) { const el = this.el; if (!this.active) return; if (this.checkpoint === checkpoint) return; if (this.checkpoint) { el.emit('navigation-end', {checkpoint: this.checkpoint}); } this.checkpoint = checkpoint; this.sync(); // Ignore new checkpoint if we're already there. if (this.position.distanceTo(this.targetPosition) < EPS) { this.checkpoint = null; return; } el.emit('navigation-start', {checkpoint: checkpoint}); if (this.data.mode === 'teleport') { this.el.setAttribute('position', this.targetPosition); this.checkpoint = null; el.emit('navigation-end', {checkpoint: checkpoint}); el.components['movement-controls'].updateNavLocation(); } }, isVelocityActive: function () { return !!(this.active && this.checkpoint); }, getVelocity: function () { if (!this.active) return; const data = this.data; const offset = this.offset; const position = this.position; const targetPosition = this.targetPosition; const checkpoint = this.checkpoint; this.sync(); if (position.distanceTo(targetPosition) < EPS) { this.checkpoint = null; this.el.emit('navigation-end', {checkpoint: checkpoint}); return offset.set(0, 0, 0); } offset.setLength(data.animateSpeed); return offset; }, sync: function () { const offset = this.offset; const position = this.position; const targetPosition = this.targetPosition; position.copy(this.el.getAttribute('position')); this.checkpoint.object3D.getWorldPosition(targetPosition); targetPosition.add(this.checkpoint.components.checkpoint.getOffset()); offset.copy(targetPosition).sub(position); } }); return checkpointControls; } var GamepadButton; var hasRequiredGamepadButton; function requireGamepadButton () { if (hasRequiredGamepadButton) return GamepadButton; hasRequiredGamepadButton = 1; GamepadButton = Object.assign(function GamepadButton () {}, { FACE_1: 0, FACE_2: 1, FACE_3: 2, FACE_4: 3, L_SHOULDER_1: 4, R_SHOULDER_1: 5, L_SHOULDER_2: 6, R_SHOULDER_2: 7, SELECT: 8, START: 9, DPAD_UP: 12, DPAD_DOWN: 13, DPAD_LEFT: 14, DPAD_RIGHT: 15, VENDOR: 16, }); return GamepadButton; } var GamepadButtonEvent_1; var hasRequiredGamepadButtonEvent; function requireGamepadButtonEvent () { if (hasRequiredGamepadButtonEvent) return GamepadButtonEvent_1; hasRequiredGamepadButtonEvent = 1; function GamepadButtonEvent (type, index, details) { this.type = type; this.index = index; this.pressed = details.pressed; this.value = details.value; } GamepadButtonEvent_1 = GamepadButtonEvent; return GamepadButtonEvent_1; } /** * Gamepad controls for A-Frame. * * Stripped-down version of: https://github.com/donmccurdy/aframe-gamepad-controls * * For more information about the Gamepad API, see: * https://developer.mozilla.org/en-US/docs/Web/API/Gamepad_API/Using_the_Gamepad_API */ var gamepadControls; var hasRequiredGamepadControls; function requireGamepadControls () { if (hasRequiredGamepadControls) return gamepadControls; hasRequiredGamepadControls = 1; const GamepadButton = requireGamepadButton(), GamepadButtonEvent = requireGamepadButtonEvent(); const JOYSTICK_EPS = 0.2; const Hand = { LEFT: 'left', RIGHT: 'right' }; const Joystick = { MOVEMENT: 1, ROTATION: 2 }; gamepadControls = AFRAME.registerComponent('gamepad-controls', { /******************************************************************* * Statics */ GamepadButton: GamepadButton, /******************************************************************* * Schema */ schema: { // Enable/disable gamepad-controls enabled: { default: true }, // Rotation sensitivity rotationSensitivity: { default: 2.0 }, }, /******************************************************************* * Core */ /** * Called once when component is attached. Generally for initial setup. */ init: function () { const sceneEl = this.el.sceneEl; // tracked-controls-webxr was renamed to tracked-controls in aframe 1.7.0 // tracked-controls-webxr is for aframe 1.6.0 and below this.system = sceneEl.systems['tracked-controls'] || sceneEl.systems['tracked-controls-webxr'] || {controllers: []}; this.prevTime = window.performance.now(); // Button state this.buttons = {}; // Rotation const rotation = this.el.object3D.rotation; this.pitch = new THREE.Object3D(); this.pitch.rotation.x = rotation.x; this.yaw = new THREE.Object3D(); this.yaw.position.y = 10; this.yaw.rotation.y = rotation.y; this.yaw.add(this.pitch); this._lookVector = new THREE.Vector2(); this._moveVector = new THREE.Vector2(); this._dpadVector = new THREE.Vector2(); sceneEl.addBehavior(this); }, /** * Called when component is attached and when component data changes. * Generally modifies the entity based on the data. */ update: function () { this.tick(); }, /** * Called on each iteration of main render loop. */ tick: function (t, dt) { this.updateButtonState(); this.updateRotation(dt); }, /** * Called when a component is removed (e.g., via removeAttribute). * Generally undoes all modifications to the entity. */ remove: function () { }, /******************************************************************* * Movement */ isVelocityActive: function () { if (!this.data.enabled || !this.isConnected()) return false; const dpad = this._dpadVector; const joystick = this._moveVector; this.getDpad(dpad); this.getJoystick(Joystick.MOVEMENT, joystick); const inputX = dpad.x || joystick.x; const inputY = dpad.y || joystick.y; return Math.abs(inputX) > JOYSTICK_EPS || Math.abs(inputY) > JOYSTICK_EPS; }, getVelocityDelta: function () { const dpad = this._dpadVector; const joystick = this._moveVector; this.getDpad(dpad); this.getJoystick(Joystick.MOVEMENT, joystick); const inputX = dpad.x || joystick.x; const inputY = dpad.y || joystick.y; const dVelocity = new THREE.Vector3(); if (Math.abs(inputX) > JOYSTICK_EPS) { dVelocity.x += inputX; } if (Math.abs(inputY) > JOYSTICK_EPS) { dVelocity.z += inputY; } return dVelocity; }, /******************************************************************* * Rotation */ isRotationActive: function () { if (!this.data.enabled || !this.isConnected()) return false; const joystick = this._lookVector; this.getJoystick(Joystick.ROTATION, joystick); return Math.abs(joystick.x) > JOYSTICK_EPS || Math.abs(joystick.y) > JOYSTICK_EPS; }, updateRotation: function (dt) { if (!this.isRotationActive()) return; const data = this.data; const yaw = this.yaw; const pitch = this.pitch; // First copy camera rig pitch/yaw, it may have been changed from // another component. yaw.rotation.y = this.el.object3D.rotation.y; pitch.rotation.x = this.el.object3D.rotation.x; const lookVector = this._lookVector; this.getJoystick(Joystick.ROTATION, lookVector); if (Math.abs(lookVector.x) <= JOYSTICK_EPS) lookVector.x = 0; if (Math.abs(lookVector.y) <= JOYSTICK_EPS) lookVector.y = 0; lookVector.multiplyScalar(data.rotationSensitivity * dt / 1000); yaw.rotation.y -= lookVector.x; pitch.rotation.x -= lookVector.y; pitch.rotation.x = Math.max(- Math.PI / 2, Math.min(Math.PI / 2, pitch.rotation.x)); this.el.object3D.rotation.set(pitch.rotation.x, yaw.rotation.y, 0); }, /******************************************************************* * Button events */ updateButtonState: function () { const gamepad = this.getGamepad(Hand.RIGHT); if (this.data.enabled && gamepad) { // Fire DOM events for button state changes. for (var i = 0; i < gamepad.buttons.length; i++) { if (gamepad.buttons[i].pressed && !this.buttons[i]) { this.emit(new GamepadButtonEvent('gamepadbuttondown', i, gamepad.buttons[i])); } else if (!gamepad.buttons[i].pressed && this.buttons[i]) { this.emit(new GamepadButtonEvent('gamepadbuttonup', i, gamepad.buttons[i])); } this.buttons[i] = gamepad.buttons[i].pressed; } } else { // Reset state if controls are disabled or controller is lost. for (const key in this.buttons) { this.buttons[key] = false; } } }, emit: function (event) { // Emit original event. this.el.emit(event.type, event); // Emit convenience event, identifying button index. this.el.emit( event.type + ':' + event.index, new GamepadButtonEvent(event.type, event.index, event) ); }, /******************************************************************* * Gamepad state */ /** * Returns the Gamepad instance attached to the component. If connected, * a proxy-controls component may provide access to Gamepad input from a * remote device. * * @param {string} handPreference * @return {Gamepad} */ getGamepad: (function () { const _xrGamepads = []; const _empty = []; return function (handPreference) { // https://github.com/donmccurdy/aframe-proxy-controls const proxyControls = this.el.sceneEl.components['proxy-controls']; const proxyGamepad = proxyControls && proxyControls.isConnected() && proxyControls.getGamepad(0); if (proxyGamepad) return proxyGamepad; // https://www.w3.org/TR/webxr/#dom-xrinputsource-handedness _xrGamepads.length = 0; for (let i = 0; i < this.system.controllers.length; i++) { const xrController = this.system.controllers[i]; const xrGamepad = xrController ? xrController.gamepad : null; _xrGamepads.push(xrGamepad); if (xrGamepad && xrController.handedness === handPreference) return xrGamepad; } // https://developer.mozilla.org/en-US/docs/Web/API/Gamepad/hand const navGamepads = navigator.getGamepads ? navigator.getGamepads() : _empty; for (let i = 0; i < navGamepads.length; i++) { const navGamepad = navGamepads[i]; if (navGamepad && navGamepad.hand === handPreference) return navGamepad; } return _xrGamepads[0] || navGamepads[0]; }; })(), /** * Returns the state of the given button. * @param {number} index The button (0-N) for which to find state. * @return {GamepadButton} */ getButton: function (index) { return this.getGamepad(Hand.RIGHT).buttons[index]; }, /** * Returns state of the given axis. Axes are labelled 0-N, where 0-1 will * represent X/Y on the first joystick, and 2-3 X/Y on the second. * @param {number} index The axis (0-N) for which to find state. * @return {number} On the interval [-1,1]. */ getAxis: function (index) { return this.getGamepad(index > 1 ? Hand.RIGHT : Hand.LEFT).axes[index]; }, /** * Returns the state of the specified joystick as a THREE.Vector2. * @param {Joystick} role * @param {THREE.Vector2} target * @return {THREE.Vector2} */ getJoystick: function (index, target) { const gamepad = this.getGamepad(index === Joystick.MOVEMENT ? Hand.LEFT : Hand.RIGHT); // gamepad can be null here if it becomes disconnected even if isConnected() was called // in the same tick before calling getJoystick. if (!gamepad) { return target.set(0, 0); } if (gamepad.mapping === 'xr-standard') { // See: https://github.com/donmccurdy/aframe-extras/issues/307 switch (index) { case Joystick.MOVEMENT: return target.set(gamepad.axes[2], gamepad.axes[3]); case Joystick.ROTATION: return target.set(gamepad.axes[2], 0); } } else { switch (index) { case Joystick.MOVEMENT: return target.set(gamepad.axes[0], gamepad.axes[1]); case Joystick.ROTATION: return target.set(gamepad.axes[2], gamepad.axes[3]); } } throw new Error('Unexpected joystick index "%d".', index); }, /** * Returns the state of the dpad as a THREE.Vector2. * @param {THREE.Vector2} target * @return {THREE.Vector2} */ getDpad: function (target) { const gamepad = this.getGamepad(Hand.LEFT); if (!gamepad) { return target.set(0, 0); } if (!gamepad.buttons[GamepadButton.DPAD_RIGHT]) { return target.set(0, 0); } return target.set( (gamepad.buttons[GamepadButton.DPAD_RIGHT].pressed ? 1 : 0) + (gamepad.buttons[GamepadButton.DPAD_LEFT].pressed ? -1 : 0), (gamepad.buttons[GamepadButton.DPAD_UP].pressed ? -1 : 0) + (gamepad.buttons[GamepadButton.DPAD_DOWN].pressed ? 1 : 0) ); }, /** * Returns true if the gamepad is currently connected to the system. * @return {boolean} */ isConnected: function () { const gamepad = this.getGamepad(Hand.LEFT); return !!(gamepad && gamepad.connected); }, /** * Returns a string containing some information about the controller. Result * may vary across browsers, for a given controller. * @return {string} */ getID: function () { return this.getGamepad(Hand.LEFT).id; } }); return gamepadControls; } var keyboard_polyfill = {}; /** * Polyfill for the additional KeyboardEvent properties defined in the D3E and * D4E draft specifications, by @inexorabletash. * * See: https://github.com/inexorabletash/polyfill */ var hasRequiredKeyboard_polyfill; function requireKeyboard_polyfill () { if (hasRequiredKeyboard_polyfill) return keyboard_polyfill; hasRequiredKeyboard_polyfill = 1; (function(global) { var nativeKeyboardEvent = ('KeyboardEvent' in global); if (!nativeKeyboardEvent) global.KeyboardEvent = function KeyboardEvent() { throw TypeError('Illegal constructor'); }; if (!('DOM_KEY_LOCATION_STANDARD' in global.KeyboardEvent)) global.KeyboardEvent.DOM_KEY_LOCATION_STANDARD = 0x00; // Default or unknown location if (!('DOM_KEY_LOCATION_LEFT' in global.KeyboardEvent)) global.KeyboardEvent.DOM_KEY_LOCATION_LEFT = 0x01; // e.g. Left Alt key if (!('DOM_KEY_LOCATION_RIGHT' in global.KeyboardEvent)) global.KeyboardEvent.DOM_KEY_LOCATION_RIGHT = 0x02; // e.g. Right Alt key if (!('DOM_KEY_LOCATION_NUMPAD' in global.KeyboardEvent)) global.KeyboardEvent.DOM_KEY_LOCATION_NUMPAD = 0x03; // e.g. Numpad 0 or + var STANDARD = window.KeyboardEvent.DOM_KEY_LOCATION_STANDARD, LEFT = window.KeyboardEvent.DOM_KEY_LOCATION_LEFT, RIGHT = window.KeyboardEvent.DOM_KEY_LOCATION_RIGHT, NUMPAD = window.KeyboardEvent.DOM_KEY_LOCATION_NUMPAD; //-------------------------------------------------------------------- // // Utilities // //-------------------------------------------------------------------- function contains(s, ss) { return String(s).indexOf(ss) !== -1; } var os = (function() { if (contains(navigator.platform, 'Win')) { return 'win'; } if (contains(navigator.platform, 'Mac')) { return 'mac'; } if (contains(navigator.platform, 'CrOS')) { return 'cros'; } if (contains(navigator.platform, 'Linux')) { return 'linux'; } if (contains(navigator.userAgent, 'iPad') || contains(navigator.platform, 'iPod') || contains(navigator.platform, 'iPhone')) { return 'ios'; } return ''; } ()); var browser = (function() { if (contains(navigator.userAgent, 'Chrome/')) { return 'chrome'; } if (contains(navigator.vendor, 'Apple')) { return 'safari'; } if (contains(navigator.userAgent, 'MSIE')) { return 'ie'; } if (contains(navigator.userAgent, 'Gecko/')) { return 'moz'; } if (contains(navigator.userAgent, 'Opera/')) { return 'opera'; } return ''; } ()); var browser_os = browser + '-' + os; function mergeIf(baseTable, select, table) { if (browser_os === select || browser === select || os === select) { Object.keys(table).forEach(function(keyCode) { baseTable[keyCode] = table[keyCode]; }); } } function remap(o, key) { var r = {}; Object.keys(o).forEach(function(k) { var item = o[k]; if (key in item) { r[item[key]] = item; } }); return r; } //-------------------------------------------------------------------- // // Generic Mappings // //-------------------------------------------------------------------- // "keyInfo" is a dictionary: // code: string - name from DOM Level 3 KeyboardEvent code Values // https://dvcs.w3.org/hg/dom3events/raw-file/tip/html/DOM3Events-code.html // location (optional): number - one of the DOM_KEY_LOCATION values // keyCap (optional): string - keyboard label in en-US locale // USB code Usage ID from page 0x07 unless otherwise noted (Informative) // Map of keyCode to keyInfo var keyCodeToInfoTable = { // 0x01 - VK_LBUTTON // 0x02 - VK_RBUTTON 0x03: { code: 'Cancel' }, // [USB: 0x9b] char \x0018 ??? (Not in D3E) // 0x04 - VK_MBUTTON // 0x05 - VK_XBUTTON1 // 0x06 - VK_XBUTTON2 0x06: { code: 'Help' }, // [USB: 0x75] ??? // 0x07 - undefined 0x08: { code: 'Backspace' }, // [USB: 0x2a] Labelled Delete on Macintosh keyboards. 0x09: { code: 'Tab' }, // [USB: 0x2b] // 0x0A-0x0B - reserved 0X0C: { code: 'Clear' }, // [USB: 0x9c] NumPad Center (Not in D3E) 0X0D: { code: 'Enter' }, // [USB: 0x28] // 0x0E-0x0F - undefined 0x10: { code: 'Shift' }, 0x11: { code: 'Control' }, 0x12: { code: 'Alt' }, 0x13: { code: 'Pause' }, // [USB: 0x48] 0x14: { code: 'CapsLock' }, // [USB: 0x39] 0x15: { code: 'KanaMode' }, // [USB: 0x88] - "HangulMode" for Korean layout 0x16: { code: 'HangulMode' }, // [USB: 0x90] 0x15 as well in MSDN VK table ??? 0x17: { code: 'JunjaMode' }, // (Not in D3E) 0x18: { code: 'FinalMode' }, // (Not in D3E) 0x19: { code: 'KanjiMode' }, // [USB: 0x91] - "HanjaMode" for Korean layout // 0x1A - undefined 0x1B: { code: 'Escape' }, // [USB: 0x29] 0x1C: { code: 'Convert' }, // [USB: 0x8a] 0x1D: { code: 'NonConvert' }, // [USB: 0x8b] 0x1E: { code: 'Accept' }, // (Not in D3E) 0x1F: { code: 'ModeChange' }, // (Not in D3E) 0x20: { code: 'Space' }, // [USB: 0x2c] 0x21: { code: 'PageUp' }, // [USB: 0x4b] 0x22: { code: 'PageDown' }, // [USB: 0x4e] 0x23: { code: 'End' }, // [USB: 0x4d] 0x24: { code: 'Home' }, // [USB: 0x4a] 0x25: { code: 'ArrowLeft' }, // [USB: 0x50] 0x26: { code: 'ArrowUp' }, // [USB: 0x52] 0x27: { code: 'ArrowRight' }, // [USB: 0x4f] 0x28: { code: 'ArrowDown' }, // [USB: 0x51] 0x29: { code: 'Select' }, // (Not in D3E) 0x2A: { code: 'Print' }, // (Not in D3E) 0x2B: { code: 'Execute' }, // [USB: 0x74] (Not in D3E) 0x2C: { code: 'PrintScreen' }, // [USB: 0x46] 0x2D: { code: 'Insert' }, // [USB: 0x49] 0x2E: { code: 'Delete' }, // [USB: 0x4c] 0x2F: { code: 'Help' }, // [USB: 0x75] ??? 0x30: { code: 'Digit0', keyCap: '0' }, // [USB: 0x27] 0) 0x31: { code: 'Digit1', keyCap: '1' }, // [USB: 0x1e] 1! 0x32: { code: 'Digit2', keyCap: '2' }, // [USB: 0x1f] 2@ 0x33: { code: 'Digit3', keyCap: '3' }, // [USB: 0x20] 3# 0x34: { code: 'Digit4', keyCap: '4' }, // [USB: 0x21] 4$ 0x35: { code: 'Digit5', keyCap: '5' }, // [USB: 0x22] 5% 0x36: { code: 'Digit6', keyCap: '6' }, // [USB: 0x23] 6^ 0x37: { code: 'Digit7', keyCap: '7' }, // [USB: 0x24] 7& 0x38: { code: 'Digit8', keyCap: '8' }, // [USB: 0x25] 8* 0x39: { code: 'Digit9', keyCap: '9' }, // [USB: 0x26] 9( // 0x3A-0x40 - undefined 0x41: { code: 'KeyA', keyCap: 'a' }, // [USB: 0x04] 0x42: { code: 'KeyB', keyCap: 'b' }, // [USB: 0x05] 0x43: { code: 'KeyC', keyCap: 'c' }, // [USB: 0x06] 0x44: { code: 'KeyD', keyCap: 'd' }, // [USB: 0x07] 0x45: { code: 'KeyE', keyCap: 'e' }, // [USB: 0x08] 0x46: { code: 'KeyF', keyCap: 'f' }, // [USB: 0x09] 0x47: { code: 'KeyG', keyCap: 'g' }, // [USB: 0x0a] 0x48: { code: 'KeyH', keyCap: 'h' }, // [USB: 0x0b] 0x49: { code: 'KeyI', keyCap: 'i' }, // [USB: 0x0c] 0x4A: { code: 'KeyJ', keyCap: 'j' }, // [USB: 0x0d] 0x4B: { code: 'KeyK', keyCap: 'k' }, // [USB: 0x0e] 0x4C: { code: 'KeyL', keyCap: 'l' }, // [USB: 0x0f] 0x4D: { code: 'KeyM', keyCap: 'm' }, // [USB: 0x10] 0x4E: { code: 'KeyN', keyCap: 'n' }, // [USB: 0x11] 0x4F: { code: 'KeyO', keyCap: 'o' }, // [USB: 0x12] 0x50: { code: 'KeyP', keyCap: 'p' }, // [USB: 0x13] 0x51: { code: 'KeyQ', keyCap: 'q' }, // [USB: 0x14] 0x52: { code: 'KeyR', keyCap: 'r' }, // [USB: 0x15] 0x53: { code: 'KeyS', keyCap: 's' }, // [USB: 0x16] 0x54: { code: 'KeyT', keyCap: 't' }, // [USB: 0x17] 0x55: { code: 'KeyU', keyCap: 'u' }, // [USB: 0x18] 0x56: { code: 'KeyV', keyCap: 'v' }, // [USB: 0x19] 0x57: { code: 'KeyW', keyCap: 'w' }, // [USB: 0x1a] 0x58: { code: 'KeyX', keyCap: 'x' }, // [USB: 0x1b] 0x59: { code: 'KeyY', keyCap: 'y' }, // [USB: 0x1c] 0x5A: { code: 'KeyZ', keyCap: 'z' }, // [USB: 0x1d] 0x5B: { code: 'OSLeft', location: LEFT }, // [USB: 0xe3] 0x5C: { code: 'OSRight', location: RIGHT }, // [USB: 0xe7] 0x5D: { code: 'ContextMenu' }, // [USB: 0x65] Context Menu // 0x5E - reserved 0x5F: { code: 'Standby' }, // [USB: 0x82] Sleep 0x60: { code: 'Numpad0', keyCap: '0', location: NUMPAD }, // [USB: 0x62] 0x61: { code: 'Numpad1', keyCap: '1', location: NUMPAD }, // [USB: 0x59] 0x62: { code: 'Numpad2', keyCap: '2', location: NUMPAD }, // [USB: 0x5a] 0x63: { code: 'Numpad3', keyCap: '3', location: NUMPAD }, // [USB: 0x5b] 0x64: { code: 'Numpad4', keyCap: '4', location: NUMPAD }, // [USB: 0x5c] 0x65: { code: 'Numpad5', keyCap: '5', location: NUMPAD }, // [USB: 0x5d] 0x66: { code: 'Numpad6', keyCap: '6', location: NUMPAD }, // [USB: 0x5e] 0x67: { code: 'Numpad7', keyCap: '7', location: NUMPAD }, // [USB: 0x5f] 0x68: { code: 'Numpad8', keyCap: '8', location: NUMPAD }, // [USB: 0x60] 0x69: { code: 'Numpad9', keyCap: '9', location: NUMPAD }, // [USB: 0x61] 0x6A: { code: 'NumpadMultiply', keyCap: '*', location: NUMPAD }, // [USB: 0x55] 0x6B: { code: 'NumpadAdd', keyCap: '+', location: NUMPAD }, // [USB: 0x57] 0x6C: { code: 'NumpadComma', keyCap: ',', location: NUMPAD }, // [USB: 0x85] 0x6D: { code: 'NumpadSubtract', keyCap: '-', location: NUMPAD }, // [USB: 0x56] 0x6E: { code: 'NumpadDecimal', keyCap: '.', location: NUMPAD }, // [USB: 0x63] 0x6F: { code: 'NumpadDivide', keyCap: '/', location: NUMPAD }, // [USB: 0x54] 0x70: { code: 'F1' }, // [USB: 0x3a] 0x71: { code: 'F2' }, // [USB: 0x3b] 0x72: { code: 'F3' }, // [USB: 0x3c] 0x73: { code: 'F4' }, // [USB: 0x3d] 0x74: { code: 'F5' }, // [USB: 0x3e] 0x75: { code: 'F6' }, // [USB: 0x3f] 0x76: { code: 'F7' }, // [USB: 0x40] 0x77: { code: 'F8' }, // [USB: 0x41] 0x78: { code: 'F9' }, // [USB: 0x42] 0x79: { code: 'F10' }, // [USB: 0x43] 0x7A: { code: 'F11' }, // [USB: 0x44] 0x7B: { code: 'F12' }, // [USB: 0x45] 0x7C: { code: 'F13' }, // [USB: 0x68] 0x7D: { code: 'F14' }, // [USB: 0x69] 0x7E: { code: 'F15' }, // [USB: 0x6a] 0x7F: { code: 'F16' }, // [USB: 0x6b] 0x80: { code: 'F17' }, // [USB: 0x6c] 0x81: { code: 'F18' }, // [USB: 0x6d] 0x82: { code: 'F19' }, // [USB: 0x6e] 0x83: { code: 'F20' }, // [USB: 0x6f] 0x84: { code: 'F21' }, // [USB: 0x70] 0x85: { code: 'F22' }, // [USB: 0x71] 0x86: { code: 'F23' }, // [USB: 0x72] 0x87: { code: 'F24' }, // [USB: 0x73] // 0x88-0x8F - unassigned 0x90: { code: 'NumLock', location: NUMPAD }, // [USB: 0x53] 0x91: { code: 'ScrollLock' }, // [USB: 0x47] // 0x92-0x96 - OEM specific // 0x97-0x9F - unassigned // NOTE: 0xA0-0xA5 usually mapped to 0x10-0x12 in browsers 0xA0: { code: 'ShiftLeft', location: LEFT }, // [USB: 0xe1] 0xA1: { code: 'ShiftRight', location: RIGHT }, // [USB: 0xe5] 0xA2: { code: 'ControlLeft', location: LEFT }, // [USB: 0xe0] 0xA3: { code: 'ControlRight', location: RIGHT }, // [USB: 0xe4] 0xA4: { code: 'AltLeft', location: LEFT }, // [USB: 0xe2] 0xA5: { code: 'AltRight', location: RIGHT }, // [USB: 0xe6] 0xA6: { code: 'BrowserBack' }, // [USB: 0x0c/0x0224] 0xA7: { code: 'BrowserForward' }, // [USB: 0x0c/0x0225] 0xA8: { code: 'BrowserRefresh' }, // [USB: 0x0c/0x0227] 0xA9: { code: 'BrowserStop' }, // [USB: 0x0c/0x0226] 0xAA: { code: 'BrowserSearch' }, // [USB: 0x0c/0x0221] 0xAB: { code: 'BrowserFavorites' }, // [USB: 0x0c/0x0228] 0xAC: { code: 'BrowserHome' }, // [USB: 0x0c/0x0222] 0xAD: { code: 'VolumeMute' }, // [USB: 0x7f] 0xAE: { code: 'VolumeDown' }, // [USB: 0x81] 0xAF: { code: 'VolumeUp' }, // [USB: 0x80] 0xB0: { code: 'MediaTrackNext' }, // [USB: 0x0c/0x00b5] 0xB1: { code: 'MediaTrackPrevious' }, // [USB: 0x0c/0x00b6] 0xB2: { code: 'MediaStop' }, // [USB: 0x0c/0x00b7] 0xB3: { code: 'MediaPlayPause' }, // [USB: 0x0c/0x00cd] 0xB4: { code: 'LaunchMail' }, // [USB: 0x0c/0x018a] 0xB5: { code: 'MediaSelect' }, 0xB6: { code: 'LaunchApp1' }, 0xB7: { code: 'LaunchApp2' }, // 0xB8-0xB9 - reserved 0xBA: { code: 'Semicolon', keyCap: ';' }, // [USB: 0x33] ;: (US Standard 101) 0xBB: { code: 'Equal', keyCap: '=' }, // [USB: 0x2e] =+ 0xBC: { code: 'Comma', keyCap: ',' }, // [USB: 0x36] ,< 0xBD: { code: 'Minus', keyCap: '-' }, // [USB: 0x2d] -_ 0xBE: { code: 'Period', keyCap: '.' }, // [USB: 0x37] .> 0xBF: { code: 'Slash', keyCap: '/' }, // [USB: 0x38] /? (US Standard 101) 0xC0: { code: 'Backquote', keyCap: '`' }, // [USB: 0x35] `~ (US Standard 101) // 0xC1-0xCF - reserved // 0xD0-0xD7 - reserved // 0xD8-0xDA - unassigned 0xDB: { code: 'BracketLeft', keyCap: '[' }, // [USB: 0x2f] [{ (US Standard 101) 0xDC: { code: 'Backslash', keyCap: '\\' }, // [USB: 0x31] \| (US Standard 101) 0xDD: { code: 'BracketRight', keyCap: ']' }, // [USB: 0x30] ]} (US Standard 101) 0xDE: { code: 'Quote', keyCap: '\'' }, // [USB: 0x34] '" (US Standard 101) // 0xDF - miscellaneous/varies // 0xE0 - reserved // 0xE1 - OEM specific 0xE2: { code: 'IntlBackslash', keyCap: '\\' }, // [USB: 0x64] \| (UK Standard 102) // 0xE3-0xE4 - OEM specific 0xE5: { code: 'Process' }, // (Not in D3E) // 0xE6 - OEM specific // 0xE7 - VK_PACKET // 0xE8 - unassigned // 0xE9-0xEF - OEM specific // 0xF0-0xF5 - OEM specific 0xF6: { code: 'Attn' }, // [USB: 0x9a] (Not in D3E) 0xF7: { code: 'CrSel' }, // [USB: 0xa3] (Not in D3E) 0xF8: { code: 'ExSel' }, // [USB: 0xa4] (Not in D3E) 0xF9: { code: 'EraseEof' }, // (Not in D3E) 0xFA: { code: 'Play' }, // (Not in D3E) 0xFB: { code: 'ZoomToggle' }, // (Not in D3E) // 0xFC - VK_NONAME - reserved // 0xFD - VK_PA1 0xFE: { code: 'Clear' } // [USB: 0x9c] (Not in D3E) }; // No legacy keyCode, but listed in D3E: // code: usb // 'IntlHash': 0x070032, // 'IntlRo': 0x070087, // 'IntlYen': 0x070089, // 'NumpadBackspace': 0x0700bb, // 'NumpadClear': 0x0700d8, // 'NumpadClearEntry': 0x0700d9, // 'NumpadMemoryAdd': 0x0700d3, // 'NumpadMemoryClear': 0x0700d2, // 'NumpadMemoryRecall': 0x0700d1, // 'NumpadMemoryStore': 0x0700d0, // 'NumpadMemorySubtract': 0x0700d4, // 'NumpadParenLeft': 0x0700b6, // 'NumpadParenRight': 0x0700b7, //-------------------------------------------------------------------- // // Browser/OS Specific Mappings // //-------------------------------------------------------------------- mergeIf(keyCodeToInfoTable, 'moz', { 0x3B: { code: 'Semicolon', keyCap: ';' }, // [USB: 0x33] ;: (US Standard 101) 0x3D: { code: 'Equal', keyCap: '=' }, // [USB: 0x2e] =+ 0x6B: { code: 'Equal', keyCap: '=' }, // [USB: 0x2e] =+ 0x6D: { code: 'Minus', keyCap: '-' }, // [USB: 0x2d] -_ 0xBB: { code: 'NumpadAdd', keyCap: '+', location: NUMPAD }, // [USB: 0x57] 0xBD: { code: 'NumpadSubtract', keyCap: '-', location: NUMPAD } // [USB: 0x56] }); mergeIf(keyCodeToInfoTable, 'moz-mac', { 0x0C: { code: 'NumLock', location: NUMPAD }, // [USB: 0x53] 0xAD: { code: 'Minus', keyCap: '-' } // [USB: 0x2d] -_ }); mergeIf(keyCodeToInfoTable, 'moz-win', { 0xAD: { code: 'Minus', keyCap: '-' } // [USB: 0x2d] -_ }); mergeIf(keyCodeToInfoTable, 'chrome-mac', { 0x5D: { code: 'OSRight', location: RIGHT } // [USB: 0xe7] }); mergeIf(keyCodeToInfoTable, 'safari', { 0x03: { code: 'Enter' }, // [USB: 0x28] old Safari 0x19: { code: 'Tab' } // [USB: 0x2b] old Safari for Shift+Tab }); mergeIf(keyCodeToInfoTable, 'ios', { 0x0A: { code: 'Enter', location: STANDARD } // [USB: 0x28] }); mergeIf(keyCodeToInfoTable, 'safari-mac', { 0x5B: { code: 'OSLeft', location: LEFT }, // [USB: 0xe3] 0x5D: { code: 'OSRight', location: RIGHT }, // [USB: 0xe7] 0xE5: { code: 'KeyQ', keyCap: 'Q' } // [USB: 0x14] On alternate presses, Ctrl+Q sends this }); //-------------------------------------------------------------------- // // Identifier Mappings // //-------------------------------------------------------------------- // Cases where newer-ish browsers send keyIdentifier which can be // used to disambiguate keys. // keyIdentifierTable[keyIdentifier] -> keyInfo var keyIdentifierTable = {}; if ('cros' === os) { keyIdentifierTable['U+00A0'] = { code: 'ShiftLeft', location: LEFT }; keyIdentifierTable['U+00A1'] = { code: 'ShiftRight', location: RIGHT }; keyIdentifierTable['U+00A2'] = { code: 'ControlLeft', location: LEFT }; keyIdentifierTable['U+00A3'] = { code: 'ControlRight', location: RIGHT }; keyIdentifierTable['U+00A4'] = { code: 'AltLeft', location: LEFT }; keyIdentifierTable['U+00A5'] = { code: 'AltRight', location: RIGHT }; } if ('chrome-mac' === browser_os) { keyIdentifierTable['U+0010'] = { code: 'ContextMenu' }; } if ('safari-mac' === browser_os) { keyIdentifierTable['U+0010'] = { code: 'ContextMenu' }; } if ('ios' === os) { // These only generate keyup events keyIdentifierTable['U+0010'] = { code: 'Function' }; keyIdentifierTable['U+001C'] = { code: 'ArrowLeft' }; keyIdentifierTable['U+001D'] = { code: 'ArrowRight' }; keyIdentifierTable['U+001E'] = { code: 'ArrowUp' }; keyIdentifierTable['U+001F'] = { code: 'ArrowDown' }; keyIdentifierTable['U+0001'] = { code: 'Home' }; // [USB: 0x4a] Fn + ArrowLeft keyIdentifierTable['U+0004'] = { code: 'End' }; // [USB: 0x4d] Fn + ArrowRight keyIdentifierTable['U+000B'] = { code: 'PageUp' }; // [USB: 0x4b] Fn + ArrowUp keyIdentifierTable['U+000C'] = { code: 'PageDown' }; // [USB: 0x4e] Fn + ArrowDown } //-------------------------------------------------------------------- // // Location Mappings // //-------------------------------------------------------------------- // Cases where newer-ish browsers send location/keyLocation which // can be used to disambiguate keys. // locationTable[location][keyCode] -> keyInfo var locationTable = []; locationTable[LEFT] = { 0x10: { code: 'ShiftLeft', location: LEFT }, // [USB: 0xe1] 0x11: { code: 'ControlLeft', location: LEFT }, // [USB: 0xe0] 0x12: { code: 'AltLeft', location: LEFT } // [USB: 0xe2] }; locationTable[RIGHT] = { 0x10: { code: 'ShiftRight', location: RIGHT }, // [USB: 0xe5] 0x11: { code: 'ControlRight', location: RIGHT }, // [USB: 0xe4] 0x12: { code: 'AltRight', location: RIGHT } // [USB: 0xe6] }; locationTable[NUMPAD] = { 0x0D: { code: 'NumpadEnter', location: NUMPAD } // [USB: 0x58] }; mergeIf(locationTable[NUMPAD], 'moz', { 0x6D: { code: 'NumpadSubtract', location: NUMPAD }, // [USB: 0x56] 0x6B: { code: 'NumpadAdd', location: NUMPAD } // [USB: 0x57] }); mergeIf(locationTable[LEFT], 'moz-mac', { 0xE0: { code: 'OSLeft', location: LEFT } // [USB: 0xe3] }); mergeIf(locationTable[RIGHT], 'moz-mac', { 0xE0: { code: 'OSRight', location: RIGHT } // [USB: 0xe7] }); mergeIf(locationTable[RIGHT], 'moz-win', { 0x5B: { code: 'OSRight', location: RIGHT } // [USB: 0xe7] }); mergeIf(locationTable[RIGHT], 'mac', { 0x5D: { code: 'OSRight', location: RIGHT } // [USB: 0xe7] }); mergeIf(locationTable[NUMPAD], 'chrome-mac', { 0x0C: { code: 'NumLock', location: NUMPAD } // [USB: 0x53] }); mergeIf(locationTable[NUMPAD], 'safari-mac', { 0x0C: { code: 'NumLock', location: NUMPAD }, // [USB: 0x53] 0xBB: { code: 'NumpadAdd', location: NUMPAD }, // [USB: 0x57] 0xBD: { code: 'NumpadSubtract', location: NUMPAD }, // [USB: 0x56] 0xBE: { code: 'NumpadDecimal', location: NUMPAD }, // [USB: 0x63] 0xBF: { code: 'NumpadDivide', location: NUMPAD } // [USB: 0x54] }); //-------------------------------------------------------------------- // // Key Values // //-------------------------------------------------------------------- // Mapping from `code` values to `key` values. Values defined at: // https://dvcs.w3.org/hg/dom3events/raw-file/tip/html/DOM3Events-key.html // Entries are only provided when `key` differs from `code`. If // printable, `shiftKey` has the shifted printable character. This // assumes US Standard 101 layout var codeToKeyTable = { // Modifier Keys ShiftLeft: { key: 'Shift' }, ShiftRight: { key: 'Shift' }, ControlLeft: { key: 'Control' }, ControlRight: { key: 'Control' }, AltLeft: { key: 'Alt' }, AltRight: { key: 'Alt' }, OSLeft: { key: 'OS' }, OSRight: { key: 'OS' }, // Whitespace Keys NumpadEnter: { key: 'Enter' }, Space: { key: ' ' }, // Printable Keys Digit0: { key: '0', shiftKey: ')' }, Digit1: { key: '1', shiftKey: '!' }, Digit2: { key: '2', shiftKey: '@' }, Digit3: { key: '3', shiftKey: '#' }, Digit4: { key: '4', shiftKey: '$' }, Digit5: { key: '5', shiftKey: '%' }, Digit6: { key: '6', shiftKey: '^' }, Digit7: { key: '7', shiftKey: '&' }, Digit8: { key: '8', shiftKey: '*' }, Digit9: { key: '9', shiftKey: '(' }, KeyA: { key: 'a', shiftKey: 'A' }, KeyB: { key: 'b', shiftKey: 'B' }, KeyC: { key: 'c', shiftKey: 'C' }, KeyD: { key: 'd', shiftKey: 'D' }, KeyE: { key: 'e', shiftKey: 'E' }, KeyF: { key: 'f', shiftKey: 'F' }, KeyG: { key: 'g', shiftKey: 'G' }, KeyH: { key: 'h', shiftKey: 'H' }, KeyI: { key: 'i', shiftKey: 'I' }, KeyJ: { key: 'j', shiftKey: 'J' }, KeyK: { key: 'k', shiftKey: 'K' }, KeyL: { key: 'l', shiftKey: 'L' }, KeyM: { key: 'm', shiftKey: 'M' }, KeyN: