jellyfin-web/dashboard-ui/bower_components/iron-a11y-keys-behavior/iron-a11y-keys-behavior.html

420 lines
13 KiB
HTML
Raw Normal View History

2015-06-15 21:52:01 -07:00
<!--
@license
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
-->
<link rel="import" href="../polymer/polymer.html">
<script>
(function() {
'use strict';
/**
* Chrome uses an older version of DOM Level 3 Keyboard Events
*
* Most keys are labeled as text, but some are Unicode codepoints.
* Values taken from: http://www.w3.org/TR/2007/WD-DOM-Level-3-Events-20071221/keyset.html#KeySet-Set
*/
var KEY_IDENTIFIER = {
2015-12-14 08:43:03 -07:00
'U+0008': 'backspace',
2015-06-15 21:52:01 -07:00
'U+0009': 'tab',
'U+001B': 'esc',
'U+0020': 'space',
'U+007F': 'del'
};
/**
* Special table for KeyboardEvent.keyCode.
* KeyboardEvent.keyIdentifier is better, and KeyBoardEvent.key is even better
* than that.
*
* Values from: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent.keyCode#Value_of_keyCode
*/
var KEY_CODE = {
2015-12-14 08:43:03 -07:00
8: 'backspace',
2015-06-15 21:52:01 -07:00
9: 'tab',
13: 'enter',
27: 'esc',
33: 'pageup',
34: 'pagedown',
35: 'end',
36: 'home',
32: 'space',
37: 'left',
38: 'up',
39: 'right',
40: 'down',
46: 'del',
106: '*'
};
/**
* MODIFIER_KEYS maps the short name for modifier keys used in a key
* combo string to the property name that references those same keys
* in a KeyboardEvent instance.
*/
var MODIFIER_KEYS = {
2015-06-25 18:13:51 -07:00
'shift': 'shiftKey',
'ctrl': 'ctrlKey',
'alt': 'altKey',
'meta': 'metaKey'
2015-06-15 21:52:01 -07:00
};
/**
* Matches a keyIdentifier string.
*/
var IDENT_CHAR = /U\+/;
/**
* Matches arrow keys in Gecko 27.0+
*/
var ARROW_KEY = /^arrow/;
/**
* Matches space keys everywhere (notably including IE10's exceptional name
* `spacebar`).
*/
var SPACE_KEY = /^space(bar)?/;
function transformKey(key) {
var validKey = '';
if (key) {
var lKey = key.toLowerCase();
2015-12-14 08:43:03 -07:00
if (lKey === ' ' || SPACE_KEY.test(lKey)) {
validKey = 'space';
} else if (lKey.length == 1) {
validKey = lKey;
2015-06-15 21:52:01 -07:00
} else if (ARROW_KEY.test(lKey)) {
validKey = lKey.replace('arrow', '');
} else if (lKey == 'multiply') {
// numpad '*' can map to Multiply on IE/Windows
validKey = '*';
} else {
validKey = lKey;
}
}
return validKey;
}
function transformKeyIdentifier(keyIdent) {
var validKey = '';
if (keyIdent) {
2015-12-14 08:43:03 -07:00
if (keyIdent in KEY_IDENTIFIER) {
2015-06-15 21:52:01 -07:00
validKey = KEY_IDENTIFIER[keyIdent];
2015-12-14 08:43:03 -07:00
} else if (IDENT_CHAR.test(keyIdent)) {
keyIdent = parseInt(keyIdent.replace('U+', '0x'), 16);
validKey = String.fromCharCode(keyIdent).toLowerCase();
2015-06-15 21:52:01 -07:00
} else {
validKey = keyIdent.toLowerCase();
}
}
return validKey;
}
function transformKeyCode(keyCode) {
var validKey = '';
if (Number(keyCode)) {
if (keyCode >= 65 && keyCode <= 90) {
// ascii a-z
// lowercase is 32 offset from uppercase
validKey = String.fromCharCode(32 + keyCode);
} else if (keyCode >= 112 && keyCode <= 123) {
// function keys f1-f12
validKey = 'f' + (keyCode - 112);
} else if (keyCode >= 48 && keyCode <= 57) {
// top 0-9 keys
validKey = String(48 - keyCode);
} else if (keyCode >= 96 && keyCode <= 105) {
// num pad 0-9
validKey = String(96 - keyCode);
} else {
validKey = KEY_CODE[keyCode];
}
}
return validKey;
}
function normalizedKeyForEvent(keyEvent) {
// fall back from .key, to .keyIdentifier, to .keyCode, and then to
// .detail.key to support artificial keyboard events
return transformKey(keyEvent.key) ||
transformKeyIdentifier(keyEvent.keyIdentifier) ||
transformKeyCode(keyEvent.keyCode) ||
transformKey(keyEvent.detail.key) || '';
}
2015-12-14 08:43:03 -07:00
function keyComboMatchesEvent(keyCombo, event, eventKey) {
return eventKey === keyCombo.key &&
(!keyCombo.hasModifiers || (
!!event.shiftKey === !!keyCombo.shiftKey &&
!!event.ctrlKey === !!keyCombo.ctrlKey &&
!!event.altKey === !!keyCombo.altKey &&
!!event.metaKey === !!keyCombo.metaKey)
);
2015-06-15 21:52:01 -07:00
}
function parseKeyComboString(keyComboString) {
2015-12-14 08:43:03 -07:00
if (keyComboString.length === 1) {
return {
combo: keyComboString,
key: keyComboString,
event: 'keydown'
};
}
2015-06-15 21:52:01 -07:00
return keyComboString.split('+').reduce(function(parsedKeyCombo, keyComboPart) {
var eventParts = keyComboPart.split(':');
var keyName = eventParts[0];
var event = eventParts[1];
if (keyName in MODIFIER_KEYS) {
parsedKeyCombo[MODIFIER_KEYS[keyName]] = true;
2015-12-14 08:43:03 -07:00
parsedKeyCombo.hasModifiers = true;
2015-06-15 21:52:01 -07:00
} else {
parsedKeyCombo.key = keyName;
parsedKeyCombo.event = event || 'keydown';
}
return parsedKeyCombo;
}, {
combo: keyComboString.split(':').shift()
});
}
function parseEventString(eventString) {
2015-12-14 08:43:03 -07:00
return eventString.trim().split(' ').map(function(keyComboString) {
2015-06-15 21:52:01 -07:00
return parseKeyComboString(keyComboString);
});
}
/**
* `Polymer.IronA11yKeysBehavior` provides a normalized interface for processing
* keyboard commands that pertain to [WAI-ARIA best practices](http://www.w3.org/TR/wai-aria-practices/#kbd_general_binding).
* The element takes care of browser differences with respect to Keyboard events
* and uses an expressive syntax to filter key presses.
*
* Use the `keyBindings` prototype property to express what combination of keys
* will trigger the event to fire.
*
* Use the `key-event-target` attribute to set up event handlers on a specific
* node.
* The `keys-pressed` event will fire when one of the key combinations set with the
* `keys` property is pressed.
*
* @demo demo/index.html
2015-09-29 21:10:14 -07:00
* @polymerBehavior
2015-06-15 21:52:01 -07:00
*/
Polymer.IronA11yKeysBehavior = {
properties: {
/**
* The HTMLElement that will be firing relevant KeyboardEvents.
*/
keyEventTarget: {
type: Object,
value: function() {
return this;
}
},
2015-12-14 08:43:03 -07:00
/**
* If true, this property will cause the implementing element to
* automatically stop propagation on any handled KeyboardEvents.
*/
stopKeyboardEventPropagation: {
type: Boolean,
value: false
},
2015-06-15 21:52:01 -07:00
_boundKeyHandlers: {
type: Array,
value: function() {
return [];
}
},
// We use this due to a limitation in IE10 where instances will have
// own properties of everything on the "prototype".
_imperativeKeyBindings: {
type: Object,
value: function() {
return {};
}
}
},
observers: [
'_resetKeyEventListeners(keyEventTarget, _boundKeyHandlers)'
],
keyBindings: {},
registered: function() {
this._prepKeyBindings();
},
attached: function() {
this._listenKeyEventListeners();
},
detached: function() {
this._unlistenKeyEventListeners();
},
/**
* Can be used to imperatively add a key binding to the implementing
* element. This is the imperative equivalent of declaring a keybinding
* in the `keyBindings` prototype property.
*/
addOwnKeyBinding: function(eventString, handlerName) {
this._imperativeKeyBindings[eventString] = handlerName;
this._prepKeyBindings();
this._resetKeyEventListeners();
},
/**
* When called, will remove all imperatively-added key bindings.
*/
removeOwnKeyBindings: function() {
this._imperativeKeyBindings = {};
this._prepKeyBindings();
this._resetKeyEventListeners();
},
keyboardEventMatchesKeys: function(event, eventString) {
var keyCombos = parseEventString(eventString);
2015-12-14 08:43:03 -07:00
var eventKey = normalizedKeyForEvent(event);
for (var i = 0; i < keyCombos.length; ++i) {
if (keyComboMatchesEvent(keyCombos[i], event, eventKey)) {
2015-06-15 21:52:01 -07:00
return true;
}
}
return false;
},
_collectKeyBindings: function() {
var keyBindings = this.behaviors.map(function(behavior) {
return behavior.keyBindings;
});
if (keyBindings.indexOf(this.keyBindings) === -1) {
keyBindings.push(this.keyBindings);
}
return keyBindings;
},
_prepKeyBindings: function() {
this._keyBindings = {};
this._collectKeyBindings().forEach(function(keyBindings) {
for (var eventString in keyBindings) {
this._addKeyBinding(eventString, keyBindings[eventString]);
}
}, this);
for (var eventString in this._imperativeKeyBindings) {
this._addKeyBinding(eventString, this._imperativeKeyBindings[eventString]);
}
2015-12-14 08:43:03 -07:00
// Give precedence to combos with modifiers to be checked first.
for (var eventName in this._keyBindings) {
this._keyBindings[eventName].sort(function (kb1, kb2) {
var b1 = kb1[0].hasModifiers;
var b2 = kb2[0].hasModifiers;
return (b1 === b2) ? 0 : b1 ? -1 : 1;
})
}
2015-06-15 21:52:01 -07:00
},
_addKeyBinding: function(eventString, handlerName) {
parseEventString(eventString).forEach(function(keyCombo) {
this._keyBindings[keyCombo.event] =
this._keyBindings[keyCombo.event] || [];
this._keyBindings[keyCombo.event].push([
keyCombo,
handlerName
]);
}, this);
},
_resetKeyEventListeners: function() {
this._unlistenKeyEventListeners();
if (this.isAttached) {
this._listenKeyEventListeners();
}
},
_listenKeyEventListeners: function() {
Object.keys(this._keyBindings).forEach(function(eventName) {
var keyBindings = this._keyBindings[eventName];
var boundKeyHandler = this._onKeyBindingEvent.bind(this, keyBindings);
this._boundKeyHandlers.push([this.keyEventTarget, eventName, boundKeyHandler]);
this.keyEventTarget.addEventListener(eventName, boundKeyHandler);
}, this);
},
_unlistenKeyEventListeners: function() {
var keyHandlerTuple;
var keyEventTarget;
var eventName;
var boundKeyHandler;
while (this._boundKeyHandlers.length) {
// My kingdom for block-scope binding and destructuring assignment..
keyHandlerTuple = this._boundKeyHandlers.pop();
keyEventTarget = keyHandlerTuple[0];
eventName = keyHandlerTuple[1];
boundKeyHandler = keyHandlerTuple[2];
keyEventTarget.removeEventListener(eventName, boundKeyHandler);
}
},
_onKeyBindingEvent: function(keyBindings, event) {
2015-12-14 08:43:03 -07:00
if (this.stopKeyboardEventPropagation) {
event.stopPropagation();
}
2015-06-15 21:52:01 -07:00
2015-12-14 08:43:03 -07:00
// if event has been already prevented, don't do anything
if (event.defaultPrevented) {
return;
}
var eventKey = normalizedKeyForEvent(event);
for (var i = 0; i < keyBindings.length; i++) {
var keyCombo = keyBindings[i][0];
var handlerName = keyBindings[i][1];
if (keyComboMatchesEvent(keyCombo, event, eventKey)) {
2015-06-15 21:52:01 -07:00
this._triggerKeyHandler(keyCombo, handlerName, event);
2015-12-14 08:43:03 -07:00
// exit the loop if eventDefault was prevented
if (event.defaultPrevented) {
return;
}
2015-06-15 21:52:01 -07:00
}
2015-12-14 08:43:03 -07:00
}
2015-06-15 21:52:01 -07:00
},
_triggerKeyHandler: function(keyCombo, handlerName, keyboardEvent) {
var detail = Object.create(keyCombo);
detail.keyboardEvent = keyboardEvent;
2015-12-14 08:43:03 -07:00
var event = new CustomEvent(keyCombo.event, {
detail: detail,
cancelable: true
});
this[handlerName].call(this, event);
if (event.defaultPrevented) {
keyboardEvent.preventDefault();
}
2015-06-15 21:52:01 -07:00
}
};
})();
</script>