3 Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
4 This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
5 The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
6 The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
7 Code distributed by Google as part of the polymer project is also
8 subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
12 `core-a11y-keys` provides a normalized interface for processing keyboard commands that pertain to [WAI-ARIA best
13 practices](http://www.w3.org/TR/wai-aria-practices/#kbd_general_binding). The element takes care of browser differences
14 with respect to Keyboard events and uses an expressive syntax to filter key presses.
16 Use the `keys` attribute to express what combination of keys will trigger the event to fire.
18 Use the `target` attribute to set up event handlers on a specific node.
19 The `keys-pressed` event will fire when one of the key combinations set with the `keys` attribute is pressed.
23 This element will call `arrowHandler` on all arrow keys:
25 <core-a11y-keys target="{{}}" keys="up down left right" on-keys-pressed="{{arrowHandler}}"></core-a11y-keys>
29 The `keys` attribute can accepts a space seprated, `+` concatenated set of modifier keys and some common keyboard keys.
31 The common keys are `a-z`, `0-9` (top row and number pad), `*` (shift 8 and number pad), `F1-F12`, `Page Up`, `Page
32 Down`, `Left Arrow`, `Right Arrow`, `Down Arrow`, `Up Arrow`, `Home`, `End`, `Escape`, `Space`, `Tab`, and `Enter` keys.
34 The modifier keys are `Shift`, `Control`, and `Alt`.
36 All keys are expected to be lowercase and shortened:
37 `Left Arrow` is `left`, `Page Down` is `pagedown`, `Control` is `ctrl`, `F1` is `f1`, `Escape` is `esc` etc.
41 Given the `keys` attribute value "ctrl+shift+f7 up pagedown esc space alt+m", the `<core-a11y-keys>` element will send
42 the `keys-pressed` event if any of the follow key combos are pressed: Control and Shift and F7 keys, Up Arrow key, Page
43 Down key, Escape key, Space key, Alt and M key.
47 The following is an example of the set of keys that fulfil the WAI-ARIA "slider" role [best
48 practices](http://www.w3.org/TR/wai-aria-practices/#slider):
50 <core-a11y-keys target="{{}}" keys="left pagedown down" on-keys-pressed="{{decrement}}"></core-a11y-keys>
51 <core-a11y-keys target="{{}}" keys="right pageup up" on-keys-pressed="{{increment}}"></core-a11y-keys>
52 <core-a11y-keys target="{{}}" keys="home" on-keys-pressed="{{setMin}}"></core-a11y-keys>
53 <core-a11y-keys target="{{}}" keys="end" on-keys-pressed="{{setMax}}"></core-a11y-keys>
55 The `increment` function will move the slider a set amount toward the maximum value.
56 The `decrement` function will move the slider a set amount toward the minimum value.
57 The `setMin` function will move the slider to the minimum value.
58 The `setMax` function will move the slider to the maximum value.
62 [EBNF](http://en.wikipedia.org/wiki/Extended_Backus%E2%80%93Naur_Form) Grammar of the `keys` attribute.
64 modifier = "shift" | "ctrl" | "alt";
65 ascii = ? /[a-z0-9]/ ? ;
66 fnkey = ? f1 through f12 ? ;
67 arrow = "up" | "down" | "left" | "right" ;
68 key = "tab" | "esc" | "space" | "*" | "pageup" | "pagedown" | "home" | "end" | arrow | ascii | fnkey ;
69 keycombo = { modifier, "+" }, key ;
70 keys = keycombo, { " ", keycombo } ;
73 @element core-a11y-keys
77 <link rel=
"import" href=
"../polymer/polymer.html">
79 <style shim-shadowdom
>
80 html /deep/ core-a11y-keys {
85 <polymer-element name=
"core-a11y-keys">
89 * Chrome uses an older version of DOM Level 3 Keyboard Events
91 * Most keys are labeled as text, but some are Unicode codepoints.
92 * Values taken from: http://www.w3.org/TR/2007/WD-DOM-Level-3-Events-20071221/keyset.html#KeySet-Set
94 var KEY_IDENTIFIER
= {
139 * Special table for KeyboardEvent.keyCode.
140 * KeyboardEvent.keyIdentifier is better, and KeyBoardEvent.key is even better than that
142 * Values from: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent.keyCode#Value_of_keyCode
162 * KeyboardEvent.key is mostly represented by printable character made by the keyboard, with unprintable keys labeled
165 * However, on OS X, Alt+char can make a Unicode character that follows an Apple-specific mapping. In this case, we
166 * fall back to .keyCode.
168 var KEY_CHAR
= /[a-z0-9*]/;
170 function transformKey(key
) {
173 var lKey
= key
.toLowerCase();
174 if (lKey
.length
== 1) {
175 if (KEY_CHAR
.test(lKey
)) {
178 } else if (lKey
== 'multiply') {
179 // numpad '*' can map to Multiply on IE/Windows
188 var IDENT_CHAR
= /U\+/;
189 function transformKeyIdentifier(keyIdent
) {
192 if (IDENT_CHAR
.test(keyIdent
)) {
193 validKey
= KEY_IDENTIFIER
[keyIdent
];
195 validKey
= keyIdent
.toLowerCase();
201 function transformKeyCode(keyCode
) {
203 if (Number(keyCode
)) {
204 if (keyCode
>= 65 && keyCode
<= 90) {
206 // lowercase is 32 offset from uppercase
207 validKey
= String
.fromCharCode(32 + keyCode
);
208 } else if (keyCode
>= 112 && keyCode
<= 123) {
209 // function keys f1-f12
210 validKey
= 'f' + (keyCode
- 112);
211 } else if (keyCode
>= 48 && keyCode
<= 57) {
213 validKey
= String(48 - keyCode
);
214 } else if (keyCode
>= 96 && keyCode
<= 105) {
216 validKey
= String(96 - keyCode
);
218 validKey
= KEY_CODE
[keyCode
];
224 function keyboardEventToKey(ev
) {
225 // fall back from .key, to .keyIdentifier, to .keyCode, and then to .detail.key to support artificial keyboard events
226 var normalizedKey
= transformKey(ev
.key
) || transformKeyIdentifier(ev
.keyIdentifier
) || transformKeyCode(ev
.keyCode
) || transformKey(ev
.detail
.key
) || '';
237 * Input: ctrl+shift+f7 => {ctrl: true, shift: true, key: 'f7'}
238 * ctrl/space => {ctrl: true} || {key: space}
240 function stringToKey(keyCombo
) {
241 var keys
= keyCombo
.split('+');
242 var keyObj
= Object
.create(null);
243 keys
.forEach(function(key
) {
244 if (key
== 'shift') {
246 } else if (key
== 'ctrl') {
248 } else if (key
== 'alt') {
257 function keyMatches(a
, b
) {
258 return Boolean(a
.alt
) == Boolean(b
.alt
) && Boolean(a
.ctrl
) == Boolean(b
.ctrl
) && Boolean(a
.shift
) == Boolean(b
.shift
) && a
.key
=== b
.key
;
262 * Fired when a keycombo in `keys` is pressed.
264 * @event keys-pressed
266 function processKeys(ev
) {
267 var current
= keyboardEventToKey(ev
);
268 for (var i
= 0, dk
; i
< this._desiredKeys
.length
; i
++) {
269 dk
= this._desiredKeys
[i
];
270 if (keyMatches(dk
, current
)) {
272 ev
.stopPropagation();
273 this.fire('keys-pressed', current
, this, false);
279 function listen(node
, handler
) {
280 if (node
&& node
.addEventListener
) {
281 node
.addEventListener('keydown', handler
);
285 function unlisten(node
, handler
) {
286 if (node
&& node
.removeEventListener
) {
287 node
.removeEventListener('keydown', handler
);
291 Polymer('core-a11y-keys', {
292 created: function() {
293 this._keyHandler
= processKeys
.bind(this);
295 attached: function() {
297 this.target
= this.parentNode
;
299 listen(this.target
, this._keyHandler
);
301 detached: function() {
302 unlisten(this.target
, this._keyHandler
);
306 * The set of key combinations to listen for.
309 * @type string (keys syntax)
314 * The node that will fire keyboard events.
315 * Default to this element's parentNode unless one is assigned
319 * @default this.parentNode
323 keysChanged: function() {
324 // * can have multiple mappings: shift+8, * on numpad or Multiply on numpad
325 var normalized
= this.keys
.replace('*', '* shift+*');
326 this._desiredKeys
= normalized
.toLowerCase().split(' ').map(stringToKey
);
328 targetChanged: function(oldTarget
) {
329 unlisten(oldTarget
, this._keyHandler
);
330 listen(this.target
, this._keyHandler
);