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