what-input.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481
  1. /**
  2. * what-input - A global utility for tracking the current input method (mouse, keyboard or touch).
  3. * @version v5.1.4
  4. * @link https://github.com/ten1seven/what-input
  5. * @license MIT
  6. */
  7. (function webpackUniversalModuleDefinition(root, factory) {
  8. if(typeof exports === 'object' && typeof module === 'object')
  9. module.exports = factory();
  10. else if(typeof define === 'function' && define.amd)
  11. define("whatInput", [], factory);
  12. else if(typeof exports === 'object')
  13. exports["whatInput"] = factory();
  14. else
  15. root["whatInput"] = factory();
  16. })(this, function() {
  17. return /******/ (function(modules) { // webpackBootstrap
  18. /******/ // The module cache
  19. /******/ var installedModules = {};
  20. /******/ // The require function
  21. /******/ function __webpack_require__(moduleId) {
  22. /******/ // Check if module is in cache
  23. /******/ if(installedModules[moduleId])
  24. /******/ return installedModules[moduleId].exports;
  25. /******/ // Create a new module (and put it into the cache)
  26. /******/ var module = installedModules[moduleId] = {
  27. /******/ exports: {},
  28. /******/ id: moduleId,
  29. /******/ loaded: false
  30. /******/ };
  31. /******/ // Execute the module function
  32. /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
  33. /******/ // Flag the module as loaded
  34. /******/ module.loaded = true;
  35. /******/ // Return the exports of the module
  36. /******/ return module.exports;
  37. /******/ }
  38. /******/ // expose the modules object (__webpack_modules__)
  39. /******/ __webpack_require__.m = modules;
  40. /******/ // expose the module cache
  41. /******/ __webpack_require__.c = installedModules;
  42. /******/ // __webpack_public_path__
  43. /******/ __webpack_require__.p = "";
  44. /******/ // Load entry module and return exports
  45. /******/ return __webpack_require__(0);
  46. /******/ })
  47. /************************************************************************/
  48. /******/ ([
  49. /* 0 */
  50. /***/ (function(module, exports) {
  51. 'use strict';
  52. module.exports = function () {
  53. /*
  54. * bail out if there is no document or window
  55. * (i.e. in a node/non-DOM environment)
  56. *
  57. * Return a stubbed API instead
  58. */
  59. if (typeof document === 'undefined' || typeof window === 'undefined') {
  60. return {
  61. // always return "initial" because no interaction will ever be detected
  62. ask: function ask() {
  63. return 'initial';
  64. },
  65. // always return null
  66. element: function element() {
  67. return null;
  68. },
  69. // no-op
  70. ignoreKeys: function ignoreKeys() {},
  71. // no-op
  72. specificKeys: function specificKeys() {},
  73. // no-op
  74. registerOnChange: function registerOnChange() {},
  75. // no-op
  76. unRegisterOnChange: function unRegisterOnChange() {}
  77. };
  78. }
  79. /*
  80. * variables
  81. */
  82. // cache document.documentElement
  83. var docElem = document.documentElement;
  84. // currently focused dom element
  85. var currentElement = null;
  86. // last used input type
  87. var currentInput = 'initial';
  88. // last used input intent
  89. var currentIntent = currentInput;
  90. // check for sessionStorage support
  91. // then check for session variables and use if available
  92. try {
  93. if (window.sessionStorage.getItem('what-input')) {
  94. currentInput = window.sessionStorage.getItem('what-input');
  95. }
  96. if (window.sessionStorage.getItem('what-intent')) {
  97. currentIntent = window.sessionStorage.getItem('what-intent');
  98. }
  99. } catch (e) {}
  100. // event buffer timer
  101. var eventTimer = null;
  102. // form input types
  103. var formInputs = ['input', 'select', 'textarea'];
  104. // empty array for holding callback functions
  105. var functionList = [];
  106. // list of modifier keys commonly used with the mouse and
  107. // can be safely ignored to prevent false keyboard detection
  108. var ignoreMap = [16, // shift
  109. 17, // control
  110. 18, // alt
  111. 91, // Windows key / left Apple cmd
  112. 93 // Windows menu / right Apple cmd
  113. ];
  114. var specificMap = [];
  115. // mapping of events to input types
  116. var inputMap = {
  117. keydown: 'keyboard',
  118. keyup: 'keyboard',
  119. mousedown: 'mouse',
  120. mousemove: 'mouse',
  121. MSPointerDown: 'pointer',
  122. MSPointerMove: 'pointer',
  123. pointerdown: 'pointer',
  124. pointermove: 'pointer',
  125. touchstart: 'touch',
  126. touchend: 'touch'
  127. // boolean: true if touch buffer is active
  128. };var isBuffering = false;
  129. // boolean: true if the page is being scrolled
  130. var isScrolling = false;
  131. // store current mouse position
  132. var mousePos = {
  133. x: null,
  134. y: null
  135. // map of IE 10 pointer events
  136. };var pointerMap = {
  137. 2: 'touch',
  138. 3: 'touch', // treat pen like touch
  139. 4: 'mouse'
  140. // check support for passive event listeners
  141. };var supportsPassive = false;
  142. try {
  143. var opts = Object.defineProperty({}, 'passive', {
  144. get: function get() {
  145. supportsPassive = true;
  146. }
  147. });
  148. window.addEventListener('test', null, opts);
  149. } catch (e) {}
  150. /*
  151. * set up
  152. */
  153. var setUp = function setUp() {
  154. // add correct mouse wheel event mapping to `inputMap`
  155. inputMap[detectWheel()] = 'mouse';
  156. addListeners();
  157. doUpdate('input');
  158. doUpdate('intent');
  159. };
  160. /*
  161. * events
  162. */
  163. var addListeners = function addListeners() {
  164. // `pointermove`, `MSPointerMove`, `mousemove` and mouse wheel event binding
  165. // can only demonstrate potential, but not actual, interaction
  166. // and are treated separately
  167. var options = supportsPassive ? { passive: true } : false;
  168. // pointer events (mouse, pen, touch)
  169. if (window.PointerEvent) {
  170. window.addEventListener('pointerdown', setInput);
  171. window.addEventListener('pointermove', setIntent);
  172. } else if (window.MSPointerEvent) {
  173. window.addEventListener('MSPointerDown', setInput);
  174. window.addEventListener('MSPointerMove', setIntent);
  175. } else {
  176. // mouse events
  177. window.addEventListener('mousedown', setInput);
  178. window.addEventListener('mousemove', setIntent);
  179. // touch events
  180. if ('ontouchstart' in window) {
  181. window.addEventListener('touchstart', eventBuffer, options);
  182. window.addEventListener('touchend', setInput);
  183. }
  184. }
  185. // mouse wheel
  186. window.addEventListener(detectWheel(), setIntent, options);
  187. // keyboard events
  188. window.addEventListener('keydown', eventBuffer);
  189. window.addEventListener('keyup', eventBuffer);
  190. // focus events
  191. window.addEventListener('focusin', setElement);
  192. window.addEventListener('focusout', clearElement);
  193. };
  194. // checks conditions before updating new input
  195. var setInput = function setInput(event) {
  196. // only execute if the event buffer timer isn't running
  197. if (!isBuffering) {
  198. var eventKey = event.which;
  199. var value = inputMap[event.type];
  200. if (value === 'pointer') {
  201. value = pointerType(event);
  202. }
  203. var ignoreMatch = !specificMap.length && ignoreMap.indexOf(eventKey) === -1;
  204. var specificMatch = specificMap.length && specificMap.indexOf(eventKey) !== -1;
  205. var shouldUpdate = value === 'keyboard' && eventKey && (ignoreMatch || specificMatch) || value === 'mouse' || value === 'touch';
  206. if (currentInput !== value && shouldUpdate) {
  207. currentInput = value;
  208. try {
  209. window.sessionStorage.setItem('what-input', currentInput);
  210. } catch (e) {}
  211. doUpdate('input');
  212. }
  213. if (currentIntent !== value && shouldUpdate) {
  214. // preserve intent for keyboard typing in form fields
  215. var activeElem = document.activeElement;
  216. var notFormInput = activeElem && activeElem.nodeName && formInputs.indexOf(activeElem.nodeName.toLowerCase()) === -1;
  217. if (notFormInput) {
  218. currentIntent = value;
  219. try {
  220. window.sessionStorage.setItem('what-intent', currentIntent);
  221. } catch (e) {}
  222. doUpdate('intent');
  223. }
  224. }
  225. }
  226. };
  227. // updates the doc and `inputTypes` array with new input
  228. var doUpdate = function doUpdate(which) {
  229. docElem.setAttribute('data-what' + which, which === 'input' ? currentInput : currentIntent);
  230. fireFunctions(which);
  231. };
  232. // updates input intent for `mousemove` and `pointermove`
  233. var setIntent = function setIntent(event) {
  234. // test to see if `mousemove` happened relative to the screen to detect scrolling versus mousemove
  235. detectScrolling(event);
  236. // only execute if the event buffer timer isn't running
  237. // or scrolling isn't happening
  238. if (!isBuffering && !isScrolling) {
  239. var value = inputMap[event.type];
  240. if (value === 'pointer') {
  241. value = pointerType(event);
  242. }
  243. if (currentIntent !== value) {
  244. currentIntent = value;
  245. try {
  246. window.sessionStorage.setItem('what-intent', currentIntent);
  247. } catch (e) {}
  248. doUpdate('intent');
  249. }
  250. }
  251. };
  252. var setElement = function setElement(event) {
  253. if (!event.target.nodeName) {
  254. // If nodeName is undefined, clear the element
  255. // This can happen if click inside an <svg> element.
  256. clearElement();
  257. return;
  258. }
  259. currentElement = event.target.nodeName.toLowerCase();
  260. docElem.setAttribute('data-whatelement', currentElement);
  261. if (event.target.classList && event.target.classList.length) {
  262. docElem.setAttribute('data-whatclasses', event.target.classList.toString().replace(' ', ','));
  263. }
  264. };
  265. var clearElement = function clearElement() {
  266. currentElement = null;
  267. docElem.removeAttribute('data-whatelement');
  268. docElem.removeAttribute('data-whatclasses');
  269. };
  270. // buffers events that frequently also fire mouse events
  271. var eventBuffer = function eventBuffer(event) {
  272. // set the current input
  273. setInput(event);
  274. // clear the timer if it happens to be running
  275. window.clearTimeout(eventTimer);
  276. // set the isBuffering to `true`
  277. isBuffering = true;
  278. // run the timer
  279. eventTimer = window.setTimeout(function () {
  280. // if the timer runs out, set isBuffering back to `false`
  281. isBuffering = false;
  282. }, 120);
  283. };
  284. /*
  285. * utilities
  286. */
  287. var pointerType = function pointerType(event) {
  288. if (typeof event.pointerType === 'number') {
  289. return pointerMap[event.pointerType];
  290. } else {
  291. // treat pen like touch
  292. return event.pointerType === 'pen' ? 'touch' : event.pointerType;
  293. }
  294. };
  295. // detect version of mouse wheel event to use
  296. // via https://developer.mozilla.org/en-US/docs/Web/Events/wheel
  297. var detectWheel = function detectWheel() {
  298. var wheelType = void 0;
  299. // Modern browsers support "wheel"
  300. if ('onwheel' in document.createElement('div')) {
  301. wheelType = 'wheel';
  302. } else {
  303. // Webkit and IE support at least "mousewheel"
  304. // or assume that remaining browsers are older Firefox
  305. wheelType = document.onmousewheel !== undefined ? 'mousewheel' : 'DOMMouseScroll';
  306. }
  307. return wheelType;
  308. };
  309. // runs callback functions
  310. var fireFunctions = function fireFunctions(type) {
  311. for (var i = 0, len = functionList.length; i < len; i++) {
  312. if (functionList[i].type === type) {
  313. functionList[i].fn.call(undefined, type === 'input' ? currentInput : currentIntent);
  314. }
  315. }
  316. };
  317. // finds matching element in an object
  318. var objPos = function objPos(match) {
  319. for (var i = 0, len = functionList.length; i < len; i++) {
  320. if (functionList[i].fn === match) {
  321. return i;
  322. }
  323. }
  324. };
  325. var detectScrolling = function detectScrolling(event) {
  326. if (mousePos['x'] !== event.screenX || mousePos['y'] !== event.screenY) {
  327. isScrolling = false;
  328. mousePos['x'] = event.screenX;
  329. mousePos['y'] = event.screenY;
  330. } else {
  331. isScrolling = true;
  332. }
  333. };
  334. /*
  335. * init
  336. */
  337. // don't start script unless browser cuts the mustard
  338. // (also passes if polyfills are used)
  339. if ('addEventListener' in window && Array.prototype.indexOf) {
  340. setUp();
  341. }
  342. /*
  343. * api
  344. */
  345. return {
  346. // returns string: the current input type
  347. // opt: 'intent'|'input'
  348. // 'input' (default): returns the same value as the `data-whatinput` attribute
  349. // 'intent': includes `data-whatintent` value if it's different than `data-whatinput`
  350. ask: function ask(opt) {
  351. return opt === 'intent' ? currentIntent : currentInput;
  352. },
  353. // returns string: the currently focused element or null
  354. element: function element() {
  355. return currentElement;
  356. },
  357. // overwrites ignored keys with provided array
  358. ignoreKeys: function ignoreKeys(arr) {
  359. ignoreMap = arr;
  360. },
  361. // overwrites specific char keys to update on
  362. specificKeys: function specificKeys(arr) {
  363. specificMap = arr;
  364. },
  365. // attach functions to input and intent "events"
  366. // funct: function to fire on change
  367. // eventType: 'input'|'intent'
  368. registerOnChange: function registerOnChange(fn, eventType) {
  369. functionList.push({
  370. fn: fn,
  371. type: eventType || 'input'
  372. });
  373. },
  374. unRegisterOnChange: function unRegisterOnChange(fn) {
  375. var position = objPos(fn);
  376. if (position || position === 0) {
  377. functionList.splice(position, 1);
  378. }
  379. }
  380. };
  381. }();
  382. /***/ })
  383. /******/ ])
  384. });
  385. ;