163 lines
5.1 KiB
JavaScript
163 lines
5.1 KiB
JavaScript
|
/*******************************************
|
||
|
* *
|
||
|
* This util was created by Marius Olbertz *
|
||
|
* Please thank Marius on GitHub /owlbertz *
|
||
|
* or the web http://www.mariusolbertz.de/ *
|
||
|
* *
|
||
|
******************************************/
|
||
|
|
||
|
'use strict';
|
||
|
|
||
|
import $ from 'jquery';
|
||
|
import { rtl as Rtl } from './foundation.core.utils';
|
||
|
|
||
|
const keyCodes = {
|
||
|
9: 'TAB',
|
||
|
13: 'ENTER',
|
||
|
27: 'ESCAPE',
|
||
|
32: 'SPACE',
|
||
|
35: 'END',
|
||
|
36: 'HOME',
|
||
|
37: 'ARROW_LEFT',
|
||
|
38: 'ARROW_UP',
|
||
|
39: 'ARROW_RIGHT',
|
||
|
40: 'ARROW_DOWN'
|
||
|
}
|
||
|
|
||
|
var commands = {}
|
||
|
|
||
|
// Functions pulled out to be referenceable from internals
|
||
|
function findFocusable($element) {
|
||
|
if(!$element) {return false; }
|
||
|
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() {
|
||
|
if (!$(this).is(':visible') || $(this).attr('tabindex') < 0) { return false; } //only have visible elements and those that have a tabindex greater or equal 0
|
||
|
return true;
|
||
|
});
|
||
|
}
|
||
|
|
||
|
function parseKey(event) {
|
||
|
var key = keyCodes[event.which || event.keyCode] || String.fromCharCode(event.which).toUpperCase();
|
||
|
|
||
|
// Remove un-printable characters, e.g. for `fromCharCode` calls for CTRL only events
|
||
|
key = key.replace(/\W+/, '');
|
||
|
|
||
|
if (event.shiftKey) key = `SHIFT_${key}`;
|
||
|
if (event.ctrlKey) key = `CTRL_${key}`;
|
||
|
if (event.altKey) key = `ALT_${key}`;
|
||
|
|
||
|
// Remove trailing underscore, in case only modifiers were used (e.g. only `CTRL_ALT`)
|
||
|
key = key.replace(/_$/, '');
|
||
|
|
||
|
return key;
|
||
|
}
|
||
|
|
||
|
var Keyboard = {
|
||
|
keys: getKeyCodes(keyCodes),
|
||
|
|
||
|
/**
|
||
|
* Parses the (keyboard) event and returns a String that represents its key
|
||
|
* Can be used like Foundation.parseKey(event) === Foundation.keys.SPACE
|
||
|
* @param {Event} event - the event generated by the event handler
|
||
|
* @return String key - String that represents the key pressed
|
||
|
*/
|
||
|
parseKey: parseKey,
|
||
|
|
||
|
/**
|
||
|
* Handles the given (keyboard) event
|
||
|
* @param {Event} event - the event generated by the event handler
|
||
|
* @param {String} component - Foundation component's name, e.g. Slider or Reveal
|
||
|
* @param {Objects} functions - collection of functions that are to be executed
|
||
|
*/
|
||
|
handleKey(event, component, functions) {
|
||
|
var commandList = commands[component],
|
||
|
keyCode = this.parseKey(event),
|
||
|
cmds,
|
||
|
command,
|
||
|
fn;
|
||
|
|
||
|
if (!commandList) return console.warn('Component not defined!');
|
||
|
|
||
|
if (typeof commandList.ltr === 'undefined') { // this component does not differentiate between ltr and rtl
|
||
|
cmds = commandList; // use plain list
|
||
|
} else { // merge ltr and rtl: if document is rtl, rtl overwrites ltr and vice versa
|
||
|
if (Rtl()) cmds = $.extend({}, commandList.ltr, commandList.rtl);
|
||
|
|
||
|
else cmds = $.extend({}, commandList.rtl, commandList.ltr);
|
||
|
}
|
||
|
command = cmds[keyCode];
|
||
|
|
||
|
fn = functions[command];
|
||
|
if (fn && typeof fn === 'function') { // execute function if exists
|
||
|
var returnValue = fn.apply();
|
||
|
if (functions.handled || typeof functions.handled === 'function') { // execute function when event was handled
|
||
|
functions.handled(returnValue);
|
||
|
}
|
||
|
} else {
|
||
|
if (functions.unhandled || typeof functions.unhandled === 'function') { // execute function when event was not handled
|
||
|
functions.unhandled();
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Finds all focusable elements within the given `$element`
|
||
|
* @param {jQuery} $element - jQuery object to search within
|
||
|
* @return {jQuery} $focusable - all focusable elements within `$element`
|
||
|
*/
|
||
|
|
||
|
findFocusable: findFocusable,
|
||
|
|
||
|
/**
|
||
|
* Returns the component name name
|
||
|
* @param {Object} component - Foundation component, e.g. Slider or Reveal
|
||
|
* @return String componentName
|
||
|
*/
|
||
|
|
||
|
register(componentName, cmds) {
|
||
|
commands[componentName] = cmds;
|
||
|
},
|
||
|
|
||
|
|
||
|
// TODO9438: These references to Keyboard need to not require global. Will 'this' work in this context?
|
||
|
//
|
||
|
/**
|
||
|
* Traps the focus in the given element.
|
||
|
* @param {jQuery} $element jQuery object to trap the foucs into.
|
||
|
*/
|
||
|
trapFocus($element) {
|
||
|
var $focusable = findFocusable($element),
|
||
|
$firstFocusable = $focusable.eq(0),
|
||
|
$lastFocusable = $focusable.eq(-1);
|
||
|
|
||
|
$element.on('keydown.zf.trapfocus', function(event) {
|
||
|
if (event.target === $lastFocusable[0] && parseKey(event) === 'TAB') {
|
||
|
event.preventDefault();
|
||
|
$firstFocusable.focus();
|
||
|
}
|
||
|
else if (event.target === $firstFocusable[0] && parseKey(event) === 'SHIFT_TAB') {
|
||
|
event.preventDefault();
|
||
|
$lastFocusable.focus();
|
||
|
}
|
||
|
});
|
||
|
},
|
||
|
/**
|
||
|
* Releases the trapped focus from the given element.
|
||
|
* @param {jQuery} $element jQuery object to release the focus for.
|
||
|
*/
|
||
|
releaseFocus($element) {
|
||
|
$element.off('keydown.zf.trapfocus');
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Constants for easier comparing.
|
||
|
* Can be used like Foundation.parseKey(event) === Foundation.keys.SPACE
|
||
|
*/
|
||
|
function getKeyCodes(kcs) {
|
||
|
var k = {};
|
||
|
for (var kc in kcs) k[kcs[kc]] = kcs[kc];
|
||
|
return k;
|
||
|
}
|
||
|
|
||
|
export {Keyboard};
|