Skip to content

Commit d270403

Browse files
Via Graphsl15k4
Via Graphs
authored andcommitted
Add keyboard support and polyfill
1 parent 123aea4 commit d270403

File tree

1 file changed

+274
-0
lines changed

1 file changed

+274
-0
lines changed
Lines changed: 274 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,274 @@
1+
package org.scalajs.dom.extensions
2+
3+
import org.scalajs.dom
4+
import org.scalajs.dom.KeyboardEvent
5+
6+
import scala.scalajs.js
7+
import scala.scalajs.js.UndefOr
8+
9+
object KeyboardPolyfill {
10+
11+
/**
12+
* Primarily it allows you to abandon onpress events that have cross-browser incompatible behavior and
13+
* that are to be deprecated in favor of beforeinput events in W3C DOM4. Calling polyfill method on keydown/keyup event
14+
* gives you normalized keyCode across platforms and browsers and estimated charCode in case of a key representing
15+
* printable character. pfKeyCode & optional pfCharCode properties are added to Event object for possible latter use
16+
*/
17+
implicit class PfEvent(e: KeyboardEvent) {
18+
19+
/** to retrieve polyfilled code later */
20+
def pfKeyCode: Option[Int] = getDynamic("pfKeyCode")
21+
def pfCharCode: Option[Int] = getDynamic("pfCharCode")
22+
23+
/**
24+
* Basically attempts to unite keyCodes across variety of platforms and browsers and
25+
* find a corresponding charCode in case of a printable unicode point
26+
* @return (keyCode, Option[charCode])
27+
*/
28+
def polyfill(): (Int, Option[Int]) = {
29+
require(e.`type` != "keypress", "This polyfill only works with keydown/keyup events")
30+
val keyCode = normalize(e.keyCode)
31+
val result = ChCode.shiftableKey2Char.lift(keyCode) match {
32+
case Some(shift) => (keyCode, Option(shift(e.shiftKey)))
33+
case None => (keyCode, ChCode.key2char.lift(keyCode))
34+
}
35+
pfKeyCode(keyCode)
36+
result._2.foreach(pfCharCode(_))
37+
result
38+
}
39+
40+
private def setDynamic(name: String, value: js.Any) = e.asInstanceOf[js.Dynamic].updateDynamic(name)(value)
41+
private def getDynamic[T](name: String): Option[T] = Option((e.asInstanceOf[js.Dynamic].selectDynamic(name): UndefOr[Dynamic]).orNull).asInstanceOf[Option[T]]
42+
43+
private def pfKeyCode(keyCode: Int) = setDynamic("pfKeyCode", keyCode)
44+
private def pfCharCode(charCode: Int) = setDynamic("pfCharCode", charCode)
45+
46+
/**
47+
* To be improved continuously, most of the other stuff concerns Mac atypical keyboard layout and ancient browsers
48+
* You're welcome to contribute
49+
*/
50+
private def normalize(keyCode: Int): Int = {
51+
if (Device.isGecko)
52+
keyCode match {
53+
case 173 => KCode.Dash
54+
case 59 => KCode.Semicolon
55+
case 61 => KCode.Equals
56+
case 0 => KCode.Win
57+
case other => keyCode
58+
}
59+
else if (Device.isMac)
60+
keyCode match {
61+
case 224 => KCode.Meta
62+
case 12 => KCode.NumLock
63+
case other => keyCode
64+
}
65+
else
66+
keyCode
67+
}
68+
}
69+
70+
object Device {
71+
val IOSRegex = "iPhone|iPod|iPad".r
72+
73+
val userAgent = dom.window.navigator.userAgent
74+
val platform = dom.window.navigator.platform
75+
76+
val isIOS = IOSRegex.findFirstIn(userAgent).isDefined
77+
val isIPad = userAgent.contains("iPad")
78+
val isIPod = userAgent.contains("iPod")
79+
val isIPhone = userAgent.contains("iPhone")
80+
val isAndroid = userAgent.contains("Android")
81+
82+
val isGecko = userAgent.contains("Gecko/")
83+
val isWebKit = userAgent.contains("WebKit/")
84+
val isIE = userAgent.contains("Trident/")
85+
val isOpera = userAgent.contains("Opera/")
86+
val isChrome = userAgent.contains("Chrome/")
87+
88+
val isLinux = platform.contains("Linux")
89+
val isWin = platform.contains("Win")
90+
val isMac = platform.contains("Mac")
91+
val isChrOS = platform.contains("CrOS")
92+
93+
val isTouchable = isIOS || isAndroid
94+
}
95+
}
96+
97+
/**
98+
* @note ([0-9] * + - / .) are the only characters shared by 2 keys on keyboard, this duplication is caused by existence of numpad.
99+
*/
100+
object ChCode {
101+
import KCode._
102+
103+
/** shift changes charCode */
104+
private def >(w: Int, wo: Int)(shift: Boolean) = if (shift) w else wo
105+
/** add offset to a lower case letter which gives you it's char code */
106+
private def >>(keyCode: Int)(shift: Boolean) = if (shift) keyCode else letterKeyToLowerCaseCharCode(keyCode)
107+
108+
/** keys that have different charCode representation when shift key is pressed */
109+
private val letterKey2Char = for(letterKeyCode <- A to Z) yield (letterKeyCode, >>(letterKeyCode)_)
110+
private implicit def Char2Int(ch: Char): Int = ch.toInt
111+
val shiftableKey2Char = Map[Int, Boolean => Int](
112+
(Num0, >(')', Num0)),
113+
(Num1, >('!', Num1)),
114+
(Num2, >('@', Num2)),
115+
(Num3, >('#', Num3)),
116+
(Num4, >('$', Num4)),
117+
(Num5, >('%', Num5)),
118+
(Num6, >('^', Num6)),
119+
(Num7, >('&', Num7)),
120+
(Num8, >('*', Num8)),
121+
(Num9, >('(', Num9)),
122+
(Comma, >('<', ',')),
123+
(Dash, >('_', '-')),
124+
(Period, >('>', '.')),
125+
(Slash, >('?', '/')),
126+
(Backtick, >('~', '`')),
127+
(SquareBracketOpen, >('{', '[')),
128+
(Backslash, >('|', '\\')),
129+
(SquareBracketClose, >('}', ']')),
130+
(SingleQuote, >('"', ''')),
131+
(Semicolon, >(':', ';')),
132+
(Equals, >('+', '='))
133+
) ++ letterKey2Char
134+
135+
val key2char = Map[Int, Int](
136+
Space -> Space,
137+
Enter -> Enter,
138+
Numpad0,
139+
Numpad1,
140+
Numpad2,
141+
Numpad3,
142+
Numpad4,
143+
Numpad5,
144+
Numpad6,
145+
Numpad7,
146+
Numpad8,
147+
Numpad9,
148+
NumpadMultiply,
149+
NumpadAdd,
150+
NumpadSubtract,
151+
NumpadDivide,
152+
NumpadPeriod
153+
)
154+
}
155+
156+
object KCode {
157+
158+
/** numbers have KeyCode equal to CharCode */
159+
def isNumber(keyCode: Int) = keyCode >= Num0 && keyCode <= Num9
160+
val Num0 = '0'.toInt // 48
161+
val Num1 = '1'.toInt // 49
162+
val Num2 = '2'.toInt // 50
163+
val Num3 = '3'.toInt // 51
164+
val Num4 = '4'.toInt // 52
165+
val Num5 = '5'.toInt // 53
166+
val Num6 = '6'.toInt // 54
167+
val Num7 = '7'.toInt // 55
168+
val Num8 = '8'.toInt // 56
169+
val Num9 = '9'.toInt // 57
170+
171+
/** [A-Z] charCode is equal to [a-z] keyCode, thus I won't duplicate constants */
172+
val charSizeOffset = 'a'.toInt - 'A'.toInt
173+
174+
def isLetterKey(keyCode: Int) = keyCode >= A && keyCode <= Z
175+
def isUpperCaseLetter(charCode: Int) = isLetterKey(charCode)
176+
def letterKeyToLowerCaseCharCode(keyCode: Int) = keyCode + charSizeOffset
177+
def letterKeyToUpperCaseCharCode(keyCode: Int) = keyCode // informative method
178+
179+
/** Upper case letters have CharCode equal to KeyCode */
180+
val A = 'A'.toInt // 65
181+
val B = 'B'.toInt // 66
182+
val C = 'C'.toInt // 67
183+
val D = 'D'.toInt // 68
184+
val E = 'E'.toInt // 69
185+
val F = 'F'.toInt // 70
186+
val G = 'G'.toInt // 71
187+
val H = 'H'.toInt // 72
188+
val I = 'I'.toInt // 73
189+
val J = 'J'.toInt // 74
190+
val K = 'K'.toInt // 75
191+
val L = 'L'.toInt // 76
192+
val M = 'M'.toInt // 77
193+
val N = 'N'.toInt // 78
194+
val O = 'O'.toInt // 79
195+
val P = 'P'.toInt // 80
196+
val Q = 'Q'.toInt // 81
197+
val R = 'R'.toInt // 82
198+
val S = 'S'.toInt // 83
199+
val T = 'T'.toInt // 84
200+
val U = 'U'.toInt // 85
201+
val V = 'V'.toInt // 86
202+
val W = 'W'.toInt // 87
203+
val X = 'X'.toInt // 88
204+
val Y = 'Y'.toInt // 89
205+
val Z = 'Z'.toInt // 90
206+
207+
val Comma = 188
208+
val Dash = 189
209+
val Period = 190
210+
val Slash = 191
211+
val Backtick = 192
212+
val SquareBracketOpen = 219
213+
val Backslash = 220
214+
val SquareBracketClose = 221
215+
val SingleQuote = 222
216+
val Semicolon = 186
217+
val Equals = 187
218+
219+
/** Space & Enter have KeyCode equal to CharCode */
220+
val Space = 32 // both charCode and keyCode
221+
val Enter = 13 // both charCode and keyCode
222+
223+
/** Numpad numbers share common numbers charCode */
224+
val Numpad0 = (96, Num0)
225+
val Numpad1 = (97, Num1)
226+
val Numpad2 = (98, Num2)
227+
val Numpad3 = (99, Num3)
228+
val Numpad4 = (100, Num4)
229+
val Numpad5 = (101, Num5)
230+
val Numpad6 = (102, Num6)
231+
val Numpad7 = (103, Num7)
232+
val Numpad8 = (104, Num8)
233+
val Numpad9 = (105, Num9)
234+
val NumpadMultiply = (106, '*'.toInt)
235+
val NumpadAdd = (107, '+'.toInt)
236+
val NumpadSubtract = (109, '-'.toInt)
237+
val NumpadDivide = (111, '/'.toInt)
238+
val NumpadPeriod = (110, '.'.toInt)
239+
240+
/** Keys that do not have unicode representation */
241+
val Backspace = 8
242+
val Tab = 9
243+
val Shift = 16
244+
val Ctrl = 17
245+
val Alt = 18
246+
val Pause = 19
247+
val CapsLock = 20
248+
val Escape = 27
249+
val PageUp = 33
250+
val PageDown = 34
251+
val End = 35
252+
val Home = 36
253+
val Left = 37
254+
val Up = 38
255+
val Right = 39
256+
val Down = 40
257+
val Insert = 45
258+
val Delete = 46
259+
val Meta = 91
260+
val Win = 224
261+
val F1 = 112
262+
val F2 = 113
263+
val F3 = 114
264+
val F4 = 115
265+
val F5 = 116
266+
val F6 = 117
267+
val F7 = 118
268+
val F8 = 119
269+
val F9 = 120
270+
val F10 = 121
271+
val F11 = 122
272+
val F12 = 123
273+
val NumLock = 144
274+
}

0 commit comments

Comments
 (0)