foundation.util.keyboard.js 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. /*******************************************
  2. * *
  3. * This util was created by Marius Olbertz *
  4. * Please thank Marius on GitHub /owlbertz *
  5. * or the web http://www.mariusolbertz.de/ *
  6. * *
  7. ******************************************/
  8. 'use strict';
  9. import $ from 'jquery';
  10. import { rtl as Rtl } from './foundation.core.utils';
  11. const keyCodes = {
  12. 9: 'TAB',
  13. 13: 'ENTER',
  14. 27: 'ESCAPE',
  15. 32: 'SPACE',
  16. 35: 'END',
  17. 36: 'HOME',
  18. 37: 'ARROW_LEFT',
  19. 38: 'ARROW_UP',
  20. 39: 'ARROW_RIGHT',
  21. 40: 'ARROW_DOWN'
  22. }
  23. var commands = {}
  24. // Functions pulled out to be referenceable from internals
  25. function findFocusable($element) {
  26. if(!$element) {return false; }
  27. return $element.find('a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), iframe, object, embed, *[tabindex], *[contenteditable]').filter(function() {
  28. if (!$(this).is(':visible') || $(this).attr('tabindex') < 0) { return false; } //only have visible elements and those that have a tabindex greater or equal 0
  29. return true;
  30. });
  31. }
  32. function parseKey(event) {
  33. var key = keyCodes[event.which || event.keyCode] || String.fromCharCode(event.which).toUpperCase();
  34. // Remove un-printable characters, e.g. for `fromCharCode` calls for CTRL only events
  35. key = key.replace(/\W+/, '');
  36. if (event.shiftKey) key = `SHIFT_${key}`;
  37. if (event.ctrlKey) key = `CTRL_${key}`;
  38. if (event.altKey) key = `ALT_${key}`;
  39. // Remove trailing underscore, in case only modifiers were used (e.g. only `CTRL_ALT`)
  40. key = key.replace(/_$/, '');
  41. return key;
  42. }
  43. var Keyboard = {
  44. keys: getKeyCodes(keyCodes),
  45. /**
  46. * Parses the (keyboard) event and returns a String that represents its key
  47. * Can be used like Foundation.parseKey(event) === Foundation.keys.SPACE
  48. * @param {Event} event - the event generated by the event handler
  49. * @return String key - String that represents the key pressed
  50. */
  51. parseKey: parseKey,
  52. /**
  53. * Handles the given (keyboard) event
  54. * @param {Event} event - the event generated by the event handler
  55. * @param {String} component - Foundation component's name, e.g. Slider or Reveal
  56. * @param {Objects} functions - collection of functions that are to be executed
  57. */
  58. handleKey(event, component, functions) {
  59. var commandList = commands[component],
  60. keyCode = this.parseKey(event),
  61. cmds,
  62. command,
  63. fn;
  64. if (!commandList) return console.warn('Component not defined!');
  65. if (typeof commandList.ltr === 'undefined') { // this component does not differentiate between ltr and rtl
  66. cmds = commandList; // use plain list
  67. } else { // merge ltr and rtl: if document is rtl, rtl overwrites ltr and vice versa
  68. if (Rtl()) cmds = $.extend({}, commandList.ltr, commandList.rtl);
  69. else cmds = $.extend({}, commandList.rtl, commandList.ltr);
  70. }
  71. command = cmds[keyCode];
  72. fn = functions[command];
  73. if (fn && typeof fn === 'function') { // execute function if exists
  74. var returnValue = fn.apply();
  75. if (functions.handled || typeof functions.handled === 'function') { // execute function when event was handled
  76. functions.handled(returnValue);
  77. }
  78. } else {
  79. if (functions.unhandled || typeof functions.unhandled === 'function') { // execute function when event was not handled
  80. functions.unhandled();
  81. }
  82. }
  83. },
  84. /**
  85. * Finds all focusable elements within the given `$element`
  86. * @param {jQuery} $element - jQuery object to search within
  87. * @return {jQuery} $focusable - all focusable elements within `$element`
  88. */
  89. findFocusable: findFocusable,
  90. /**
  91. * Returns the component name name
  92. * @param {Object} component - Foundation component, e.g. Slider or Reveal
  93. * @return String componentName
  94. */
  95. register(componentName, cmds) {
  96. commands[componentName] = cmds;
  97. },
  98. // TODO9438: These references to Keyboard need to not require global. Will 'this' work in this context?
  99. //
  100. /**
  101. * Traps the focus in the given element.
  102. * @param {jQuery} $element jQuery object to trap the foucs into.
  103. */
  104. trapFocus($element) {
  105. var $focusable = findFocusable($element),
  106. $firstFocusable = $focusable.eq(0),
  107. $lastFocusable = $focusable.eq(-1);
  108. $element.on('keydown.zf.trapfocus', function(event) {
  109. if (event.target === $lastFocusable[0] && parseKey(event) === 'TAB') {
  110. event.preventDefault();
  111. $firstFocusable.focus();
  112. }
  113. else if (event.target === $firstFocusable[0] && parseKey(event) === 'SHIFT_TAB') {
  114. event.preventDefault();
  115. $lastFocusable.focus();
  116. }
  117. });
  118. },
  119. /**
  120. * Releases the trapped focus from the given element.
  121. * @param {jQuery} $element jQuery object to release the focus for.
  122. */
  123. releaseFocus($element) {
  124. $element.off('keydown.zf.trapfocus');
  125. }
  126. }
  127. /*
  128. * Constants for easier comparing.
  129. * Can be used like Foundation.parseKey(event) === Foundation.keys.SPACE
  130. */
  131. function getKeyCodes(kcs) {
  132. var k = {};
  133. for (var kc in kcs) k[kcs[kc]] = kcs[kc];
  134. return k;
  135. }
  136. export {Keyboard};