window.whatInput = (function() { 'use strict'; /* --------------- variables --------------- */ // array of actively pressed keys var activeKeys = []; // cache document.body var body; // boolean: true if touch buffer timer is running var buffer = false; // the last used input type var currentInput = null; // `input` types that don't accept text var nonTypingInputs = [ 'button', 'checkbox', 'file', 'image', 'radio', 'reset', 'submit' ]; // detect version of mouse wheel event to use // via https://developer.mozilla.org/en-US/docs/Web/Events/wheel var mouseWheel = detectWheel(); // list of modifier keys commonly used with the mouse and // can be safely ignored to prevent false keyboard detection var ignoreMap = [ 16, // shift 17, // control 18, // alt 91, // Windows key / left Apple cmd 93 // Windows menu / right Apple cmd ]; // mapping of events to input types var inputMap = { 'keydown': 'keyboard', 'keyup': 'keyboard', 'mousedown': 'mouse', 'mousemove': 'mouse', 'MSPointerDown': 'pointer', 'MSPointerMove': 'pointer', 'pointerdown': 'pointer', 'pointermove': 'pointer', 'touchstart': 'touch' }; // add correct mouse wheel event mapping to `inputMap` inputMap[detectWheel()] = 'mouse'; // array of all used input types var inputTypes = []; // mapping of key codes to a common name var keyMap = { 9: 'tab', 13: 'enter', 16: 'shift', 27: 'esc', 32: 'space', 37: 'left', 38: 'up', 39: 'right', 40: 'down' }; // map of IE 10 pointer events var pointerMap = { 2: 'touch', 3: 'touch', // treat pen like touch 4: 'mouse' }; // touch buffer timer var timer; /* --------------- functions --------------- */ // allows events that are also triggered to be filtered out for `touchstart` function eventBuffer() { clearTimer(); setInput(event); buffer = true; timer = window.setTimeout(function() { buffer = false; }, 650); } function bufferedEvent(event) { if (!buffer) setInput(event); } function unBufferedEvent(event) { clearTimer(); setInput(event); } function clearTimer() { window.clearTimeout(timer); } function setInput(event) { var eventKey = key(event); var value = inputMap[event.type]; if (value === 'pointer') value = pointerType(event); // don't do anything if the value matches the input type already set if (currentInput !== value) { var eventTarget = target(event); var eventTargetNode = eventTarget.nodeName.toLowerCase(); var eventTargetType = (eventTargetNode === 'input') ? eventTarget.getAttribute('type') : null; if ( (// only if the user flag to allow typing in form fields isn't set !body.hasAttribute('data-whatinput-formtyping') && // only if currentInput has a value currentInput && // only if the input is `keyboard` value === 'keyboard' && // not if the key is `TAB` keyMap[eventKey] !== 'tab' && // only if the target is a form input that accepts text ( eventTargetNode === 'textarea' || eventTargetNode === 'select' || (eventTargetNode === 'input' && nonTypingInputs.indexOf(eventTargetType) < 0) )) || ( // ignore modifier keys ignoreMap.indexOf(eventKey) > -1 ) ) { // ignore keyboard typing } else { switchInput(value); } } if (value === 'keyboard') logKeys(eventKey); } function switchInput(string) { currentInput = string; body.setAttribute('data-whatinput', currentInput); if (inputTypes.indexOf(currentInput) === -1) inputTypes.push(currentInput); } function key(event) { return (event.keyCode) ? event.keyCode : event.which; } function target(event) { return event.target || event.srcElement; } function pointerType(event) { if (typeof event.pointerType === 'number') { return pointerMap[event.pointerType]; } else { return (event.pointerType === 'pen') ? 'touch' : event.pointerType; // treat pen like touch } } // keyboard logging function logKeys(eventKey) { if (activeKeys.indexOf(keyMap[eventKey]) === -1 && keyMap[eventKey]) activeKeys.push(keyMap[eventKey]); } function unLogKeys(event) { var eventKey = key(event); var arrayPos = activeKeys.indexOf(keyMap[eventKey]); if (arrayPos !== -1) activeKeys.splice(arrayPos, 1); } function bindEvents() { body = document.body; // pointer events (mouse, pen, touch) if (window.PointerEvent) { body.addEventListener('pointerdown', bufferedEvent); body.addEventListener('pointermove', bufferedEvent); } else if (window.MSPointerEvent) { body.addEventListener('MSPointerDown', bufferedEvent); body.addEventListener('MSPointerMove', bufferedEvent); } else { // mouse events body.addEventListener('mousedown', bufferedEvent); body.addEventListener('mousemove', bufferedEvent); // touch events if ('ontouchstart' in window) { body.addEventListener('touchstart', eventBuffer); } } // mouse wheel body.addEventListener(mouseWheel, bufferedEvent); // keyboard events body.addEventListener('keydown', unBufferedEvent); body.addEventListener('keyup', unBufferedEvent); document.addEventListener('keyup', unLogKeys); } /* --------------- utilities --------------- */ // detect version of mouse wheel event to use // via https://developer.mozilla.org/en-US/docs/Web/Events/wheel function detectWheel() { return mouseWheel = 'onwheel' in document.createElement('div') ? 'wheel' : // Modern browsers support "wheel" document.onmousewheel !== undefined ? 'mousewheel' : // Webkit and IE support at least "mousewheel" 'DOMMouseScroll'; // let's assume that remaining browsers are older Firefox } /* --------------- init don't start script unless browser cuts the mustard, also passes if polyfills are used --------------- */ if ( 'addEventListener' in window && Array.prototype.indexOf ) { // if the dom is already ready already (script was placed at bottom of ) if (document.body) { bindEvents(); // otherwise wait for the dom to load (script was placed in the ) } else { document.addEventListener('DOMContentLoaded', bindEvents); } } /* --------------- api --------------- */ return { // returns string: the current input type ask: function() { return currentInput; }, // returns array: currently pressed keys keys: function() { return activeKeys; }, // returns array: all the detected input types types: function() { return inputTypes; }, // accepts string: manually set the input type set: switchInput }; }());