Skip to content

Commit 7d225e8

Browse files
author
Via Graphs
committed
Keyboard extension
1 parent 123aea4 commit 7d225e8

File tree

1 file changed

+271
-0
lines changed

1 file changed

+271
-0
lines changed
Lines changed: 271 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,271 @@
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

Comments
 (0)