what-input.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480
  1. /**
  2. * what-input - A global utility for tracking the current input method (mouse, keyboard or touch).
  3. * @version v5.1.2
  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. // boolean: true if touch buffer is active
  127. };var isBuffering = false;
  128. // boolean: true if the page is being scrolled
  129. var isScrolling = false;
  130. // store current mouse position
  131. var mousePos = {
  132. x: null,
  133. y: null
  134. // map of IE 10 pointer events
  135. };var pointerMap = {
  136. 2: 'touch',
  137. 3: 'touch', // treat pen like touch
  138. 4: 'mouse'
  139. // check support for passive event listeners
  140. };var supportsPassive = false;
  141. try {
  142. var opts = Object.defineProperty({}, 'passive', {
  143. get: function get() {
  144. supportsPassive = true;
  145. }
  146. });
  147. window.addEventListener('test', null, opts);
  148. } catch (e) {}
  149. /*
  150. * set up
  151. */
  152. var setUp = function setUp() {
  153. // add correct mouse wheel event mapping to `inputMap`
  154. inputMap[detectWheel()] = 'mouse';
  155. addListeners();
  156. doUpdate('input');
  157. doUpdate('intent');
  158. };
  159. /*
  160. * events
  161. */
  162. var addListeners = function addListeners() {
  163. // `pointermove`, `MSPointerMove`, `mousemove` and mouse wheel event binding
  164. // can only demonstrate potential, but not actual, interaction
  165. // and are treated separately
  166. var options = supportsPassive ? { passive: true } : false;
  167. // pointer events (mouse, pen, touch)
  168. if (window.PointerEvent) {
  169. window.addEventListener('pointerdown', setInput);
  170. window.addEventListener('pointermove', setIntent);
  171. } else if (window.MSPointerEvent) {
  172. window.addEventListener('MSPointerDown', setInput);
  173. window.addEventListener('MSPointerMove', setIntent);
  174. } else {
  175. // mouse events
  176. window.addEventListener('mousedown', setInput);
  177. window.addEventListener('mousemove', setIntent);
  178. // touch events
  179. if ('ontouchstart' in window) {
  180. window.addEventListener('touchstart', eventBuffer, options);
  181. window.addEventListener('touchend', setInput);
  182. }
  183. }
  184. // mouse wheel
  185. window.addEventListener(detectWheel(), setIntent, options);
  186. // keyboard events
  187. window.addEventListener('keydown', eventBuffer);
  188. window.addEventListener('keyup', eventBuffer);
  189. // focus events
  190. window.addEventListener('focusin', setElement);
  191. window.addEventListener('focusout', clearElement);
  192. };
  193. // checks conditions before updating new input
  194. var setInput = function setInput(event) {
  195. // only execute if the event buffer timer isn't running
  196. if (!isBuffering) {
  197. var eventKey = event.which;
  198. var value = inputMap[event.type];
  199. if (value === 'pointer') {
  200. value = pointerType(event);
  201. }
  202. var ignoreMatch = !specificMap.length && ignoreMap.indexOf(eventKey) === -1;
  203. var specificMatch = specificMap.length && specificMap.indexOf(eventKey) !== -1;
  204. var shouldUpdate = value === 'keyboard' && eventKey && (ignoreMatch || specificMatch) || value === 'mouse' || value === 'touch';
  205. if (currentInput !== value && shouldUpdate) {
  206. currentInput = value;
  207. try {
  208. window.sessionStorage.setItem('what-input', currentInput);
  209. } catch (e) {}
  210. doUpdate('input');
  211. }
  212. if (currentIntent !== value && shouldUpdate) {
  213. // preserve intent for keyboard typing in form fields
  214. var activeElem = document.activeElement;
  215. var notFormInput = activeElem && activeElem.nodeName && formInputs.indexOf(activeElem.nodeName.toLowerCase()) === -1;
  216. if (notFormInput) {
  217. currentIntent = value;
  218. try {
  219. window.sessionStorage.setItem('what-intent', currentIntent);
  220. } catch (e) {}
  221. doUpdate('intent');
  222. }
  223. }
  224. }
  225. };
  226. // updates the doc and `inputTypes` array with new input
  227. var doUpdate = function doUpdate(which) {
  228. docElem.setAttribute('data-what' + which, which === 'input' ? currentInput : currentIntent);
  229. fireFunctions(which);
  230. };
  231. // updates input intent for `mousemove` and `pointermove`
  232. var setIntent = function setIntent(event) {
  233. // test to see if `mousemove` happened relative to the screen to detect scrolling versus mousemove
  234. detectScrolling(event);
  235. // only execute if the event buffer timer isn't running
  236. // or scrolling isn't happening
  237. if (!isBuffering && !isScrolling) {
  238. var value = inputMap[event.type];
  239. if (value === 'pointer') {
  240. value = pointerType(event);
  241. }
  242. if (currentIntent !== value) {
  243. currentIntent = value;
  244. try {
  245. window.sessionStorage.setItem('what-intent', currentIntent);
  246. } catch (e) {}
  247. doUpdate('intent');
  248. }
  249. }
  250. };
  251. var setElement = function setElement(event) {
  252. if (!event.target.nodeName) {
  253. // If nodeName is undefined, clear the element
  254. // This can happen if click inside an <svg> element.
  255. clearElement();
  256. return;
  257. }
  258. currentElement = event.target.nodeName.toLowerCase();
  259. docElem.setAttribute('data-whatelement', currentElement);
  260. if (event.target.classList && event.target.classList.length) {
  261. docElem.setAttribute('data-whatclasses', event.target.classList.toString().replace(' ', ','));
  262. }
  263. };
  264. var clearElement = function clearElement() {
  265. currentElement = null;
  266. docElem.removeAttribute('data-whatelement');
  267. docElem.removeAttribute('data-whatclasses');
  268. };
  269. // buffers events that frequently also fire mouse events
  270. var eventBuffer = function eventBuffer(event) {
  271. // set the current input
  272. setInput(event);
  273. // clear the timer if it happens to be running
  274. window.clearTimeout(eventTimer);
  275. // set the isBuffering to `true`
  276. isBuffering = true;
  277. // run the timer
  278. eventTimer = window.setTimeout(function () {
  279. // if the timer runs out, set isBuffering back to `false`
  280. isBuffering = false;
  281. }, 100);
  282. };
  283. /*
  284. * utilities
  285. */
  286. var pointerType = function pointerType(event) {
  287. if (typeof event.pointerType === 'number') {
  288. return pointerMap[event.pointerType];
  289. } else {
  290. // treat pen like touch
  291. return event.pointerType === 'pen' ? 'touch' : event.pointerType;
  292. }
  293. };
  294. // detect version of mouse wheel event to use
  295. // via https://developer.mozilla.org/en-US/docs/Web/Events/wheel
  296. var detectWheel = function detectWheel() {
  297. var wheelType = void 0;
  298. // Modern browsers support "wheel"
  299. if ('onwheel' in document.createElement('div')) {
  300. wheelType = 'wheel';
  301. } else {
  302. // Webkit and IE support at least "mousewheel"
  303. // or assume that remaining browsers are older Firefox
  304. wheelType = document.onmousewheel !== undefined ? 'mousewheel' : 'DOMMouseScroll';
  305. }
  306. return wheelType;
  307. };
  308. // runs callback functions
  309. var fireFunctions = function fireFunctions(type) {
  310. for (var i = 0, len = functionList.length; i < len; i++) {
  311. if (functionList[i].type === type) {
  312. functionList[i].fn.call(undefined, type === 'input' ? currentInput : currentIntent);
  313. }
  314. }
  315. };
  316. // finds matching element in an object
  317. var objPos = function objPos(match) {
  318. for (var i = 0, len = functionList.length; i < len; i++) {
  319. if (functionList[i].fn === match) {
  320. return i;
  321. }
  322. }
  323. };
  324. var detectScrolling = function detectScrolling(event) {
  325. if (mousePos['x'] !== event.screenX || mousePos['y'] !== event.screenY) {
  326. isScrolling = false;
  327. mousePos['x'] = event.screenX;
  328. mousePos['y'] = event.screenY;
  329. } else {
  330. isScrolling = true;
  331. }
  332. };
  333. /*
  334. * init
  335. */
  336. // don't start script unless browser cuts the mustard
  337. // (also passes if polyfills are used)
  338. if ('addEventListener' in window && Array.prototype.indexOf) {
  339. setUp();
  340. }
  341. /*
  342. * api
  343. */
  344. return {
  345. // returns string: the current input type
  346. // opt: 'intent'|'input'
  347. // 'input' (default): returns the same value as the `data-whatinput` attribute
  348. // 'intent': includes `data-whatintent` value if it's different than `data-whatinput`
  349. ask: function ask(opt) {
  350. return opt === 'intent' ? currentIntent : currentInput;
  351. },
  352. // returns string: the currently focused element or null
  353. element: function element() {
  354. return currentElement;
  355. },
  356. // overwrites ignored keys with provided array
  357. ignoreKeys: function ignoreKeys(arr) {
  358. ignoreMap = arr;
  359. },
  360. // overwrites specific char keys to update on
  361. specificKeys: function specificKeys(arr) {
  362. specificMap = arr;
  363. },
  364. // attach functions to input and intent "events"
  365. // funct: function to fire on change
  366. // eventType: 'input'|'intent'
  367. registerOnChange: function registerOnChange(fn, eventType) {
  368. functionList.push({
  369. fn: fn,
  370. type: eventType || 'input'
  371. });
  372. },
  373. unRegisterOnChange: function unRegisterOnChange(fn) {
  374. var position = objPos(fn);
  375. if (position || position === 0) {
  376. functionList.splice(position, 1);
  377. }
  378. }
  379. };
  380. }();
  381. /***/ })
  382. /******/ ])
  383. });
  384. ;