blob: 1f2a7ff451c095997083ce915d67813b427866de [file] [log] [blame]
Copybara854996b2021-09-07 19:36:02 +00001/**
2 * Copyright 2008 Steve McKay.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17/**
18 * Kibbles.Keys is a Javascript library providing simple cross browser
19 * keyboard event support.
20 */
21(function(){
22
23var _listening = false;
24
25// code to handler list map.
26// Wildcard listeners use magic code wildcards "before" and "after".
27var _listeners = {
28 before: [],
29 after: []
30};
31
32/*
33 * Map of key names to char code. This map is consulted before
34 * charCodeAt(0) is used to determine the character code.
35 *
36 * This map also serves as a definitive list of supported "special" keys.
37 * See _codeForEvent for details.
38 */
39var _CODE_MAP = {
40 ESC: 27,
41 ENTER: 13
42};
43
44/**
45 * Register a keypress listener.
46 */
47function _listen() {
48 if (_listening) return;
49
50 var d = document;
51 if (d.addEventListener) {
52 d.addEventListener('keypress', _handleKeyboardEvent, false);
53 d.addEventListener('keydown', _handleKeyDownEvent, false);
54 } else if (d.attachEvent) {
55 d.documentElement.attachEvent('onkeypress', _handleKeyboardEvent);
56 d.documentElement.attachEvent('onkeydown', _handleKeyDownEvent);
57 }
58 _listening = true;
59}
60
61/**
62 * Register a keypress listener for the supplied skip code.
63 */
64function _addKeyPressListener(spec, handler) {
65 var code = spec.toLowerCase();
66 if (code == "before" || code == "after") {
67 _listeners[code].push(handler);
68 return;
69 }
70
71 // try to find the character or key code.
72 code = _CODE_MAP[spec.toUpperCase()];
73 if (!code) {
74 code = spec.charCodeAt(0);
75 }
76 if (!_listeners[code]) {
77 _listeners[code] = [];
78 }
79 _listeners[code].push(handler);
80}
81
82/**
83 * Our handler for keypress events.
84 */
85function _handleKeyboardEvent(e) {
86
87 // If event is null, this is probably IE.
88 if (!e) e = window.event;
89
90 var source = _getSourceElement(e);
91 if (_isInputElement(source)) {
92 return;
93 }
94
95 if (_hasFlakeyModifier(e)) return;
96
97 var code = _codeForEvent(e);
98
99 if (code == undefined) return;
100
101 var payload = {
102 code: code
103 };
104
105 for (var i = 0; i < _listeners.before.length; i++) {
106 _listeners.before[i](payload);
107 }
108
109 var listeners = _listeners[code];
110 if (listeners) {
111 for (var i = 0; i < listeners.length; i++) {
112 listeners[i]({
113 code: code
114 });
115 }
116 }
117
118 for (var i = 0; i < _listeners.after.length; i++) {
119 _listeners.after[i](payload);
120 }
121}
122
123function _handleKeyDownEvent(e) {
124 if (!e) e = window.event;
125 var code = _codeForEvent(e);
126 if (code == _CODE_MAP['ESC'] || code == _CODE_MAP['ENTER']) {
127 _handleKeyboardEvent(e);
128 }
129}
130
131/**
132 * Returns the keycode associated with the event.
133 */
134function _codeForEvent(e) {
135 return e.keyCode ? e.keyCode : e.which;
136}
137
138/**
139 * Returns true if the supplied event has an associated modifier key
140 * that we have had trouble with in certain browsers.
141 */
142function _hasFlakeyModifier(e) {
143 return e.altKey || e.ctrlKey || e.metaKey;
144}
145
146/**
147 * Returns the source element for the supplied event.
148 */
149function _getSourceElement(e) {
150 var element = e.target;
151 if (!element) {
152 element = e.srcElement;
153 }
154
155 if (element.shadowRoot) {
156 // Find the element within the shadowDOM.
157 const path = e.path || e.composedPath();
158 element = path[0];
159 }
160
161 // If the source element is a text node, the parent is the object
162 // we're interested in.
163 if (element.nodeType == 3) {
164 element = element.parentNode;
165 }
166
167 return element;
168}
169
170/**
171 * Returns true if the element is a known form input element.
172 */
173function _isInputElement(element) {
174 return element.tagName == 'INPUT' || element.tagName == 'TEXTAREA';
175}
176
177/*
178 * A nice little namespace to call our own.
179 *
180 * Formalizing Kibbles.Keys as a traditional javascript class caused headaches
181 * with respect to capturing the context (what is "this" at any point in time).
182 * So we use a simple script exported via the "kibbles.keys" namespace.
183 */
184if (!window.kibbles)
185 window.kibbles = {}
186
187window.kibbles.keys = {
188 listen: _listen,
189 addKeyPressListener: _addKeyPressListener
190};
191
192})();