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