UNPKG

3d-force-graph-vr

Version:

UI component for a 3D force-directed graph in VR

1,344 lines (1,160 loc) 756 kB
// Version 3.1.1 3d-force-graph-vr - https://github.com/vasturiano/3d-force-graph-vr (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('three')) : typeof define === 'function' && define.amd ? define(['three'], factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.ForceGraphVR = factory(global.THREE)); })(this, (function (three$2) { 'use strict'; function styleInject(css, ref) { if ( ref === void 0 ) ref = {}; var insertAt = ref.insertAt; if (typeof document === 'undefined') { return; } var head = document.head || document.getElementsByTagName('head')[0]; var style = document.createElement('style'); style.type = 'text/css'; if (insertAt === 'top') { if (head.firstChild) { head.insertBefore(style, head.firstChild); } else { head.appendChild(style); } } else { head.appendChild(style); } if (style.styleSheet) { style.styleSheet.cssText = css; } else { style.appendChild(document.createTextNode(css)); } } var css_248z = ".graph-nav-info {\n position: absolute;\n bottom: 5px;\n width: 100%;\n text-align: center;\n color: slategrey;\n opacity: 0.7;\n font-size: 10px;\n font-family: Sans-serif;\n z-index: 1000;\n}"; styleInject(css_248z); function _arrayLikeToArray$3(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$3(r) { if (Array.isArray(r)) return r; } function _arrayWithoutHoles$2(r) { if (Array.isArray(r)) return _arrayLikeToArray$3(r); } function _defineProperty$1(e, r, t) { return (r = _toPropertyKey$2(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: true, configurable: true, writable: true }) : e[r] = t, e; } function _iterableToArray$2(r) { if ("undefined" != typeof Symbol && null != r[Symbol.iterator] || null != r["@@iterator"]) return Array.from(r); } function _iterableToArrayLimit$3(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$3() { 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$2() { 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 ownKeys$1(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; } function _objectSpread2$1(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys$1(Object(t), true).forEach(function (r) { _defineProperty$1(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys$1(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; } function _slicedToArray$3(r, e) { return _arrayWithHoles$3(r) || _iterableToArrayLimit$3(r, e) || _unsupportedIterableToArray$3(r, e) || _nonIterableRest$3(); } function _toConsumableArray$2(r) { return _arrayWithoutHoles$2(r) || _iterableToArray$2(r) || _unsupportedIterableToArray$3(r) || _nonIterableSpread$2(); } function _toPrimitive$2(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } function _toPropertyKey$2(t) { var i = _toPrimitive$2(t, "string"); return "symbol" == typeof i ? i : i + ""; } function _unsupportedIterableToArray$3(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray$3(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$3(r, a) : void 0; } } function getDefaultExportFromCjs (x) { return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x; } function getAugmentedNamespace(n) { if (Object.prototype.hasOwnProperty.call(n, '__esModule')) return n; var f = n.default; if (typeof f == "function") { var a = function a () { var isInstance = false; try { isInstance = this instanceof a; } catch {} if (isInstance) { 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: { key: 'n', shiftKey: 'N' }, KeyO: { key: 'o', shiftKey: 'O' }, KeyP: { key: 'p', shiftKey: 'P' }, KeyQ: { key: 'q', shiftKey: 'Q' }, KeyR: { key: 'r', shiftKey: 'R' }, KeyS: { key: 's', shiftKey: 'S' }, KeyT: { key: 't', shiftKey: 'T' }, KeyU: { key: 'u', shiftKey: 'U' }, KeyV: { key: 'v', shiftKey: 'V' }, KeyW: { key: 'w', shiftKey: 'W' }, KeyX: { key: 'x', shiftKey: 'X' }, KeyY: { key: 'y', shiftKey: 'Y' }, KeyZ: { key: 'z', shiftKey: 'Z' }, Numpad0: { key: '0' }, Numpad1: { key: '1' }, Numpad2: { key: '2' }, Numpad3: { key: '3' }, Numpad4: { key: '4' }, Numpad5: { key: '5' }, Numpad6: { key: '6' }, Numpad7: { key: '7' }, Numpad8: { key: '8' }, Numpad9: { key: '9' }, NumpadMultiply: { key: '*' }, NumpadAdd: { key: '+' }, NumpadComma: { key: ',' }, NumpadSubtract: { key: '-' }, NumpadDecimal: { key: '.' }, NumpadDivide: { key: '/' }, Semicolon: { key: ';', shiftKey: ':' }, Equal: { key: '=', shiftKey: '+' }, Comma: { key: ',', shiftKey: '<' }, Minus: { key: '-', shiftKey: '_' }, Period: { key: '.', shiftKey: '>' }, Slash: { key: '/', shiftKey: '?' }, Backquote: { key: '`', shiftKey: '~' }, BracketLeft: { key: '[', shiftKey: '{' }, Backslash: { key: '\\', shiftKey: '|' }, BracketRight: { key: ']', shiftKey: '}' }, Quote: { key: '\'', shiftKey: '"' }, IntlBackslash: { key: '\\', shiftKey: '|' } }; mergeIf(codeToKeyTable, 'mac', { OSLeft: { key: 'Meta' }, OSRight: { key: 'Meta' } }); // Corrections for 'key' names in older browsers (e.g. FF36-) // https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent.key#Key_values var keyFixTable = { Esc: 'Escape', Nonconvert: 'NonConvert', Left: 'ArrowLeft', Up: 'ArrowUp', Right: 'ArrowRight', Down: 'ArrowDown', Del: 'Delete', Menu: 'ContextMenu', MediaNextTrack: 'MediaTrackNext', MediaPreviousTrack: 'MediaTrackPrevious', SelectMedia: 'MediaSelect', HalfWidth: 'Hankaku', FullWidth: 'Zenkaku', RomanCharacters: 'Romaji', Crsel: 'CrSel', Exsel: 'ExSel', Zoom: 'ZoomToggle' }; //-------------------------------------------------------------------- // // Exported Functions // //-------------------------------------------------------------------- var codeTable = remap(keyCodeToInfoTable, 'code'); try { var nativeLocation = nativeKeyboardEvent && ('location' in new KeyboardEvent('')); } catch (_) {} function keyInfoForEvent(event) { var keyCode = 'keyCode' in event ? event.keyCode : 'which' in event ? event.which : 0; var keyInfo = (function(){ if (nativeLocation || 'keyLocation' in event) { var location = nativeLocation ? event.location : event.keyLocation; if (location && keyCode in locationTable[location]) { return locationTable[location][keyCode]; } } if ('keyIdentifier' in event && event.keyIdentifier in keyIdentifierTable) { return keyIdentifierTable[event.keyIdentifier]; } if (keyCode in keyCodeToInfoTable) { return keyCodeToInfoTable[keyCode]; } return null; }()); if (!keyInfo) return null; var key = (function() { var entry = codeToKeyTable[keyInfo.code]; if (!entry) return keyInfo.code; return (event.shiftKey && 'shiftKey' in entry) ? entry.shiftKey : entry.key; }()); return { code: keyInfo.code, key: key, location: keyInfo.location, keyCap: keyInfo.keyCap }; } function queryKeyCap(code, locale) { code = String(code); if (!codeTable.hasOwnProperty(code)) return 'Undefined'; if (locale && String(locale).toLowerCase() !== 'en-us') throw Error('Unsupported locale'); var keyInfo = codeTable[code]; return keyInfo.keyCap || keyInfo.code || 'Undefined'; } if ('KeyboardEvent' in global && 'defineProperty' in Object) { (function() { function define(o, p, v) { if (p in o) return; Object.defineProperty(o, p, v); } define(KeyboardEvent.prototype, 'code', { get: function() { var keyInfo = keyInfoForEvent(this); return keyInfo ? keyInfo.code : ''; }}); // Fix for nonstandard `key` values (FF36-) if ('key' in KeyboardEvent.prototype) { var desc = Object.getOwnPropertyDescriptor(KeyboardEvent.prototype, 'key'); Object.defineProperty(KeyboardEvent.prototype, 'key', { get: function() { var key = desc.get.call(this); return keyFixTable.hasOwnProperty(key) ? keyFixTable[key] : key; }}); } define(KeyboardEvent.prototype, 'key', { get: function() { var keyInfo = keyInfoForEvent(this); return (keyInfo && 'key' in keyInfo) ? keyInfo.key : 'Unidentified'; }}); define(KeyboardEvent.prototype, 'location', { get: function() { var keyInfo = keyInfoForEvent(this); return (keyInfo && 'location' in keyInfo) ? keyInfo.location : STANDARD;