react-force-graph
Version:
React component for 2D, 3D, VR and AR force directed graphs
1,317 lines (1,148 loc) • 5.27 MB
JavaScript
// 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: