mirror of
https://github.com/hodgef/simple-keyboard.git
synced 2026-02-03 00:06:50 +08:00
Compare commits
565 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3d6baaa1d2 | ||
|
|
2fcd634090 | ||
|
|
a1668da044 | ||
|
|
3032bef324 | ||
|
|
c6f12f0f92 | ||
|
|
44c4e46825 | ||
|
|
6d88549dee | ||
|
|
17d474e936 | ||
|
|
933e5ad55e | ||
|
|
57a145c5f9 | ||
|
|
b98f7a814d | ||
|
|
7899778e8a | ||
|
|
a13c908b87 | ||
|
|
bbee486f40 | ||
|
|
60d667ad0e | ||
|
|
f9475a9e4f | ||
|
|
f9243f24ee | ||
|
|
be9fe54228 | ||
|
|
deeb2abdf3 | ||
|
|
5659c98de6 | ||
|
|
273f926574 | ||
|
|
7bd34e8f86 | ||
|
|
2fe0c3a7da | ||
|
|
7b8d4f6548 | ||
|
|
506660a881 | ||
|
|
3b1d738d65 | ||
|
|
5ca7df5094 | ||
|
|
ff3bef81de | ||
|
|
02241d9da4 | ||
|
|
a2d5eb3de2 | ||
|
|
bedb14dd40 | ||
|
|
b892de103f | ||
|
|
66dc907040 | ||
|
|
543e6d9cbb | ||
|
|
509b25fbb1 | ||
|
|
71a682cf74 | ||
|
|
04acf24a55 | ||
|
|
65e707cd8c | ||
|
|
bf54d2f418 | ||
|
|
917d3647ba | ||
|
|
f497fea9c9 | ||
|
|
69db5a3ae7 | ||
|
|
665cc5e07c | ||
|
|
fe6becb8b7 | ||
|
|
50320c58c4 | ||
|
|
4efd0f726e | ||
|
|
f4a8ec23fe | ||
|
|
5dd2dc5975 | ||
|
|
10b62cf5c4 | ||
|
|
a6fdc4cb5d | ||
|
|
b6b2fa2a4c | ||
|
|
eeebda9c22 | ||
|
|
8b80dcca3b | ||
|
|
917af8b983 | ||
|
|
7021f08850 | ||
|
|
fca109c791 | ||
|
|
0796a090f9 | ||
|
|
83c3297eac | ||
|
|
a4a0dd7f0c | ||
|
|
ca3aa65ac7 | ||
|
|
de01ca46e6 | ||
|
|
0f68a5b0e6 | ||
|
|
3bc3940f8c | ||
|
|
074676c291 | ||
|
|
e45208564d | ||
|
|
e140ef6e61 | ||
|
|
bf327f1e44 | ||
|
|
e49e497e2a | ||
|
|
b98cf64687 | ||
|
|
07700a05c8 | ||
|
|
28bf21e27c | ||
|
|
897e042299 | ||
|
|
7b3e670b56 | ||
|
|
d80d58e9d4 | ||
|
|
2431f7193f | ||
|
|
11ed2360be | ||
|
|
a129d3d65b | ||
|
|
63cb14aa1a | ||
|
|
63f666bce2 | ||
|
|
5f92af77c6 | ||
|
|
034dca529c | ||
|
|
dc815e3e1d | ||
|
|
02861ef048 | ||
|
|
da886726ff | ||
|
|
09c1b88db1 | ||
|
|
6b4732e8f1 | ||
|
|
a476bff98b | ||
|
|
b2e3ba5fc9 | ||
|
|
e84be88939 | ||
|
|
71a61a2beb | ||
|
|
330a08ba20 | ||
|
|
88d02f5227 | ||
|
|
a3c375e77e | ||
|
|
09e1decf57 | ||
|
|
402defe6d6 | ||
|
|
a585ddd46a | ||
|
|
e16defb0c3 | ||
|
|
09b687b29d | ||
|
|
0a77a3a89b | ||
|
|
a40d31d6e0 | ||
|
|
d8418562c5 | ||
|
|
1b2e893a90 | ||
|
|
4be73fb6be | ||
|
|
21ae777592 | ||
|
|
58313becef | ||
|
|
5ff1c2b6dc | ||
|
|
eb0bc7755e | ||
|
|
0f81395b07 | ||
|
|
a4ddc5ee79 | ||
|
|
faba06e4c9 | ||
|
|
cfb7c7503b | ||
|
|
7b630d8c9b | ||
|
|
28e55d2381 | ||
|
|
e1ff0a65bd | ||
|
|
3cb5f31df5 | ||
|
|
ff06028297 | ||
|
|
5172a66044 | ||
|
|
bf7eae17a0 | ||
|
|
ee9585c9a4 | ||
|
|
2ff2b2d1c2 | ||
|
|
33d82e5f1e | ||
|
|
95abc0f458 | ||
|
|
06da8fffd4 | ||
|
|
439975a4ac | ||
|
|
d72584b439 | ||
|
|
e2669bd87b | ||
|
|
3d075a2f57 | ||
|
|
f0fbf760b2 | ||
|
|
bb19308e40 | ||
|
|
294a9a2ac7 | ||
|
|
72e440ea4d | ||
|
|
708237360b | ||
|
|
d326c723fd | ||
|
|
2c7243139e | ||
|
|
2b1bcaa933 | ||
|
|
3fc0470972 | ||
|
|
8d23150a78 | ||
|
|
841a486455 | ||
|
|
00556cb58e | ||
|
|
e55221d318 | ||
|
|
3baf3e929a | ||
|
|
f194389fec | ||
|
|
832cb8b06d | ||
|
|
0643fa35ec | ||
|
|
a5d054c752 | ||
|
|
a824d37d7a | ||
|
|
03e566b6c2 | ||
|
|
194838e8eb | ||
|
|
109ac61c8a | ||
|
|
543a65bcaf | ||
|
|
15cdc21e72 | ||
|
|
b15428665d | ||
|
|
cc123d6cd4 | ||
|
|
a0ae588727 | ||
|
|
ff2a899674 | ||
|
|
be2d28c8e0 | ||
|
|
4aa027d233 | ||
|
|
8ced6a641f | ||
|
|
6e1bfa8300 | ||
|
|
5a72f0f0d2 | ||
|
|
d392710609 | ||
|
|
78682b14c9 | ||
|
|
bd231d9f89 | ||
|
|
133008c9a5 | ||
|
|
28832cd7f2 | ||
|
|
a928d71ff2 | ||
|
|
fe20a66505 | ||
|
|
f5ea454bb4 | ||
|
|
07ce5c8f9f | ||
|
|
940f49cec9 | ||
|
|
74a58352a5 | ||
|
|
3e8c9d4a9d | ||
|
|
31fb8b5bef | ||
|
|
7f044b6b6c | ||
|
|
73d52fddb7 | ||
|
|
ea37af41f1 | ||
|
|
26eee6d4f9 | ||
|
|
7166a98ea4 | ||
|
|
2206115ada | ||
|
|
a4d4f1011f | ||
|
|
919132fab1 | ||
|
|
0d0860164a | ||
|
|
55b4274a83 | ||
|
|
10de5a1455 | ||
|
|
bd1478ef08 | ||
|
|
b03662dc8c | ||
|
|
41729dffe5 | ||
|
|
daafd04a5d | ||
|
|
86c66729e5 | ||
|
|
12842d12b6 | ||
|
|
b522d1c1bc | ||
|
|
4c9711dfb9 | ||
|
|
d1a0f112dc | ||
|
|
605bf893a5 | ||
|
|
4049979dc5 | ||
|
|
a18ed869dc | ||
|
|
9adb865156 | ||
|
|
bb2d495bb8 | ||
|
|
10585fd65b | ||
|
|
29c9337fdd | ||
|
|
25c7f19a24 | ||
|
|
13eac703ce | ||
|
|
73c7a7eca2 | ||
|
|
8162dfef57 | ||
|
|
b57dd58456 | ||
|
|
c98c995f10 | ||
|
|
b26b461f7c | ||
|
|
5813b674dc | ||
|
|
86f16d9786 | ||
|
|
72609a200b | ||
|
|
cba1441b29 | ||
|
|
40f4c774bd | ||
|
|
365c3d6efc | ||
|
|
5a054791a5 | ||
|
|
26da07de96 | ||
|
|
2e1c83c029 | ||
|
|
8816d8c961 | ||
|
|
ffdf89cb58 | ||
|
|
6004ca29c1 | ||
|
|
53a84fcd68 | ||
|
|
9152ed1576 | ||
|
|
a6057dc7f0 | ||
|
|
b00bcff976 | ||
|
|
469c62d37f | ||
|
|
8e74305b60 | ||
|
|
f239dafcd9 | ||
|
|
1263b1ef2f | ||
|
|
07d2c30994 | ||
|
|
03e594f0e8 | ||
|
|
6f8f4c19d3 | ||
|
|
7cf0e6c8e9 | ||
|
|
d6f015a4d5 | ||
|
|
243be6e116 | ||
|
|
17eb699195 | ||
|
|
e5856dcc31 | ||
|
|
b857cfbb63 | ||
|
|
7b12d6048f | ||
|
|
7a4b3b8250 | ||
|
|
848e86d54d | ||
|
|
f3dc6a2d81 | ||
|
|
d3771b7107 | ||
|
|
b993885bd5 | ||
|
|
c2d9cc0f3f | ||
|
|
a668b31f82 | ||
|
|
7235a1af9c | ||
|
|
9d3a9bc79f | ||
|
|
73b96ea181 | ||
|
|
f5358032d5 | ||
|
|
0409ee337e | ||
|
|
343b24af98 | ||
|
|
cc2b0128d6 | ||
|
|
e84e66543d | ||
|
|
037d145cc2 | ||
|
|
500bf7914e | ||
|
|
723874297e | ||
|
|
9367ea460f | ||
|
|
b9bb8270f1 | ||
|
|
3f7d49aaca | ||
|
|
153d1bf034 | ||
|
|
6b771ce409 | ||
|
|
e0068b7f91 | ||
|
|
8a5f14ea4d | ||
|
|
f6e8508a2c | ||
|
|
44f5d37972 | ||
|
|
d886f0c7e5 | ||
|
|
be823336d5 | ||
|
|
396e225059 | ||
|
|
76356ec589 | ||
|
|
b3df0ee177 | ||
|
|
6b5b681fbb | ||
|
|
90551343ce | ||
|
|
e629685b52 | ||
|
|
518d4a2090 | ||
|
|
9da6b76e71 | ||
|
|
5b3775761f | ||
|
|
f15b94b916 | ||
|
|
17f202ea51 | ||
|
|
f0312a9dc0 | ||
|
|
e7ce08f721 | ||
|
|
cd86eedf74 | ||
|
|
e8c102f554 | ||
|
|
35f5b302f2 | ||
|
|
6a28461e62 | ||
|
|
e5e9904389 | ||
|
|
94063679d5 | ||
|
|
4f3efd2a50 | ||
|
|
e15d47942a | ||
|
|
4a69a6ef57 | ||
|
|
29e0adeff7 | ||
|
|
00db972178 | ||
|
|
6cfeb83726 | ||
|
|
3211ad27ff | ||
|
|
eaf6fdeffb | ||
|
|
12e5f89046 | ||
|
|
a32b6fe85b | ||
|
|
11e35df3c6 | ||
|
|
d8a76cd7d7 | ||
|
|
8a40427bb4 | ||
|
|
108c52e1fd | ||
|
|
90892d435c | ||
|
|
d601e08b05 | ||
|
|
d78fb1830a | ||
|
|
d059475bf8 | ||
|
|
d5a821117d | ||
|
|
b9aa7c037c | ||
|
|
62e9db69fa | ||
|
|
00e7b22208 | ||
|
|
30c4bb3096 | ||
|
|
4ce1626a4c | ||
|
|
de9ee83904 | ||
|
|
58b47111be | ||
|
|
90c7728944 | ||
|
|
ff89c899ca | ||
|
|
81bd65b22e | ||
|
|
06093e20bd | ||
|
|
1486decbfd | ||
|
|
4949f98535 | ||
|
|
961d6a8c14 | ||
|
|
beff14fec7 | ||
|
|
cee868f98b | ||
|
|
1f7545da3b | ||
|
|
9f056b611a | ||
|
|
d2bb81879b | ||
|
|
9140430931 | ||
|
|
1c498e42c9 | ||
|
|
155f17263b | ||
|
|
1f58f2a253 | ||
|
|
1ad38b866b | ||
|
|
a7cb8a847f | ||
|
|
d53255c923 | ||
|
|
59500ea5c0 | ||
|
|
d5fcf88e0e | ||
|
|
60ed2ff417 | ||
|
|
6781c93064 | ||
|
|
12f5ebfaab | ||
|
|
9a38298846 | ||
|
|
4c126d702e | ||
|
|
137d083a45 | ||
|
|
e35bb67742 | ||
|
|
cb42b86e30 | ||
|
|
60c784f28e | ||
|
|
c5f33a707c | ||
|
|
309b423279 | ||
|
|
e228d65657 | ||
|
|
0a72c6c754 | ||
|
|
53e7893a73 | ||
|
|
dde1fd7ee3 | ||
|
|
43b6e7cb52 | ||
|
|
904d181e16 | ||
|
|
d2fcd0d127 | ||
|
|
ffde2a4d14 | ||
|
|
7fbe68769f | ||
|
|
788eb6ed58 | ||
|
|
c9709ceb15 | ||
|
|
686660075d | ||
|
|
d21842b05d | ||
|
|
8b5239ff8d | ||
|
|
24d7ead5e9 | ||
|
|
c6f1a6b1df | ||
|
|
cf78b378f8 | ||
|
|
e5090801b4 | ||
|
|
a89789df25 | ||
|
|
6d217bdc27 | ||
|
|
68fdd5f38e | ||
|
|
82ca52575b | ||
|
|
94391526ce | ||
|
|
cc44299d3e | ||
|
|
c2db2920d1 | ||
|
|
d07a0045eb | ||
|
|
07b474f2b6 | ||
|
|
1ddd9b7b23 | ||
|
|
da61669fe6 | ||
|
|
32d013257f | ||
|
|
23074ebead | ||
|
|
6a29a5cf68 | ||
|
|
7020c4ee61 | ||
|
|
0e9ae31f43 | ||
|
|
34e063824c | ||
|
|
c028918ca9 | ||
|
|
136fb01cc2 | ||
|
|
fc5f0446d3 | ||
|
|
048dc5d430 | ||
|
|
250fefbd06 | ||
|
|
04d0ab3542 | ||
|
|
ffb905641a | ||
|
|
9802d8d049 | ||
|
|
1dc886744d | ||
|
|
400a25c604 | ||
|
|
316bc13349 | ||
|
|
649cad0eb0 | ||
|
|
ca35c08dcb | ||
|
|
3778e3c276 | ||
|
|
16c70a41b5 | ||
|
|
2b4cfe4c63 | ||
|
|
2c6e40aeb0 | ||
|
|
20642650e5 | ||
|
|
308d4777ea | ||
|
|
9646c36048 | ||
|
|
1b544254c7 | ||
|
|
ec1dfb6023 | ||
|
|
a9df3f9de1 | ||
|
|
d5aa78a1ea | ||
|
|
f9a0d00367 | ||
|
|
9fe80c0897 | ||
|
|
261c778d81 | ||
|
|
dd34cfdd85 | ||
|
|
dd3ab66142 | ||
|
|
ca74c2a3af | ||
|
|
379b7ebec9 | ||
|
|
6629189efe | ||
|
|
c3788175db | ||
|
|
cfe2f0e5c7 | ||
|
|
b7a25f5023 | ||
|
|
c1860e2494 | ||
|
|
e6df9ea5b4 | ||
|
|
a438bfdf33 | ||
|
|
b30dd1094b | ||
|
|
1672168092 | ||
|
|
48ba4a3c85 | ||
|
|
fe3d20ca90 | ||
|
|
0ec9020784 | ||
|
|
38653ceda0 | ||
|
|
33c7e4f81e | ||
|
|
22294438c0 | ||
|
|
a94af1fcf3 | ||
|
|
8342ab8954 | ||
|
|
293c91ae5f | ||
|
|
5b0734f6dd | ||
|
|
c9074cc532 | ||
|
|
dabb9cc575 | ||
|
|
38c7edc919 | ||
|
|
09bf4b53aa | ||
|
|
542020e6ca | ||
|
|
c95f7a6066 | ||
|
|
7519f818cd | ||
|
|
6978072ba1 | ||
|
|
79ef7f21a5 | ||
|
|
36c1d25dce | ||
|
|
a3d1a34de7 | ||
|
|
4cd845e5dd | ||
|
|
6839948698 | ||
|
|
832f066c8e | ||
|
|
9d4cce8ef7 | ||
|
|
aac68aa4ab | ||
|
|
00e3dbb511 | ||
|
|
a7f479044d | ||
|
|
55f8bb9bed | ||
|
|
fcff58d993 | ||
|
|
dd784b7fa7 | ||
|
|
bddf3f6640 | ||
|
|
0d1faf61c2 | ||
|
|
42ce937f3e | ||
|
|
26d57c0aa0 | ||
|
|
03eaa18daf | ||
|
|
3adab08c16 | ||
|
|
739e83e28e | ||
|
|
a174cce115 | ||
|
|
367e2c14bb | ||
|
|
85bcea6a40 | ||
|
|
c62bf941a4 | ||
|
|
10fefe6f60 | ||
|
|
cf01641519 | ||
|
|
81e51335e5 | ||
|
|
416ef9d257 | ||
|
|
e724f17837 | ||
|
|
ff92273aac | ||
|
|
b2fc663cc4 | ||
|
|
f0a6f69f4f | ||
|
|
438811081c | ||
|
|
b65e0cc465 | ||
|
|
b0147d77f1 | ||
|
|
912169db80 | ||
|
|
3c6ab03a6f | ||
|
|
bcbd2257c6 | ||
|
|
d235da1eff | ||
|
|
0ae4384ec7 | ||
|
|
fd1dd6a55b | ||
|
|
7056d19df6 | ||
|
|
ebb849de3f | ||
|
|
3f4d5bdf9c | ||
|
|
f797bbbd3e | ||
|
|
5b017304e1 | ||
|
|
dcd1feece5 | ||
|
|
2843b2b87a | ||
|
|
283204e041 | ||
|
|
f1c6594561 | ||
|
|
aa1df21acc | ||
|
|
b286d1a6d7 | ||
|
|
4d2e3c47f0 | ||
|
|
5ae9c0597f | ||
|
|
2ac0ff9c3c | ||
|
|
6135b11aef | ||
|
|
683327b701 | ||
|
|
e964216079 | ||
|
|
caa5d36926 | ||
|
|
a43e36d353 | ||
|
|
0b746847e7 | ||
|
|
0131171fd3 | ||
|
|
a9c757aefd | ||
|
|
d475f8a929 | ||
|
|
ee25c96f01 | ||
|
|
ac74811722 | ||
|
|
085fa54a4e | ||
|
|
031bfc29cb | ||
|
|
8d2d4ec1e2 | ||
|
|
d03e26b4f7 | ||
|
|
359aa35abf | ||
|
|
6d3b92caee | ||
|
|
a3a03af161 | ||
|
|
2d86c6c2c0 | ||
|
|
12c149ca66 | ||
|
|
ac9e2a0b89 | ||
|
|
ba594327de | ||
|
|
71db6987bc | ||
|
|
7fcac10e8e | ||
|
|
7056cd4ea1 | ||
|
|
e6217f2951 | ||
|
|
faf07a3acf | ||
|
|
788265c45c | ||
|
|
3589a129d1 | ||
|
|
3954a61094 | ||
|
|
f775441263 | ||
|
|
262ae619c8 | ||
|
|
02ed95ecc9 | ||
|
|
3829218971 | ||
|
|
74acfe71d6 | ||
|
|
17d3abb69f | ||
|
|
8954eda6e3 | ||
|
|
82a3081bc0 | ||
|
|
146dbde1ed | ||
|
|
9fd6b6bfba | ||
|
|
03e0c2e887 | ||
|
|
191aa73376 | ||
|
|
a300a09516 | ||
|
|
36eea5da9a | ||
|
|
faad96e0fe | ||
|
|
621ac5f53a | ||
|
|
4b68c62475 | ||
|
|
494905b398 | ||
|
|
467ffd4eaa | ||
|
|
fca962ea09 | ||
|
|
c7481a534e | ||
|
|
b47155218a | ||
|
|
0b3a192084 | ||
|
|
ae6fae245c | ||
|
|
629615f314 | ||
|
|
a898bb30dc | ||
|
|
ce3d383031 | ||
|
|
ad0659d917 | ||
|
|
713e1755a0 | ||
|
|
d3cc68fd72 | ||
|
|
96140d94ae | ||
|
|
4df3800cef | ||
|
|
23c2c29c5a | ||
|
|
c0fa3a6d60 | ||
|
|
f83b1d8b47 | ||
|
|
c67e706a6e | ||
|
|
e7454ed500 | ||
|
|
9db2c828b1 | ||
|
|
dd0d768304 | ||
|
|
cfbdf044bd | ||
|
|
c4f5b31fbf | ||
|
|
6ad0a1fda9 | ||
|
|
e08d6d6d74 | ||
|
|
7a621ff48e |
2
.github/workflows/pull_request.yml
vendored
2
.github/workflows/pull_request.yml
vendored
@@ -17,8 +17,6 @@ jobs:
|
||||
- name: npm install, build, and test
|
||||
run: |
|
||||
npm install
|
||||
npm run start -- --testMode
|
||||
npm run demo
|
||||
npm run coverage
|
||||
env:
|
||||
CI: true
|
||||
|
||||
18
README.md
18
README.md
@@ -1,6 +1,6 @@
|
||||
<p>
|
||||
<a href="https://simple-keyboard.com/demo">
|
||||
<img alt="simple-keyboard: Javascript Virtual Keyboard" src="https://i.imgur.com/Po6659n.gif">
|
||||
<img alt="simple-keyboard: Javascript Virtual Keyboard" src="https://i.imgur.com/PrpbdIu.png">
|
||||
</a>
|
||||
|
||||
<a href="https://www.npmjs.com/package/simple-keyboard">
|
||||
@@ -14,10 +14,6 @@
|
||||
<a href="https://github.com/hodgef/simple-keyboard/actions">
|
||||
<img alt="Publish Status" src="https://github.com/hodgef/simple-keyboard/workflows/Publish/badge.svg?color=green" />
|
||||
</a>
|
||||
|
||||
<a href="https://bundlephobia.com/result?p=simple-keyboard">
|
||||
<img src="https://badgen.net/bundlephobia/minzip/simple-keyboard/?color=green" alt="install size">
|
||||
</a>
|
||||
|
||||
<a href="https://david-dm.org/hodgef/simple-keyboard">
|
||||
<img src="https://badgen.net/david/dep/hodgef/simple-keyboard" alt="coverage">
|
||||
@@ -30,17 +26,17 @@
|
||||
|
||||
<blockquote>Virtual Keyboard for Javascript. Compatible with your JS, React, Angular or Vue projects.</blockquote>
|
||||
|
||||
## Demo 🚀
|
||||
## 🚀 Demo
|
||||
|
||||
[Demo Showcase (Vanilla, Angular, React, Vue)](https://simple-keyboard.com/demo)
|
||||
|
||||
## Installation & Usage 📦
|
||||
## 📦 Installation & Usage
|
||||
|
||||
You can use simple-keyboard as a `<script>` tag from a CDN, or install it from npm.
|
||||
|
||||
Check out the [Getting Started](https://simple-keyboard.com/getting-started) docs to begin.
|
||||
|
||||
## Documentation 📖
|
||||
## 📖 Documentation
|
||||
|
||||
Check out the [simple-keyboard documentation](https://simple-keyboard.com/documentation) site.
|
||||
|
||||
@@ -63,7 +59,7 @@ Feel free to browse the [Questions & Answers (FAQ)](https://simple-keyboard.com/
|
||||
|
||||
<a href="https://discordapp.com/invite/SJexsCG" title="Join our Discord chat" target="_blank"><img src="https://discordapp.com/api/guilds/498978399801573396/widget.png?style=banner2" align="center"></a>
|
||||
|
||||
## Modules ✳️
|
||||
## ✳️ Modules
|
||||
|
||||
You can extend simple-keyboard's functionality with [modules](https://hodgef.com/simple-keyboard/modules/). Such as:
|
||||
|
||||
@@ -74,7 +70,7 @@ You can extend simple-keyboard's functionality with [modules](https://hodgef.com
|
||||
|
||||
Want to create your own module? Check out the [Modules page](https://hodgef.com/simple-keyboard/modules/) for instructions.
|
||||
|
||||
## Compatibility 🎯
|
||||
## 🎯 Compatibility
|
||||
|
||||
- Internet Explorer 11
|
||||
- Edge (Spartan) 16+
|
||||
@@ -84,7 +80,7 @@ Want to create your own module? Check out the [Modules page](https://hodgef.com/
|
||||
- Firefox 57+
|
||||
- iOS 9+
|
||||
|
||||
## Contributing ✅
|
||||
## ✅ Contributing
|
||||
|
||||
PRs and issues are always welcome. Feel free to submit any issues you have at:
|
||||
[https://github.com/hodgef/simple-keyboard/issues](https://github.com/hodgef/simple-keyboard/issues)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*!
|
||||
*
|
||||
* simple-keyboard v3.0.0
|
||||
* simple-keyboard v3.2.1
|
||||
* https://github.com/hodgef/simple-keyboard
|
||||
*
|
||||
* Copyright (c) Francisco Hodge (https://github.com/hodgef) and project contributors.
|
||||
@@ -8,4 +8,4 @@
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
*/.hg-theme-default{width:100%;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;box-sizing:border-box;overflow:hidden;touch-action:manipulation;font-family:HelveticaNeue-Light,Helvetica Neue Light,Helvetica Neue,Helvetica,Arial,Lucida Grande,sans-serif;background-color:#ececec;padding:5px;border-radius:5px}.hg-theme-default .hg-button span{pointer-events:none}.hg-theme-default button.hg-button{border-width:0;outline:0;font-size:inherit}.hg-theme-default .hg-button{display:inline-block;flex-grow:1}.hg-theme-default .hg-row{display:flex}.hg-theme-default .hg-row:not(:last-child){margin-bottom:5px}.hg-theme-default .hg-row .hg-button-container,.hg-theme-default .hg-row .hg-button:not(:last-child){margin-right:5px}.hg-theme-default .hg-row>div:last-child{margin-right:0}.hg-theme-default .hg-row .hg-button-container{display:flex}.hg-theme-default .hg-button{box-shadow:0 0 3px -1px rgba(0,0,0,.3);height:40px;border-radius:5px;box-sizing:border-box;padding:5px;background:#fff;border-bottom:1px solid #b5b5b5;cursor:pointer;display:flex;align-items:center;justify-content:center;-webkit-tap-highlight-color:rgba(0,0,0,0)}.hg-theme-default .hg-button.hg-standardBtn{width:20px}.hg-theme-default .hg-button.hg-activeButton{background:#efefef}.hg-theme-default.hg-layout-numeric .hg-button{width:33.3%;height:60px;align-items:center;display:flex;justify-content:center}.hg-theme-default .hg-button.hg-button-numpadadd,.hg-theme-default .hg-button.hg-button-numpadenter{height:85px}.hg-theme-default .hg-button.hg-button-numpad0{width:105px}.hg-theme-default .hg-button.hg-button-com{max-width:85px}.hg-theme-default .hg-button.hg-standardBtn.hg-button-at{max-width:45px}.hg-theme-default .hg-button.hg-selectedButton{background:rgba(5,25,70,.53);color:#fff}.hg-theme-default .hg-button.hg-standardBtn[data-skbtn=".com"]{max-width:82px}.hg-theme-default .hg-button.hg-standardBtn[data-skbtn="@"]{max-width:60px}.hg-candidate-box{display:inline-flex;border-radius:5px;position:absolute;background:#ececec;border-bottom:2px solid #b5b5b5;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;max-width:272px;transform:translateY(-100%);margin-top:-10px}ul.hg-candidate-box-list{display:flex;list-style:none;padding:0;margin:0;flex:1}li.hg-candidate-box-list-item{height:40px;width:40px;display:flex;align-items:center;justify-content:center}li.hg-candidate-box-list-item:hover{background:rgba(0,0,0,.03);cursor:pointer}li.hg-candidate-box-list-item:active{background:rgba(0,0,0,.1)}.hg-candidate-box-prev:before{content:"◄"}.hg-candidate-box-next:before{content:"►"}.hg-candidate-box-next,.hg-candidate-box-prev{display:flex;align-items:center;padding:0 10px;background:#d0d0d0;color:#969696;cursor:pointer}.hg-candidate-box-next{border-top-right-radius:5px;border-bottom-right-radius:5px}.hg-candidate-box-prev{border-top-left-radius:5px;border-bottom-left-radius:5px}.hg-candidate-box-btn-active{color:#444}
|
||||
*/.hg-theme-default{background-color:#ececec;border-radius:5px;box-sizing:border-box;font-family:HelveticaNeue-Light,Helvetica Neue Light,Helvetica Neue,Helvetica,Arial,Lucida Grande,sans-serif;overflow:hidden;padding:5px;touch-action:manipulation;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;width:100%}.hg-theme-default .hg-button span{pointer-events:none}.hg-theme-default button.hg-button{border-width:0;font-size:inherit;outline:0}.hg-theme-default .hg-button{display:inline-block;flex-grow:1}.hg-theme-default .hg-row{display:flex}.hg-theme-default .hg-row:not(:last-child){margin-bottom:5px}.hg-theme-default .hg-row .hg-button:not(:last-child){margin-right:5px}.hg-theme-default .hg-row .hg-button-container{margin-right:5px}.hg-theme-default .hg-row>div:last-child{margin-right:0}.hg-theme-default .hg-row .hg-button-container{display:flex}.hg-theme-default .hg-button{-webkit-tap-highlight-color:rgba(0,0,0,0);align-items:center;background:#fff;border-bottom:1px solid #b5b5b5;border-radius:5px;box-shadow:0 0 3px -1px rgba(0,0,0,.3);box-sizing:border-box;cursor:pointer;display:flex;height:40px;justify-content:center;padding:5px}.hg-theme-default .hg-button.hg-standardBtn{width:20px}.hg-theme-default .hg-button.hg-activeButton{background:#efefef}.hg-theme-default.hg-layout-numeric .hg-button{align-items:center;display:flex;height:60px;justify-content:center;width:33.3%}.hg-theme-default .hg-button.hg-button-numpadadd,.hg-theme-default .hg-button.hg-button-numpadenter{height:85px}.hg-theme-default .hg-button.hg-button-numpad0{width:105px}.hg-theme-default .hg-button.hg-button-com{max-width:85px}.hg-theme-default .hg-button.hg-standardBtn.hg-button-at{max-width:45px}.hg-theme-default .hg-button.hg-selectedButton{background:rgba(5,25,70,.53);color:#fff}.hg-theme-default .hg-button.hg-standardBtn[data-skbtn=".com"]{max-width:82px}.hg-theme-default .hg-button.hg-standardBtn[data-skbtn="@"]{max-width:60px}.hg-candidate-box{background:#ececec;border-bottom:2px solid #b5b5b5;border-radius:5px;display:inline-flex;margin-top:-10px;max-width:272px;position:absolute;transform:translateY(-100%);-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}ul.hg-candidate-box-list{display:flex;flex:1;list-style:none;margin:0;padding:0}li.hg-candidate-box-list-item{align-items:center;display:flex;height:40px;justify-content:center;width:40px}li.hg-candidate-box-list-item:hover{background:rgba(0,0,0,.03);cursor:pointer}li.hg-candidate-box-list-item:active{background:rgba(0,0,0,.1)}.hg-candidate-box-prev:before{content:"◄"}.hg-candidate-box-next:before{content:"►"}.hg-candidate-box-next,.hg-candidate-box-prev{align-items:center;background:#d0d0d0;color:#969696;cursor:pointer;display:flex;padding:0 10px}.hg-candidate-box-next{border-bottom-right-radius:5px;border-top-right-radius:5px}.hg-candidate-box-prev{border-bottom-left-radius:5px;border-top-left-radius:5px}.hg-candidate-box-btn-active{color:#444}
|
||||
File diff suppressed because one or more lines are too long
20
build/types/components/Keyboard.d.ts
vendored
20
build/types/components/Keyboard.d.ts
vendored
@@ -13,8 +13,8 @@ declare class SimpleKeyboard {
|
||||
input: KeyboardInput;
|
||||
options: KeyboardOptions;
|
||||
utilities: any;
|
||||
caretPosition: number;
|
||||
caretPositionEnd: number;
|
||||
caretPosition: number | null;
|
||||
caretPositionEnd: number | null;
|
||||
keyboardDOM: KeyboardElement;
|
||||
keyboardPluginClasses: string;
|
||||
keyboardDOMClass: string;
|
||||
@@ -34,8 +34,10 @@ declare class SimpleKeyboard {
|
||||
holdTimeout: number;
|
||||
isMouseHold: boolean;
|
||||
initialized: boolean;
|
||||
candidateBox: CandidateBox;
|
||||
candidateBox: CandidateBox | null;
|
||||
keyboardRowsDOM: KeyboardElement;
|
||||
defaultName: string;
|
||||
activeInputElement: HTMLInputElement | HTMLTextAreaElement | null;
|
||||
/**
|
||||
* Creates an instance of SimpleKeyboard
|
||||
* @param {Array} params If first parameter is a string, it is considered the container class. The second parameter is then considered the options object. If first parameter is an object, it is considered the options object.
|
||||
@@ -47,20 +49,20 @@ declare class SimpleKeyboard {
|
||||
handleParams: (params: KeyboardParams) => {
|
||||
keyboardDOMClass: string;
|
||||
keyboardDOM: KeyboardElement;
|
||||
options: Partial<KeyboardOptions>;
|
||||
options: Partial<KeyboardOptions | undefined>;
|
||||
};
|
||||
/**
|
||||
* Getters
|
||||
*/
|
||||
getOptions: () => KeyboardOptions;
|
||||
getCaretPosition: () => number;
|
||||
getCaretPositionEnd: () => number;
|
||||
getCaretPosition: () => number | null;
|
||||
getCaretPositionEnd: () => number | null;
|
||||
/**
|
||||
* Changes the internal caret position
|
||||
* @param {number} position The caret's start position
|
||||
* @param {number} positionEnd The caret's end position
|
||||
*/
|
||||
setCaretPosition(position: number, endPosition?: number): void;
|
||||
setCaretPosition(position: number | null, endPosition?: number | null): void;
|
||||
/**
|
||||
* Retrieve the candidates for a given input
|
||||
* @param input The input string to check
|
||||
@@ -104,7 +106,7 @@ declare class SimpleKeyboard {
|
||||
* Clear the keyboard’s input.
|
||||
* @param {string} [inputName] optional - the internal input to select
|
||||
*/
|
||||
clearInput(inputName: string): void;
|
||||
clearInput(inputName?: string): void;
|
||||
/**
|
||||
* Get the keyboard’s input (You can also get it from the onChange prop).
|
||||
* @param {string} [inputName] optional - the internal input to select
|
||||
@@ -166,7 +168,7 @@ declare class SimpleKeyboard {
|
||||
* Get the DOM Element of a button. If there are several buttons with the same name, an array of the DOM Elements is returned.
|
||||
* @param {string} button The button layout name to select
|
||||
*/
|
||||
getButtonElement(button: string): KeyboardElement | KeyboardElement[];
|
||||
getButtonElement(button: string): KeyboardElement | KeyboardElement[] | undefined;
|
||||
/**
|
||||
* This handles the "inputPattern" option
|
||||
* by checking if the provided inputPattern passes
|
||||
|
||||
28
build/types/interfaces.d.ts
vendored
28
build/types/interfaces.d.ts
vendored
@@ -3,10 +3,10 @@ import Utilities from "./services/Utilities";
|
||||
export interface KeyboardLayoutObject {
|
||||
[key: string]: string[];
|
||||
}
|
||||
export interface KeyboardButtonTheme {
|
||||
export declare type KeyboardButtonTheme = {
|
||||
class: string;
|
||||
buttons: string;
|
||||
}
|
||||
} | null;
|
||||
export interface KeyboardButtonAttributes {
|
||||
attribute: string;
|
||||
value: string;
|
||||
@@ -22,26 +22,28 @@ export declare type CandidateBoxParams = {
|
||||
export declare type CandidateBoxShowParams = {
|
||||
candidateValue: string;
|
||||
targetElement: KeyboardElement;
|
||||
onSelect: (selectedCandidate: string) => void;
|
||||
onSelect: (selectedCandidate: string, e: MouseEvent) => void;
|
||||
};
|
||||
export declare type CandidateBoxRenderParams = {
|
||||
candidateListPages: string[][];
|
||||
targetElement: KeyboardElement;
|
||||
pageIndex: number;
|
||||
nbPages: number;
|
||||
onItemSelected: (selectedCandidate: string) => void;
|
||||
onItemSelected: (selectedCandidate: string, e: MouseEvent) => void;
|
||||
};
|
||||
export declare type KeyboardElement = HTMLDivElement | HTMLButtonElement;
|
||||
export declare type KeyboardHandlerEvent = PointerEvent & TouchEvent & KeyboardEvent & {
|
||||
target: HTMLDivElement & HTMLInputElement;
|
||||
};
|
||||
export declare type KeyboardHandlerEvent = any;
|
||||
export interface KeyboardButtonElements {
|
||||
[key: string]: KeyboardElement[];
|
||||
}
|
||||
export interface UtilitiesParams {
|
||||
getOptions: () => KeyboardOptions;
|
||||
getCaretPosition: () => number;
|
||||
getCaretPositionEnd: () => number;
|
||||
getCaretPosition: () => number | null;
|
||||
getCaretPositionEnd: () => number | null;
|
||||
dispatch: any;
|
||||
}
|
||||
export interface PhysicalKeyboardParams {
|
||||
getOptions: () => KeyboardOptions;
|
||||
dispatch: any;
|
||||
}
|
||||
export interface KeyboardOptions {
|
||||
@@ -193,6 +195,14 @@ export interface KeyboardOptions {
|
||||
* Executes the callback function once simple-keyboard is rendered for the first time (on initialization).
|
||||
*/
|
||||
onInit?: (instance?: SimpleKeyboard) => void;
|
||||
/**
|
||||
* Retrieves the current input
|
||||
*/
|
||||
onChange?: (input: string, e?: MouseEvent) => any;
|
||||
/**
|
||||
* Retrieves all inputs
|
||||
*/
|
||||
onChangeAll?: (inputObj: KeyboardInput, e?: MouseEvent) => any;
|
||||
/**
|
||||
* Module options can have any format
|
||||
*/
|
||||
|
||||
8
build/types/services/PhysicalKeyboard.d.ts
vendored
8
build/types/services/PhysicalKeyboard.d.ts
vendored
@@ -1,4 +1,4 @@
|
||||
import { KeyboardOptions, UtilitiesParams } from "../interfaces";
|
||||
import { KeyboardOptions, PhysicalKeyboardParams } from "../interfaces";
|
||||
/**
|
||||
* Physical Keyboard Service
|
||||
*/
|
||||
@@ -8,7 +8,7 @@ declare class PhysicalKeyboard {
|
||||
/**
|
||||
* Creates an instance of the PhysicalKeyboard service
|
||||
*/
|
||||
constructor({ dispatch, getOptions }: Partial<UtilitiesParams>);
|
||||
constructor({ dispatch, getOptions }: PhysicalKeyboardParams);
|
||||
handleHighlightKeyDown(event: KeyboardEvent): void;
|
||||
handleHighlightKeyUp(event: KeyboardEvent): void;
|
||||
/**
|
||||
@@ -16,5 +16,9 @@ declare class PhysicalKeyboard {
|
||||
* @param {object} event The KeyboardEvent
|
||||
*/
|
||||
getSimpleKeyboardLayoutKey(event: KeyboardEvent): string;
|
||||
/**
|
||||
* Retrieve key from keyCode
|
||||
*/
|
||||
keyCodeToKey(keyCode: number): string | undefined;
|
||||
}
|
||||
export default PhysicalKeyboard;
|
||||
|
||||
22
build/types/services/Utilities.d.ts
vendored
22
build/types/services/Utilities.d.ts
vendored
@@ -5,8 +5,8 @@ import { KeyboardOptions, UtilitiesParams } from "../interfaces";
|
||||
*/
|
||||
declare class Utilities {
|
||||
getOptions: () => KeyboardOptions;
|
||||
getCaretPosition: () => number;
|
||||
getCaretPositionEnd: () => number;
|
||||
getCaretPosition: () => number | null;
|
||||
getCaretPositionEnd: () => number | null;
|
||||
dispatch: any;
|
||||
maxLengthReached: boolean;
|
||||
/**
|
||||
@@ -72,6 +72,7 @@ declare class Utilities {
|
||||
"{home}": string;
|
||||
"{pageup}": string;
|
||||
"{delete}": string;
|
||||
"{forwarddelete}": string;
|
||||
"{end}": string;
|
||||
"{pagedown}": string;
|
||||
"{numpadmultiply}": string;
|
||||
@@ -122,7 +123,7 @@ declare class Utilities {
|
||||
* @param {number} length Represents by how many characters the input should be moved
|
||||
* @param {boolean} minus Whether the cursor should be moved to the left or not.
|
||||
*/
|
||||
updateCaretPosAction(length: number, minus?: boolean): number;
|
||||
updateCaretPosAction(length: number, minus?: boolean): number | null;
|
||||
/**
|
||||
* Adds a string to the input at a given position
|
||||
*
|
||||
@@ -133,20 +134,31 @@ declare class Utilities {
|
||||
*/
|
||||
addStringAt(source: string, str: string, position?: number, positionEnd?: number, moveCaret?: boolean): string;
|
||||
/**
|
||||
* Removes an amount of characters at a given position
|
||||
* Check whether the button is a standard button
|
||||
*/
|
||||
isStandardButton: (button: string) => boolean | "";
|
||||
/**
|
||||
* Removes an amount of characters before a given position
|
||||
*
|
||||
* @param {string} source The source input
|
||||
* @param {number} position The (cursor) position from where the characters should be removed
|
||||
* @param {boolean} moveCaret Whether to update simple-keyboard's cursor
|
||||
*/
|
||||
removeAt(source: string, position?: number, positionEnd?: number, moveCaret?: boolean): string;
|
||||
/**
|
||||
* Removes an amount of characters after a given position
|
||||
*
|
||||
* @param {string} source The source input
|
||||
* @param {number} position The (cursor) position from where the characters should be removed
|
||||
*/
|
||||
removeForwardsAt(source: string, position?: number, positionEnd?: number, moveCaret?: boolean): string;
|
||||
/**
|
||||
* Determines whether the maxLength has been reached. This function is called when the maxLength option it set.
|
||||
*
|
||||
* @param {object} inputObj
|
||||
* @param {string} updatedInput
|
||||
*/
|
||||
handleMaxLength(inputObj: KeyboardInput, updatedInput: string): boolean;
|
||||
handleMaxLength(inputObj: KeyboardInput, updatedInput: string): boolean | undefined;
|
||||
/**
|
||||
* Gets the current value of maxLengthReached
|
||||
*/
|
||||
|
||||
5945
package-lock.json
generated
5945
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
52
package.json
52
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "simple-keyboard",
|
||||
"version": "3.0.0",
|
||||
"version": "3.2.1",
|
||||
"description": "On-screen Javascript Virtual Keyboard",
|
||||
"main": "build/index.js",
|
||||
"types": "build/types/index.d.ts",
|
||||
@@ -39,38 +39,38 @@
|
||||
],
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@babel/cli": "^7.13.0",
|
||||
"@babel/core": "^7.13.8",
|
||||
"@babel/plugin-proposal-class-properties": "^7.13.0",
|
||||
"@babel/plugin-transform-typescript": "^7.13.0",
|
||||
"@babel/cli": "^7.14.5",
|
||||
"@babel/core": "^7.14.6",
|
||||
"@babel/plugin-proposal-class-properties": "^7.14.5",
|
||||
"@babel/plugin-transform-typescript": "^7.14.6",
|
||||
"@babel/polyfill": "^7.12.1",
|
||||
"@babel/preset-env": "^7.13.9",
|
||||
"@types/jest": "^26.0.20",
|
||||
"@typescript-eslint/eslint-plugin": "^4.16.1",
|
||||
"@typescript-eslint/parser": "^4.16.1",
|
||||
"autoprefixer": "^10.2.5",
|
||||
"@babel/preset-env": "^7.14.7",
|
||||
"@types/jest": "^26.0.23",
|
||||
"@typescript-eslint/eslint-plugin": "^4.28.0",
|
||||
"@typescript-eslint/parser": "^4.28.0",
|
||||
"autoprefixer": "^10.2.6",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"babel-loader": "^8.2.2",
|
||||
"babel-preset-minify": "^0.5.0",
|
||||
"core-js": "^3.9.1",
|
||||
"css-loader": "^5.1.1",
|
||||
"eslint": "^7.21.0",
|
||||
"core-js": "^3.15.1",
|
||||
"css-loader": "^5.2.6",
|
||||
"eslint": "^7.29.0",
|
||||
"file-loader": "^6.2.0",
|
||||
"html-webpack-plugin": "^5.3.0",
|
||||
"html-webpack-plugin": "^5.3.2",
|
||||
"jest": "^26.6.3",
|
||||
"mini-css-extract-plugin": "^1.3.9",
|
||||
"optimize-css-assets-webpack-plugin": "^5.0.4",
|
||||
"postcss": "^8.2.8",
|
||||
"postcss-loader": "^5.2.0",
|
||||
"prettier": "^2.2.1",
|
||||
"mini-css-extract-plugin": "^1.6.1",
|
||||
"optimize-css-assets-webpack-plugin": "^6.0.1",
|
||||
"postcss": "^8.3.5",
|
||||
"postcss-loader": "^6.1.0",
|
||||
"prettier": "^2.3.2",
|
||||
"prettier-webpack-plugin": "^1.2.0",
|
||||
"style-loader": "^2.0.0",
|
||||
"terser-webpack-plugin": "^5.1.1",
|
||||
"typescript": "^4.2.3",
|
||||
"style-loader": "^3.0.0",
|
||||
"terser-webpack-plugin": "^5.1.4",
|
||||
"typescript": "^4.3.4",
|
||||
"url-loader": "^4.1.1",
|
||||
"webpack": "^5.24.3",
|
||||
"webpack-cli": "^4.5.0",
|
||||
"webpack-dev-server": "4.0.0-beta.0"
|
||||
"webpack": "^5.40.0",
|
||||
"webpack-cli": "^4.7.2",
|
||||
"webpack-dev-server": "4.0.0-beta.3"
|
||||
},
|
||||
"jest": {
|
||||
"roots": [
|
||||
@@ -81,7 +81,7 @@
|
||||
"!src/**/*.d.ts",
|
||||
"!src/lib/index.js",
|
||||
"!src/lib/polyfills.js",
|
||||
"!src/demo/index.js",
|
||||
"!src/demo/**",
|
||||
"!src/utils/**",
|
||||
"!src/**/*.d.ts",
|
||||
"!**/tests/**"
|
||||
|
||||
81
src/demo/CandidateBoxDemo.js
Normal file
81
src/demo/CandidateBoxDemo.js
Normal file
@@ -0,0 +1,81 @@
|
||||
import Keyboard from "../lib";
|
||||
import "./css/CandidateBoxDemo.css";
|
||||
|
||||
const setDOM = () => {
|
||||
document.querySelector("body").innerHTML = `
|
||||
<input class="input" placeholder="Tap on the virtual keyboard to start" />
|
||||
<div class="simple-keyboard"></div>
|
||||
`;
|
||||
};
|
||||
|
||||
class Demo {
|
||||
constructor() {
|
||||
setDOM();
|
||||
|
||||
/**
|
||||
* Demo Start
|
||||
*/
|
||||
this.keyboard = new Keyboard({
|
||||
onChange: input => this.onChange(input),
|
||||
onKeyPress: button => this.onKeyPress(button),
|
||||
preventMouseDownDefault: true,
|
||||
layoutCandidates: {
|
||||
ni: "你 尼",
|
||||
hao: "好 号"
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Update simple-keyboard when input is changed directly
|
||||
*/
|
||||
document.querySelector(".input").addEventListener("input", event => {
|
||||
this.keyboard.setInput(event.target.value);
|
||||
});
|
||||
}
|
||||
|
||||
onChange(input) {
|
||||
const inputElement = document.querySelector(".input");
|
||||
|
||||
/**
|
||||
* Updating input's value
|
||||
*/
|
||||
inputElement.value = input;
|
||||
console.log("Input changed", input);
|
||||
|
||||
/**
|
||||
* Synchronizing input caret position
|
||||
*/
|
||||
const caretPosition = this.keyboard.caretPosition;
|
||||
if (caretPosition !== null)
|
||||
this.setInputCaretPosition(inputElement, caretPosition);
|
||||
|
||||
console.log("caretPosition", caretPosition);
|
||||
}
|
||||
|
||||
setInputCaretPosition(elem, pos) {
|
||||
if (elem.setSelectionRange) {
|
||||
elem.focus();
|
||||
elem.setSelectionRange(pos, pos);
|
||||
}
|
||||
}
|
||||
|
||||
onKeyPress(button) {
|
||||
console.log("Button pressed", button);
|
||||
|
||||
/**
|
||||
* If you want to handle the shift and caps lock buttons
|
||||
*/
|
||||
if (button === "{shift}" || button === "{lock}") this.handleShift();
|
||||
}
|
||||
|
||||
handleShift() {
|
||||
const currentLayout = this.keyboard.options.layoutName;
|
||||
const shiftToggle = currentLayout === "default" ? "shift" : "default";
|
||||
|
||||
this.keyboard.setOptions({
|
||||
layoutName: shiftToggle
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default Demo;
|
||||
12
src/demo/css/CandidateBoxDemo.css
Normal file
12
src/demo/css/CandidateBoxDemo.css
Normal file
@@ -0,0 +1,12 @@
|
||||
input {
|
||||
width: 100%;
|
||||
height: 100px;
|
||||
padding: 20px;
|
||||
font-size: 20px;
|
||||
border: none;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.simple-keyboard {
|
||||
max-width: 850px;
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import BasicDemo from "./BasicDemo";
|
||||
//import DOMElementDemo from "./DOMElementDemo";
|
||||
//import FullKeyboardDemo from "./FullKeyboardDemo";
|
||||
//import MultipleKeyboardsDemo from "./MultipleKeyboardsDestroyDemo";
|
||||
//import CandidateBoxDemo from "./CandidateBoxDemo";
|
||||
|
||||
/**
|
||||
* Selected demo
|
||||
|
||||
36
src/demo/tests/CandidateBoxDemo.test.js
Normal file
36
src/demo/tests/CandidateBoxDemo.test.js
Normal file
@@ -0,0 +1,36 @@
|
||||
import { setDOM } from '../../utils/TestUtility';
|
||||
import CandidateBoxDemo from '../CandidateBoxDemo';
|
||||
|
||||
it('Demo will load', () => {
|
||||
setDOM();
|
||||
|
||||
new CandidateBoxDemo();
|
||||
});
|
||||
|
||||
it('Demo caret positioning will adjust accordingly', () => {
|
||||
setDOM();
|
||||
|
||||
const demo = new CandidateBoxDemo();
|
||||
|
||||
demo.keyboard.setCaretPosition(0);
|
||||
|
||||
demo.keyboard.getButtonElement("n").click();
|
||||
demo.keyboard.getButtonElement("h").click();
|
||||
demo.keyboard.getButtonElement("a").click();
|
||||
demo.keyboard.getButtonElement("o").click();
|
||||
expect(demo.keyboard.getCaretPosition()).toBe(4);
|
||||
|
||||
demo.keyboard.candidateBox.candidateBoxElement.querySelector("li").click();
|
||||
|
||||
expect(demo.keyboard.getCaretPosition()).toBe(2);
|
||||
|
||||
demo.keyboard.setCaretPosition(1);
|
||||
demo.keyboard.getButtonElement("i").click();
|
||||
|
||||
expect(demo.keyboard.getCaretPosition()).toBe(2);
|
||||
|
||||
demo.keyboard.candidateBox.candidateBoxElement.querySelector("li").click();
|
||||
|
||||
expect(demo.keyboard.getCaretPosition()).toBe(1);
|
||||
expect(demo.keyboard.getInput()).toBe("你好");
|
||||
});
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
|
||||
class CandidateBox {
|
||||
utilities: Utilities;
|
||||
candidateBoxElement: HTMLDivElement;
|
||||
candidateBoxElement!: HTMLDivElement;
|
||||
pageIndex = 0;
|
||||
pageSize;
|
||||
|
||||
@@ -45,8 +45,8 @@ class CandidateBox {
|
||||
targetElement,
|
||||
pageIndex: this.pageIndex,
|
||||
nbPages: candidateListPages.length,
|
||||
onItemSelected: (selectedCandidate: string) => {
|
||||
onSelect(selectedCandidate);
|
||||
onItemSelected: (selectedCandidate: string, e: MouseEvent) => {
|
||||
onSelect(selectedCandidate, e);
|
||||
this.destroy();
|
||||
},
|
||||
});
|
||||
@@ -73,9 +73,18 @@ class CandidateBox {
|
||||
// Create Candidate box list items
|
||||
candidateListPages[pageIndex].forEach((candidateListItem) => {
|
||||
const candidateListLIElement = document.createElement("li");
|
||||
const getMouseEvent = () => {
|
||||
const mouseEvent = new MouseEvent("click");
|
||||
Object.defineProperty(mouseEvent, "target", {
|
||||
value: candidateListLIElement,
|
||||
});
|
||||
return mouseEvent;
|
||||
};
|
||||
|
||||
candidateListLIElement.className = "hg-candidate-box-list-item";
|
||||
candidateListLIElement.textContent = candidateListItem;
|
||||
candidateListLIElement.onclick = () => onItemSelected(candidateListItem);
|
||||
candidateListLIElement.onclick = (e = getMouseEvent()) =>
|
||||
onItemSelected(candidateListItem, e);
|
||||
|
||||
// Append list item to ul
|
||||
candidateListULElement.appendChild(candidateListLIElement);
|
||||
|
||||
@@ -22,28 +22,30 @@ import CandidateBox from "./CandidateBox";
|
||||
* - Handles button functionality
|
||||
*/
|
||||
class SimpleKeyboard {
|
||||
input: KeyboardInput;
|
||||
options: KeyboardOptions;
|
||||
input!: KeyboardInput;
|
||||
options!: KeyboardOptions;
|
||||
utilities: any;
|
||||
caretPosition: number;
|
||||
caretPositionEnd: number;
|
||||
keyboardDOM: KeyboardElement;
|
||||
keyboardPluginClasses: string;
|
||||
keyboardDOMClass: string;
|
||||
buttonElements: KeyboardButtonElements;
|
||||
currentInstanceName: string;
|
||||
allKeyboardInstances: { [key: string]: SimpleKeyboard };
|
||||
keyboardInstanceNames: string[];
|
||||
isFirstKeyboardInstance: boolean;
|
||||
physicalKeyboard: PhysicalKeyboard;
|
||||
modules: { [key: string]: any };
|
||||
activeButtonClass: string;
|
||||
holdInteractionTimeout: number;
|
||||
holdTimeout: number;
|
||||
isMouseHold: boolean;
|
||||
initialized: boolean;
|
||||
candidateBox: CandidateBox;
|
||||
keyboardRowsDOM: KeyboardElement;
|
||||
caretPosition!: number | null;
|
||||
caretPositionEnd!: number | null;
|
||||
keyboardDOM!: KeyboardElement;
|
||||
keyboardPluginClasses!: string;
|
||||
keyboardDOMClass!: string;
|
||||
buttonElements!: KeyboardButtonElements;
|
||||
currentInstanceName!: string;
|
||||
allKeyboardInstances!: { [key: string]: SimpleKeyboard };
|
||||
keyboardInstanceNames!: string[];
|
||||
isFirstKeyboardInstance!: boolean;
|
||||
physicalKeyboard!: PhysicalKeyboard;
|
||||
modules!: { [key: string]: any };
|
||||
activeButtonClass!: string;
|
||||
holdInteractionTimeout!: number;
|
||||
holdTimeout!: number;
|
||||
isMouseHold!: boolean;
|
||||
initialized!: boolean;
|
||||
candidateBox!: CandidateBox | null;
|
||||
keyboardRowsDOM!: KeyboardElement;
|
||||
defaultName = "default";
|
||||
activeInputElement: HTMLInputElement | HTMLTextAreaElement | null = null;
|
||||
|
||||
/**
|
||||
* Creates an instance of SimpleKeyboard
|
||||
@@ -52,9 +54,11 @@ class SimpleKeyboard {
|
||||
constructor(...params: KeyboardParams) {
|
||||
if (typeof window === "undefined") return;
|
||||
|
||||
const { keyboardDOMClass, keyboardDOM, options = {} } = this.handleParams(
|
||||
params
|
||||
);
|
||||
const {
|
||||
keyboardDOMClass,
|
||||
keyboardDOM,
|
||||
options = {},
|
||||
} = this.handleParams(params);
|
||||
|
||||
/**
|
||||
* Initializing Utilities
|
||||
@@ -159,8 +163,9 @@ class SimpleKeyboard {
|
||||
* @property {object} default Default SimpleKeyboard internal input.
|
||||
* @property {object} myInputName Example input that can be set through `options.inputName:"myInputName"`.
|
||||
*/
|
||||
const { inputName = this.defaultName } = this.options;
|
||||
this.input = {};
|
||||
this.input[this.options.inputName] = "";
|
||||
this.input[inputName] = "";
|
||||
|
||||
/**
|
||||
* @type {string} DOM class of the keyboard wrapper, normally "simple-keyboard" by default.
|
||||
@@ -231,7 +236,7 @@ class SimpleKeyboard {
|
||||
): {
|
||||
keyboardDOMClass: string;
|
||||
keyboardDOM: KeyboardElement;
|
||||
options: Partial<KeyboardOptions>;
|
||||
options: Partial<KeyboardOptions | undefined>;
|
||||
} => {
|
||||
let keyboardDOMClass;
|
||||
let keyboardDOM;
|
||||
@@ -287,15 +292,15 @@ class SimpleKeyboard {
|
||||
* Getters
|
||||
*/
|
||||
getOptions = (): KeyboardOptions => this.options;
|
||||
getCaretPosition = (): number => this.caretPosition;
|
||||
getCaretPositionEnd = (): number => this.caretPositionEnd;
|
||||
getCaretPosition = (): number | null => this.caretPosition;
|
||||
getCaretPositionEnd = (): number | null => this.caretPositionEnd;
|
||||
|
||||
/**
|
||||
* Changes the internal caret position
|
||||
* @param {number} position The caret's start position
|
||||
* @param {number} positionEnd The caret's end position
|
||||
*/
|
||||
setCaretPosition(position: number, endPosition = position): void {
|
||||
setCaretPosition(position: number | null, endPosition = position): void {
|
||||
this.caretPosition = position;
|
||||
this.caretPositionEnd = endPosition;
|
||||
}
|
||||
@@ -315,8 +320,10 @@ class SimpleKeyboard {
|
||||
|
||||
const layoutCandidates = Object.keys(layoutCandidatesObj).filter(
|
||||
(layoutCandidate: string) => {
|
||||
const inputSubstr =
|
||||
input.substring(0, this.getCaretPositionEnd() || 0) || input;
|
||||
const regexp = new RegExp(`${layoutCandidate}$`, "g");
|
||||
const matches = [...input.matchAll(regexp)];
|
||||
const matches = [...inputSubstr.matchAll(regexp)];
|
||||
return !!matches.length;
|
||||
}
|
||||
);
|
||||
@@ -354,21 +361,37 @@ class SimpleKeyboard {
|
||||
this.candidateBox.show({
|
||||
candidateValue,
|
||||
targetElement,
|
||||
onSelect: (selectedCandidate: string) => {
|
||||
onSelect: (selectedCandidate: string, e: MouseEvent) => {
|
||||
const currentInput = this.getInput(this.options.inputName, true);
|
||||
const initialCaretPosition = this.getCaretPositionEnd() || 0;
|
||||
const inputSubstr =
|
||||
currentInput.substring(0, initialCaretPosition || 0) ||
|
||||
currentInput;
|
||||
|
||||
const regexp = new RegExp(`${candidateKey}$`, "g");
|
||||
const newInput = currentInput.replace(regexp, selectedCandidate);
|
||||
const newInputSubstr = inputSubstr.replace(regexp, selectedCandidate);
|
||||
const newInput = currentInput.replace(inputSubstr, newInputSubstr);
|
||||
|
||||
const caretPositionDiff = newInputSubstr.length - inputSubstr.length;
|
||||
let newCaretPosition =
|
||||
(initialCaretPosition || currentInput.length) + caretPositionDiff;
|
||||
|
||||
if (newCaretPosition < 0) newCaretPosition = 0;
|
||||
|
||||
this.setInput(newInput, this.options.inputName, true);
|
||||
this.setCaretPosition(newCaretPosition);
|
||||
|
||||
if (typeof this.options.onChange === "function")
|
||||
this.options.onChange(this.getInput(this.options.inputName, true));
|
||||
this.options.onChange(
|
||||
this.getInput(this.options.inputName, true),
|
||||
e
|
||||
);
|
||||
|
||||
/**
|
||||
* Calling onChangeAll
|
||||
*/
|
||||
if (typeof this.options.onChangeAll === "function")
|
||||
this.options.onChangeAll(this.getAllInputs());
|
||||
this.options.onChangeAll(this.getAllInputs(), e);
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -379,8 +402,7 @@ class SimpleKeyboard {
|
||||
* @param {string} button The button's layout name.
|
||||
*/
|
||||
handleButtonClicked(button: string, e?: KeyboardHandlerEvent): void {
|
||||
const debug = this.options.debug;
|
||||
|
||||
const { inputName = this.defaultName, debug } = this.options;
|
||||
/**
|
||||
* Ignoring placeholder buttons
|
||||
*/
|
||||
@@ -389,28 +411,47 @@ class SimpleKeyboard {
|
||||
/**
|
||||
* Creating inputName if it doesn't exist
|
||||
*/
|
||||
if (!this.input[this.options.inputName])
|
||||
this.input[this.options.inputName] = "";
|
||||
if (!this.input[inputName]) this.input[inputName] = "";
|
||||
|
||||
/**
|
||||
* Calculating new input
|
||||
*/
|
||||
const updatedInput = this.utilities.getUpdatedInput(
|
||||
button,
|
||||
this.input[this.options.inputName],
|
||||
this.input[inputName],
|
||||
this.caretPosition,
|
||||
this.caretPositionEnd
|
||||
);
|
||||
|
||||
/**
|
||||
* EDGE CASE: Check for whole input selection changes that will yield same updatedInput
|
||||
*/
|
||||
if (this.utilities.isStandardButton(button) && this.activeInputElement) {
|
||||
const isEntireInputSelection =
|
||||
this.input[inputName] &&
|
||||
this.input[inputName] === updatedInput &&
|
||||
this.caretPosition === 0 &&
|
||||
this.caretPositionEnd === updatedInput.length;
|
||||
|
||||
if (isEntireInputSelection) {
|
||||
this.setInput("", this.options.inputName, true);
|
||||
this.setCaretPosition(0);
|
||||
this.activeInputElement.value = "";
|
||||
this.activeInputElement.setSelectionRange(0, 0);
|
||||
this.handleButtonClicked(button, e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calling onKeyPress
|
||||
*/
|
||||
if (typeof this.options.onKeyPress === "function")
|
||||
this.options.onKeyPress(button);
|
||||
this.options.onKeyPress(button, e);
|
||||
|
||||
if (
|
||||
// If input will change as a result of this button press
|
||||
this.input[this.options.inputName] !== updatedInput &&
|
||||
this.input[inputName] !== updatedInput &&
|
||||
// This pertains to the "inputPattern" option:
|
||||
// If inputPattern isn't set
|
||||
(!this.options.inputPattern ||
|
||||
@@ -432,7 +473,7 @@ class SimpleKeyboard {
|
||||
*/
|
||||
const newInputValue = this.utilities.getUpdatedInput(
|
||||
button,
|
||||
this.input[this.options.inputName],
|
||||
this.input[inputName],
|
||||
this.caretPosition,
|
||||
this.caretPositionEnd,
|
||||
true
|
||||
@@ -460,21 +501,20 @@ class SimpleKeyboard {
|
||||
* Calling onChange
|
||||
*/
|
||||
if (typeof this.options.onChange === "function")
|
||||
this.options.onChange(this.getInput(this.options.inputName, true));
|
||||
this.options.onChange(this.getInput(this.options.inputName, true), e);
|
||||
|
||||
/**
|
||||
* Calling onChangeAll
|
||||
*/
|
||||
if (typeof this.options.onChangeAll === "function")
|
||||
this.options.onChangeAll(this.getAllInputs());
|
||||
this.options.onChangeAll(this.getAllInputs(), e);
|
||||
|
||||
/**
|
||||
* Check if this new input has candidates (suggested words)
|
||||
*/
|
||||
if (e?.target && this.options.enableLayoutCandidates) {
|
||||
const { candidateKey, candidateValue } = this.getInputCandidates(
|
||||
updatedInput
|
||||
);
|
||||
const { candidateKey, candidateValue } =
|
||||
this.getInputCandidates(updatedInput);
|
||||
|
||||
if (candidateKey && candidateValue) {
|
||||
this.showCandidatesBox(
|
||||
@@ -483,7 +523,7 @@ class SimpleKeyboard {
|
||||
this.keyboardDOM
|
||||
);
|
||||
} else {
|
||||
this.candidateBox.destroy();
|
||||
this.candidateBox?.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -639,8 +679,9 @@ class SimpleKeyboard {
|
||||
* Clear the keyboard’s input.
|
||||
* @param {string} [inputName] optional - the internal input to select
|
||||
*/
|
||||
clearInput(inputName: string): void {
|
||||
inputName = inputName || this.options.inputName;
|
||||
clearInput(
|
||||
inputName: string = this.options.inputName || this.defaultName
|
||||
): void {
|
||||
this.input[inputName] = "";
|
||||
|
||||
/**
|
||||
@@ -658,7 +699,10 @@ class SimpleKeyboard {
|
||||
* Get the keyboard’s input (You can also get it from the onChange prop).
|
||||
* @param {string} [inputName] optional - the internal input to select
|
||||
*/
|
||||
getInput(inputName = this.options.inputName, skipSync = false): string {
|
||||
getInput(
|
||||
inputName: string = this.options.inputName || this.defaultName,
|
||||
skipSync = false
|
||||
): string {
|
||||
/**
|
||||
* Enforce syncInstanceInputs, if set
|
||||
*/
|
||||
@@ -697,7 +741,7 @@ class SimpleKeyboard {
|
||||
*/
|
||||
setInput(
|
||||
input: string,
|
||||
inputName = this.options.inputName,
|
||||
inputName: string = this.options.inputName || this.defaultName,
|
||||
skipSync?: boolean
|
||||
): void {
|
||||
this.input[inputName] = input;
|
||||
@@ -758,19 +802,6 @@ class SimpleKeyboard {
|
||||
* @param {object} options The options to set
|
||||
*/
|
||||
onSetOptions(changedOptions: string[] = []): void {
|
||||
/**
|
||||
* Changed: inputName
|
||||
*/
|
||||
if (changedOptions.includes("inputName")) {
|
||||
/**
|
||||
* inputName changed. This requires a caretPosition reset
|
||||
*/
|
||||
if (this.options.debug) {
|
||||
console.log("inputName changed. caretPosition reset.");
|
||||
}
|
||||
this.setCaretPosition(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Changed: layoutName
|
||||
*/
|
||||
@@ -851,7 +882,7 @@ class SimpleKeyboard {
|
||||
* If class is already defined, we add button to class definition
|
||||
*/
|
||||
this.options.buttonTheme.map((buttonTheme) => {
|
||||
if (buttonTheme.class.split(" ").includes(classNameItem)) {
|
||||
if (buttonTheme?.class.split(" ").includes(classNameItem)) {
|
||||
classNameFound = true;
|
||||
|
||||
const buttonThemeArray = buttonTheme.buttons.split(" ");
|
||||
@@ -904,26 +935,28 @@ class SimpleKeyboard {
|
||||
) {
|
||||
const buttonArray = buttons.split(" ");
|
||||
buttonArray.forEach((button) => {
|
||||
this.options.buttonTheme.map((buttonTheme, index) => {
|
||||
this.options?.buttonTheme?.map((buttonTheme, index) => {
|
||||
/**
|
||||
* If className is set, we affect the buttons only for that class
|
||||
* Otherwise, we afect all classes
|
||||
*/
|
||||
if (
|
||||
(className && className.includes(buttonTheme.class)) ||
|
||||
(buttonTheme &&
|
||||
className &&
|
||||
className.includes(buttonTheme.class)) ||
|
||||
!className
|
||||
) {
|
||||
const filteredButtonArray = buttonTheme.buttons
|
||||
const filteredButtonArray = buttonTheme?.buttons
|
||||
.split(" ")
|
||||
.filter((item) => item !== button);
|
||||
|
||||
/**
|
||||
* If buttons left, return them, otherwise, remove button Theme
|
||||
*/
|
||||
if (filteredButtonArray.length) {
|
||||
if (buttonTheme && filteredButtonArray?.length) {
|
||||
buttonTheme.buttons = filteredButtonArray.join(" ");
|
||||
} else {
|
||||
this.options.buttonTheme.splice(index, 1);
|
||||
this.options.buttonTheme?.splice(index, 1);
|
||||
buttonTheme = null;
|
||||
}
|
||||
}
|
||||
@@ -940,7 +973,9 @@ class SimpleKeyboard {
|
||||
* Get the DOM Element of a button. If there are several buttons with the same name, an array of the DOM Elements is returned.
|
||||
* @param {string} button The button layout name to select
|
||||
*/
|
||||
getButtonElement(button: string): KeyboardElement | KeyboardElement[] {
|
||||
getButtonElement(
|
||||
button: string
|
||||
): KeyboardElement | KeyboardElement[] | undefined {
|
||||
let output;
|
||||
|
||||
const buttonArr = this.buttonElements[button];
|
||||
@@ -969,7 +1004,8 @@ class SimpleKeyboard {
|
||||
if (inputPatternRaw instanceof RegExp) {
|
||||
inputPattern = inputPatternRaw;
|
||||
} else {
|
||||
inputPattern = inputPatternRaw[this.options.inputName];
|
||||
inputPattern =
|
||||
inputPatternRaw[this.options.inputName || this.defaultName];
|
||||
}
|
||||
|
||||
if (inputPattern && inputVal) {
|
||||
@@ -1077,10 +1113,11 @@ class SimpleKeyboard {
|
||||
}
|
||||
|
||||
if (
|
||||
(targetTagName === "textarea" || targetTagName === "input") &&
|
||||
["text", "search", "url", "tel", "password"].includes(
|
||||
event.target.type
|
||||
) &&
|
||||
(targetTagName === "textarea" ||
|
||||
(targetTagName === "input" &&
|
||||
["text", "search", "url", "tel", "password"].includes(
|
||||
event.target.type
|
||||
))) &&
|
||||
!instance.options.disableCaretPositioning
|
||||
) {
|
||||
/**
|
||||
@@ -1092,6 +1129,11 @@ class SimpleKeyboard {
|
||||
event.target.selectionEnd
|
||||
);
|
||||
|
||||
/**
|
||||
* Tracking current input in order to handle caret positioning edge cases
|
||||
*/
|
||||
this.activeInputElement = event.target;
|
||||
|
||||
if (instance.options.debug) {
|
||||
console.log(
|
||||
"Caret at: ",
|
||||
@@ -1106,6 +1148,18 @@ class SimpleKeyboard {
|
||||
* If we toggled off disableCaretPositioning, we must ensure caretPosition doesn't persist once reactivated.
|
||||
*/
|
||||
instance.setCaretPosition(null);
|
||||
|
||||
/**
|
||||
* Resetting activeInputElement
|
||||
*/
|
||||
this.activeInputElement = null;
|
||||
|
||||
if (instance.options.debug) {
|
||||
console.log(
|
||||
`Caret position reset due to "${event?.type}" event`,
|
||||
event
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1146,26 +1200,25 @@ class SimpleKeyboard {
|
||||
/**
|
||||
* Remove buttons
|
||||
*/
|
||||
let deleteButton = (buttonElement: KeyboardElement) => {
|
||||
buttonElement.onpointerdown = null;
|
||||
buttonElement.onpointerup = null;
|
||||
buttonElement.onpointercancel = null;
|
||||
buttonElement.ontouchstart = null;
|
||||
buttonElement.ontouchend = null;
|
||||
buttonElement.ontouchcancel = null;
|
||||
buttonElement.onclick = null;
|
||||
buttonElement.onmousedown = null;
|
||||
buttonElement.onmouseup = null;
|
||||
const deleteButton = (buttonElement: KeyboardElement | null) => {
|
||||
if (buttonElement) {
|
||||
buttonElement.onpointerdown = null;
|
||||
buttonElement.onpointerup = null;
|
||||
buttonElement.onpointercancel = null;
|
||||
buttonElement.ontouchstart = null;
|
||||
buttonElement.ontouchend = null;
|
||||
buttonElement.ontouchcancel = null;
|
||||
buttonElement.onclick = null;
|
||||
buttonElement.onmousedown = null;
|
||||
buttonElement.onmouseup = null;
|
||||
|
||||
buttonElement.remove();
|
||||
buttonElement = null;
|
||||
buttonElement.remove();
|
||||
buttonElement = null;
|
||||
}
|
||||
};
|
||||
|
||||
this.recurseButtons(deleteButton);
|
||||
|
||||
this.recurseButtons = null;
|
||||
deleteButton = null;
|
||||
|
||||
/**
|
||||
* Remove wrapper events
|
||||
*/
|
||||
@@ -1186,6 +1239,11 @@ class SimpleKeyboard {
|
||||
this.candidateBox = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clearing activeInputElement
|
||||
*/
|
||||
this.activeInputElement = null;
|
||||
|
||||
/**
|
||||
* Clearing keyboardDOM
|
||||
*/
|
||||
@@ -1213,6 +1271,7 @@ class SimpleKeyboard {
|
||||
if (Array.isArray(buttonTheme)) {
|
||||
buttonTheme.forEach((themeObj) => {
|
||||
if (
|
||||
themeObj &&
|
||||
themeObj.class &&
|
||||
typeof themeObj.class === "string" &&
|
||||
themeObj.buttons &&
|
||||
@@ -1336,7 +1395,7 @@ class SimpleKeyboard {
|
||||
}
|
||||
|
||||
if (typeof this.options.beforeFirstRender === "function")
|
||||
this.options.beforeFirstRender();
|
||||
this.options.beforeFirstRender(this);
|
||||
|
||||
/**
|
||||
* Notify about PointerEvents usage
|
||||
@@ -1369,7 +1428,7 @@ class SimpleKeyboard {
|
||||
*/
|
||||
beforeRender() {
|
||||
if (typeof this.options.beforeRender === "function")
|
||||
this.options.beforeRender();
|
||||
this.options.beforeRender(this);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1574,211 +1633,219 @@ class SimpleKeyboard {
|
||||
/**
|
||||
* Iterating through each row
|
||||
*/
|
||||
layout[this.options.layoutName].forEach((row, rIndex) => {
|
||||
let rowArray = row.split(" ");
|
||||
|
||||
/**
|
||||
* Enforce excludeFromLayout
|
||||
*/
|
||||
if (this.options.excludeFromLayout[this.options.layoutName]) {
|
||||
rowArray = rowArray.filter(
|
||||
(buttonName) =>
|
||||
!this.options.excludeFromLayout[this.options.layoutName].includes(
|
||||
buttonName
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creating empty row
|
||||
*/
|
||||
let rowDOM = document.createElement("div");
|
||||
rowDOM.className += "hg-row";
|
||||
|
||||
/**
|
||||
* Tracking container indicators in rows
|
||||
*/
|
||||
const containerStartIndexes: number[] = [];
|
||||
const containerEndIndexes: number[] = [];
|
||||
|
||||
/**
|
||||
* Iterating through each button in row
|
||||
*/
|
||||
rowArray.forEach((button, bIndex) => {
|
||||
/**
|
||||
* Check if button has a container indicator
|
||||
*/
|
||||
const buttonHasContainerStart =
|
||||
!disableRowButtonContainers &&
|
||||
typeof button === "string" &&
|
||||
button.length > 1 &&
|
||||
button.indexOf("[") === 0;
|
||||
|
||||
const buttonHasContainerEnd =
|
||||
!disableRowButtonContainers &&
|
||||
typeof button === "string" &&
|
||||
button.length > 1 &&
|
||||
button.indexOf("]") === button.length - 1;
|
||||
layout[this.options.layoutName || this.defaultName].forEach(
|
||||
(row, rIndex) => {
|
||||
let rowArray = row.split(" ");
|
||||
|
||||
/**
|
||||
* Save container start index, if applicable
|
||||
* Enforce excludeFromLayout
|
||||
*/
|
||||
if (buttonHasContainerStart) {
|
||||
containerStartIndexes.push(bIndex);
|
||||
|
||||
/**
|
||||
* Removing indicator
|
||||
*/
|
||||
button = button.replace(/\[/g, "");
|
||||
}
|
||||
|
||||
if (buttonHasContainerEnd) {
|
||||
containerEndIndexes.push(bIndex);
|
||||
|
||||
/**
|
||||
* Removing indicator
|
||||
*/
|
||||
button = button.replace(/\]/g, "");
|
||||
}
|
||||
|
||||
/**
|
||||
* Processing button options
|
||||
*/
|
||||
const fctBtnClass = this.utilities.getButtonClass(button);
|
||||
const buttonDisplayName = this.utilities.getButtonDisplayName(
|
||||
button,
|
||||
this.options.display,
|
||||
this.options.mergeDisplay
|
||||
);
|
||||
|
||||
/**
|
||||
* Creating button
|
||||
*/
|
||||
const buttonType = this.options.useButtonTag ? "button" : "div";
|
||||
const buttonDOM = document.createElement(buttonType);
|
||||
buttonDOM.className += `hg-button ${fctBtnClass}`;
|
||||
|
||||
/**
|
||||
* Adding buttonTheme
|
||||
*/
|
||||
buttonDOM.classList.add(...this.getButtonThemeClasses(button));
|
||||
|
||||
/**
|
||||
* Adding buttonAttributes
|
||||
*/
|
||||
this.setDOMButtonAttributes(
|
||||
button,
|
||||
(attribute: string, value: string) => {
|
||||
buttonDOM.setAttribute(attribute, value);
|
||||
}
|
||||
);
|
||||
|
||||
this.activeButtonClass = "hg-activeButton";
|
||||
|
||||
/**
|
||||
* Handle button click event
|
||||
*/
|
||||
/* istanbul ignore next */
|
||||
if (
|
||||
this.utilities.pointerEventsSupported() &&
|
||||
!useTouchEvents &&
|
||||
!useMouseEvents
|
||||
this.options.excludeFromLayout &&
|
||||
this.options.excludeFromLayout[
|
||||
this.options.layoutName || this.defaultName
|
||||
]
|
||||
) {
|
||||
rowArray = rowArray.filter(
|
||||
(buttonName) =>
|
||||
this.options.excludeFromLayout &&
|
||||
!this.options.excludeFromLayout[
|
||||
this.options.layoutName || this.defaultName
|
||||
].includes(buttonName)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creating empty row
|
||||
*/
|
||||
let rowDOM = document.createElement("div");
|
||||
rowDOM.className += "hg-row";
|
||||
|
||||
/**
|
||||
* Tracking container indicators in rows
|
||||
*/
|
||||
const containerStartIndexes: number[] = [];
|
||||
const containerEndIndexes: number[] = [];
|
||||
|
||||
/**
|
||||
* Iterating through each button in row
|
||||
*/
|
||||
rowArray.forEach((button, bIndex) => {
|
||||
/**
|
||||
* Handle PointerEvents
|
||||
* Check if button has a container indicator
|
||||
*/
|
||||
buttonDOM.onpointerdown = (e: KeyboardHandlerEvent) => {
|
||||
this.handleButtonClicked(button, e);
|
||||
this.handleButtonMouseDown(button, e);
|
||||
};
|
||||
buttonDOM.onpointerup = (e: KeyboardHandlerEvent) => {
|
||||
this.handleButtonMouseUp(button, e);
|
||||
};
|
||||
buttonDOM.onpointercancel = (e: KeyboardHandlerEvent) => {
|
||||
this.handleButtonMouseUp(button, e);
|
||||
};
|
||||
} else {
|
||||
const buttonHasContainerStart =
|
||||
!disableRowButtonContainers &&
|
||||
typeof button === "string" &&
|
||||
button.length > 1 &&
|
||||
button.indexOf("[") === 0;
|
||||
|
||||
const buttonHasContainerEnd =
|
||||
!disableRowButtonContainers &&
|
||||
typeof button === "string" &&
|
||||
button.length > 1 &&
|
||||
button.indexOf("]") === button.length - 1;
|
||||
|
||||
/**
|
||||
* Fallback for browsers not supporting PointerEvents
|
||||
* Save container start index, if applicable
|
||||
*/
|
||||
if (useTouchEvents) {
|
||||
if (buttonHasContainerStart) {
|
||||
containerStartIndexes.push(bIndex);
|
||||
|
||||
/**
|
||||
* Handle touch events
|
||||
* Removing indicator
|
||||
*/
|
||||
buttonDOM.ontouchstart = (e: KeyboardHandlerEvent) => {
|
||||
button = button.replace(/\[/g, "");
|
||||
}
|
||||
|
||||
if (buttonHasContainerEnd) {
|
||||
containerEndIndexes.push(bIndex);
|
||||
|
||||
/**
|
||||
* Removing indicator
|
||||
*/
|
||||
button = button.replace(/\]/g, "");
|
||||
}
|
||||
|
||||
/**
|
||||
* Processing button options
|
||||
*/
|
||||
const fctBtnClass = this.utilities.getButtonClass(button);
|
||||
const buttonDisplayName = this.utilities.getButtonDisplayName(
|
||||
button,
|
||||
this.options.display,
|
||||
this.options.mergeDisplay
|
||||
);
|
||||
|
||||
/**
|
||||
* Creating button
|
||||
*/
|
||||
const buttonType = this.options.useButtonTag ? "button" : "div";
|
||||
const buttonDOM = document.createElement(buttonType);
|
||||
buttonDOM.className += `hg-button ${fctBtnClass}`;
|
||||
|
||||
/**
|
||||
* Adding buttonTheme
|
||||
*/
|
||||
buttonDOM.classList.add(...this.getButtonThemeClasses(button));
|
||||
|
||||
/**
|
||||
* Adding buttonAttributes
|
||||
*/
|
||||
this.setDOMButtonAttributes(
|
||||
button,
|
||||
(attribute: string, value: string) => {
|
||||
buttonDOM.setAttribute(attribute, value);
|
||||
}
|
||||
);
|
||||
|
||||
this.activeButtonClass = "hg-activeButton";
|
||||
|
||||
/**
|
||||
* Handle button click event
|
||||
*/
|
||||
/* istanbul ignore next */
|
||||
if (
|
||||
this.utilities.pointerEventsSupported() &&
|
||||
!useTouchEvents &&
|
||||
!useMouseEvents
|
||||
) {
|
||||
/**
|
||||
* Handle PointerEvents
|
||||
*/
|
||||
buttonDOM.onpointerdown = (e: KeyboardHandlerEvent) => {
|
||||
this.handleButtonClicked(button, e);
|
||||
this.handleButtonMouseDown(button, e);
|
||||
};
|
||||
buttonDOM.ontouchend = (e: KeyboardHandlerEvent) => {
|
||||
buttonDOM.onpointerup = (e: KeyboardHandlerEvent) => {
|
||||
this.handleButtonMouseUp(button, e);
|
||||
};
|
||||
buttonDOM.ontouchcancel = (e: KeyboardHandlerEvent) => {
|
||||
buttonDOM.onpointercancel = (e: KeyboardHandlerEvent) => {
|
||||
this.handleButtonMouseUp(button, e);
|
||||
};
|
||||
} else {
|
||||
/**
|
||||
* Handle mouse events
|
||||
* Fallback for browsers not supporting PointerEvents
|
||||
*/
|
||||
buttonDOM.onclick = (e: KeyboardHandlerEvent) => {
|
||||
this.isMouseHold = false;
|
||||
this.handleButtonClicked(button, e);
|
||||
};
|
||||
buttonDOM.onmousedown = (e: KeyboardHandlerEvent) => {
|
||||
this.handleButtonMouseDown(button, e);
|
||||
};
|
||||
buttonDOM.onmouseup = (e: KeyboardHandlerEvent) => {
|
||||
this.handleButtonMouseUp(button, e);
|
||||
};
|
||||
if (useTouchEvents) {
|
||||
/**
|
||||
* Handle touch events
|
||||
*/
|
||||
buttonDOM.ontouchstart = (e: KeyboardHandlerEvent) => {
|
||||
this.handleButtonClicked(button, e);
|
||||
this.handleButtonMouseDown(button, e);
|
||||
};
|
||||
buttonDOM.ontouchend = (e: KeyboardHandlerEvent) => {
|
||||
this.handleButtonMouseUp(button, e);
|
||||
};
|
||||
buttonDOM.ontouchcancel = (e: KeyboardHandlerEvent) => {
|
||||
this.handleButtonMouseUp(button, e);
|
||||
};
|
||||
} else {
|
||||
/**
|
||||
* Handle mouse events
|
||||
*/
|
||||
buttonDOM.onclick = (e: KeyboardHandlerEvent) => {
|
||||
this.isMouseHold = false;
|
||||
this.handleButtonClicked(button, e);
|
||||
};
|
||||
buttonDOM.onmousedown = (e: KeyboardHandlerEvent) => {
|
||||
this.handleButtonMouseDown(button, e);
|
||||
};
|
||||
buttonDOM.onmouseup = (e: KeyboardHandlerEvent) => {
|
||||
this.handleButtonMouseUp(button, e);
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adding identifier
|
||||
*/
|
||||
buttonDOM.setAttribute("data-skBtn", button);
|
||||
|
||||
/**
|
||||
* Adding unique id
|
||||
* Since there's no limit on spawning same buttons, the unique id ensures you can style every button
|
||||
*/
|
||||
const buttonUID = `${this.options.layoutName}-r${rIndex}b${bIndex}`;
|
||||
buttonDOM.setAttribute("data-skBtnUID", buttonUID);
|
||||
|
||||
/**
|
||||
* Adding button label to button
|
||||
*/
|
||||
const buttonSpanDOM = document.createElement("span");
|
||||
buttonSpanDOM.innerHTML = buttonDisplayName;
|
||||
buttonDOM.appendChild(buttonSpanDOM);
|
||||
|
||||
/**
|
||||
* Adding to buttonElements
|
||||
*/
|
||||
if (!this.buttonElements[button]) this.buttonElements[button] = [];
|
||||
|
||||
this.buttonElements[button].push(buttonDOM);
|
||||
|
||||
/**
|
||||
* Appending button to row
|
||||
*/
|
||||
rowDOM.appendChild(buttonDOM);
|
||||
});
|
||||
|
||||
/**
|
||||
* Adding identifier
|
||||
* Parse containers in row
|
||||
*/
|
||||
buttonDOM.setAttribute("data-skBtn", button);
|
||||
rowDOM = this.parseRowDOMContainers(
|
||||
rowDOM,
|
||||
rIndex,
|
||||
containerStartIndexes,
|
||||
containerEndIndexes
|
||||
);
|
||||
|
||||
/**
|
||||
* Adding unique id
|
||||
* Since there's no limit on spawning same buttons, the unique id ensures you can style every button
|
||||
* Appending row to hg-rows
|
||||
*/
|
||||
const buttonUID = `${this.options.layoutName}-r${rIndex}b${bIndex}`;
|
||||
buttonDOM.setAttribute("data-skBtnUID", buttonUID);
|
||||
|
||||
/**
|
||||
* Adding button label to button
|
||||
*/
|
||||
const buttonSpanDOM = document.createElement("span");
|
||||
buttonSpanDOM.innerHTML = buttonDisplayName;
|
||||
buttonDOM.appendChild(buttonSpanDOM);
|
||||
|
||||
/**
|
||||
* Adding to buttonElements
|
||||
*/
|
||||
if (!this.buttonElements[button]) this.buttonElements[button] = [];
|
||||
|
||||
this.buttonElements[button].push(buttonDOM);
|
||||
|
||||
/**
|
||||
* Appending button to row
|
||||
*/
|
||||
rowDOM.appendChild(buttonDOM);
|
||||
});
|
||||
|
||||
/**
|
||||
* Parse containers in row
|
||||
*/
|
||||
rowDOM = this.parseRowDOMContainers(
|
||||
rowDOM,
|
||||
rIndex,
|
||||
containerStartIndexes,
|
||||
containerEndIndexes
|
||||
);
|
||||
|
||||
/**
|
||||
* Appending row to hg-rows
|
||||
*/
|
||||
this.keyboardRowsDOM.appendChild(rowDOM);
|
||||
});
|
||||
this.keyboardRowsDOM.appendChild(rowDOM);
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Appending row to keyboard
|
||||
@@ -1806,7 +1873,7 @@ class SimpleKeyboard {
|
||||
!useMouseEvents
|
||||
) {
|
||||
document.onpointerup = (e: KeyboardHandlerEvent) =>
|
||||
this.handleButtonMouseUp(null, e);
|
||||
this.handleButtonMouseUp(undefined, e);
|
||||
this.keyboardDOM.onpointerdown = (e: KeyboardHandlerEvent) =>
|
||||
this.handleKeyboardContainerMouseDown(e);
|
||||
} else if (useTouchEvents) {
|
||||
@@ -1814,9 +1881,9 @@ class SimpleKeyboard {
|
||||
* Handling ontouchend, ontouchcancel
|
||||
*/
|
||||
document.ontouchend = (e: KeyboardHandlerEvent) =>
|
||||
this.handleButtonMouseUp(null, e);
|
||||
this.handleButtonMouseUp(undefined, e);
|
||||
document.ontouchcancel = (e: KeyboardHandlerEvent) =>
|
||||
this.handleButtonMouseUp(null, e);
|
||||
this.handleButtonMouseUp(undefined, e);
|
||||
|
||||
this.keyboardDOM.ontouchstart = (e: KeyboardHandlerEvent) =>
|
||||
this.handleKeyboardContainerMouseDown(e);
|
||||
@@ -1825,7 +1892,7 @@ class SimpleKeyboard {
|
||||
* Handling mouseup
|
||||
*/
|
||||
document.onmouseup = (e: KeyboardHandlerEvent) =>
|
||||
this.handleButtonMouseUp(null, e);
|
||||
this.handleButtonMouseUp(undefined, e);
|
||||
this.keyboardDOM.onmousedown = (e: KeyboardHandlerEvent) =>
|
||||
this.handleKeyboardContainerMouseDown(e);
|
||||
}
|
||||
|
||||
@@ -216,7 +216,7 @@ it('CandidateBox select candidate will work', () => {
|
||||
keyboard.getButtonElement("a").click();
|
||||
keyboard.candidateBox.candidateBoxElement.querySelector("li").click();
|
||||
|
||||
expect(onSelect).toBeCalledWith("1");
|
||||
expect(onSelect).toBeCalledWith("1", expect.anything());
|
||||
keyboard.destroy();
|
||||
});
|
||||
|
||||
@@ -353,6 +353,7 @@ it('CandidateBox selection should trigger onChange', () => {
|
||||
});
|
||||
|
||||
const candidateBoxRenderFn = keyboard.candidateBox.renderPage;
|
||||
|
||||
jest.spyOn(keyboard.candidateBox, "renderPage").mockImplementation((params) => {
|
||||
candidateBoxOnItemSelected = params.onItemSelected;
|
||||
params.onItemSelected = onSelect;
|
||||
@@ -362,10 +363,10 @@ it('CandidateBox selection should trigger onChange', () => {
|
||||
keyboard.getButtonElement("a").click();
|
||||
keyboard.candidateBox.candidateBoxElement.querySelector("li").click();
|
||||
|
||||
expect(keyboard.options.onChange).toBeCalledWith("a");
|
||||
expect(keyboard.options.onChangeAll).toBeCalledWith({"default": "a"});
|
||||
expect(keyboard.options.onChange.mock.calls[0][0]).toBe("a");
|
||||
expect(keyboard.options.onChangeAll.mock.calls[0][0]).toMatchObject({"default": "a"});
|
||||
|
||||
expect(keyboard.options.onChange).toBeCalledWith("1");
|
||||
expect(keyboard.options.onChangeAll).toBeCalledWith({"default": "1"});
|
||||
expect(keyboard.options.onChange.mock.calls[1][0]).toBe("1");
|
||||
expect(keyboard.options.onChangeAll.mock.calls[1][0]).toMatchObject({"default": "1"});
|
||||
keyboard.destroy();
|
||||
});
|
||||
@@ -1138,27 +1138,6 @@ it('Keyboard disableRowButtonContainers will bypass parseRowDOMContainers', () =
|
||||
expect(containers.length).toBe(0);
|
||||
});
|
||||
|
||||
it('Keyboard inputName change will trigget caretPosition reset', () => {
|
||||
const keyboard = new Keyboard();
|
||||
|
||||
keyboard.setCaretPosition(0);
|
||||
|
||||
keyboard.getButtonElement("q").onpointerdown();
|
||||
keyboard.getButtonElement("1").onpointerdown();
|
||||
|
||||
expect(keyboard.getCaretPosition()).toBe(2);
|
||||
|
||||
keyboard.setOptions({
|
||||
inputName: "myInput"
|
||||
});
|
||||
|
||||
keyboard.getButtonElement("q").onpointerdown();
|
||||
keyboard.getButtonElement("1").onpointerdown();
|
||||
keyboard.getButtonElement("b").onpointerdown();
|
||||
|
||||
expect(keyboard.getCaretPosition()).toBe(null);
|
||||
});
|
||||
|
||||
it('Keyboard destroy will work', () => {
|
||||
const keyboard = new Keyboard();
|
||||
keyboard.destroy();
|
||||
@@ -1351,4 +1330,25 @@ it('Keyboard excludeFromLayout will work', () => {
|
||||
it('Keyboard onSetOptions can be called without changedOptions param', () => {
|
||||
const keyboard = new Keyboard();
|
||||
expect(keyboard.onSetOptions()).toBeUndefined();
|
||||
});
|
||||
|
||||
it('Keyboard will handle selected input with unchanged updatedInput edge case', () => {
|
||||
const inputElem = document.createElement("input");
|
||||
const onChange = jest.fn();
|
||||
const keyboard = new Keyboard({ onChange });
|
||||
|
||||
const initialValue = "3";
|
||||
inputElem.value = initialValue;
|
||||
inputElem.select();
|
||||
keyboard.setInput(initialValue);
|
||||
keyboard.activeInputElement = inputElem;
|
||||
keyboard.setCaretPosition(0, 1);
|
||||
|
||||
keyboard.getButtonElement("3").onpointerdown();
|
||||
keyboard.getButtonElement("3").onpointerdown();
|
||||
|
||||
expect(onChange).toBeCalledTimes(2);
|
||||
expect(keyboard.getInput()).toBe("33");
|
||||
expect(keyboard.getCaretPosition()).toBe(2);
|
||||
expect(keyboard.getCaretPositionEnd()).toBe(2);
|
||||
});
|
||||
@@ -6,10 +6,10 @@ export interface KeyboardLayoutObject {
|
||||
[key: string]: string[];
|
||||
}
|
||||
|
||||
export interface KeyboardButtonTheme {
|
||||
export type KeyboardButtonTheme = {
|
||||
class: string;
|
||||
buttons: string;
|
||||
}
|
||||
} | null;
|
||||
|
||||
export interface KeyboardButtonAttributes {
|
||||
attribute: string;
|
||||
@@ -31,7 +31,7 @@ export type CandidateBoxShowParams = {
|
||||
candidateValue: string,
|
||||
targetElement: KeyboardElement,
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
onSelect: (selectedCandidate: string) => void
|
||||
onSelect: (selectedCandidate: string, e: MouseEvent) => void
|
||||
}
|
||||
|
||||
export type CandidateBoxRenderParams = {
|
||||
@@ -40,11 +40,11 @@ export type CandidateBoxRenderParams = {
|
||||
pageIndex: number;
|
||||
nbPages: number;
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
onItemSelected: (selectedCandidate: string) => void
|
||||
onItemSelected: (selectedCandidate: string, e: MouseEvent) => void
|
||||
}
|
||||
|
||||
export type KeyboardElement = HTMLDivElement | HTMLButtonElement;
|
||||
export type KeyboardHandlerEvent = PointerEvent & TouchEvent & KeyboardEvent & { target: HTMLDivElement & HTMLInputElement };
|
||||
export type KeyboardHandlerEvent = any;
|
||||
|
||||
export interface KeyboardButtonElements {
|
||||
[key: string]: KeyboardElement[]
|
||||
@@ -52,8 +52,13 @@ export interface KeyboardButtonElements {
|
||||
|
||||
export interface UtilitiesParams {
|
||||
getOptions: () => KeyboardOptions;
|
||||
getCaretPosition: () => number;
|
||||
getCaretPositionEnd: () => number;
|
||||
getCaretPosition: () => number | null;
|
||||
getCaretPositionEnd: () => number | null;
|
||||
dispatch: any;
|
||||
}
|
||||
|
||||
export interface PhysicalKeyboardParams {
|
||||
getOptions: () => KeyboardOptions;
|
||||
dispatch: any;
|
||||
}
|
||||
|
||||
@@ -235,6 +240,16 @@ export interface KeyboardOptions {
|
||||
*/
|
||||
onInit?: (instance?: SimpleKeyboard) => void;
|
||||
|
||||
/**
|
||||
* Retrieves the current input
|
||||
*/
|
||||
onChange?: (input: string, e?: MouseEvent) => any;
|
||||
|
||||
/**
|
||||
* Retrieves all inputs
|
||||
*/
|
||||
onChangeAll?: (inputObj: KeyboardInput, e?: MouseEvent) => any;
|
||||
|
||||
/**
|
||||
* Module options can have any format
|
||||
*/
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { KeyboardOptions, UtilitiesParams } from "../interfaces";
|
||||
import { KeyboardOptions, PhysicalKeyboardParams } from "../interfaces";
|
||||
import Utilities from "../services/Utilities";
|
||||
|
||||
/**
|
||||
@@ -11,7 +11,7 @@ class PhysicalKeyboard {
|
||||
/**
|
||||
* Creates an instance of the PhysicalKeyboard service
|
||||
*/
|
||||
constructor({ dispatch, getOptions }: Partial<UtilitiesParams>) {
|
||||
constructor({ dispatch, getOptions }: PhysicalKeyboardParams) {
|
||||
/**
|
||||
* @type {object} A simple-keyboard instance
|
||||
*/
|
||||
@@ -40,22 +40,13 @@ class PhysicalKeyboard {
|
||||
options.physicalKeyboardHighlightTextColor || "black";
|
||||
|
||||
if (options.physicalKeyboardHighlightPress) {
|
||||
/**
|
||||
* Trigger pointerdown
|
||||
*/
|
||||
(
|
||||
buttonDOM.onpointerdown ||
|
||||
buttonDOM.onmousedown ||
|
||||
buttonDOM.ontouchstart ||
|
||||
Utilities.noop
|
||||
)();
|
||||
buttonDOM.click();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
handleHighlightKeyUp(event: KeyboardEvent) {
|
||||
const options = this.getOptions();
|
||||
const buttonPressed = this.getSimpleKeyboardLayoutKey(event);
|
||||
|
||||
this.dispatch((instance: any) => {
|
||||
@@ -65,18 +56,6 @@ class PhysicalKeyboard {
|
||||
|
||||
if (buttonDOM && buttonDOM.removeAttribute) {
|
||||
buttonDOM.removeAttribute("style");
|
||||
|
||||
if (options.physicalKeyboardHighlightPress) {
|
||||
/**
|
||||
* Trigger pointerup
|
||||
*/
|
||||
(
|
||||
buttonDOM.onpointerup ||
|
||||
buttonDOM.onmouseup ||
|
||||
buttonDOM.ontouchend ||
|
||||
Utilities.noop
|
||||
)();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -86,35 +65,129 @@ class PhysicalKeyboard {
|
||||
* @param {object} event The KeyboardEvent
|
||||
*/
|
||||
getSimpleKeyboardLayoutKey(event: KeyboardEvent) {
|
||||
let output;
|
||||
let output = "";
|
||||
const keyId = event.code || event.key || this.keyCodeToKey(event?.keyCode);
|
||||
|
||||
if (
|
||||
event.code.includes("Numpad") ||
|
||||
event.code.includes("Shift") ||
|
||||
event.code.includes("Space") ||
|
||||
event.code.includes("Backspace") ||
|
||||
event.code.includes("Control") ||
|
||||
event.code.includes("Alt") ||
|
||||
event.code.includes("Meta")
|
||||
keyId?.includes("Numpad") ||
|
||||
keyId?.includes("Shift") ||
|
||||
keyId?.includes("Space") ||
|
||||
keyId?.includes("Backspace") ||
|
||||
keyId?.includes("Control") ||
|
||||
keyId?.includes("Alt") ||
|
||||
keyId?.includes("Meta")
|
||||
) {
|
||||
output = event.code;
|
||||
output = event.code || "";
|
||||
} else {
|
||||
output = event.key;
|
||||
output = event.key || this.keyCodeToKey(event?.keyCode) || "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Casting key to lowercase
|
||||
*/
|
||||
if (
|
||||
(output && output !== output.toUpperCase()) ||
|
||||
(event.code[0] === "F" &&
|
||||
Number.isInteger(Number(event.code[1])) &&
|
||||
event.code.length <= 3)
|
||||
) {
|
||||
output = output ? output.toLowerCase() : output;
|
||||
}
|
||||
return output.length > 1 ? output?.toLowerCase() : output;
|
||||
}
|
||||
|
||||
return output;
|
||||
/**
|
||||
* Retrieve key from keyCode
|
||||
*/
|
||||
keyCodeToKey(keyCode: number) {
|
||||
return {
|
||||
8: "Backspace",
|
||||
9: "Tab",
|
||||
13: "Enter",
|
||||
16: "Shift",
|
||||
17: "Ctrl",
|
||||
18: "Alt",
|
||||
19: "Pause",
|
||||
20: "CapsLock",
|
||||
27: "Esc",
|
||||
32: "Space",
|
||||
33: "PageUp",
|
||||
34: "PageDown",
|
||||
35: "End",
|
||||
36: "Home",
|
||||
37: "ArrowLeft",
|
||||
38: "ArrowUp",
|
||||
39: "ArrowRight",
|
||||
40: "ArrowDown",
|
||||
45: "Insert",
|
||||
46: "Delete",
|
||||
48: "0",
|
||||
49: "1",
|
||||
50: "2",
|
||||
51: "3",
|
||||
52: "4",
|
||||
53: "5",
|
||||
54: "6",
|
||||
55: "7",
|
||||
56: "8",
|
||||
57: "9",
|
||||
65: "A",
|
||||
66: "B",
|
||||
67: "C",
|
||||
68: "D",
|
||||
69: "E",
|
||||
70: "F",
|
||||
71: "G",
|
||||
72: "H",
|
||||
73: "I",
|
||||
74: "J",
|
||||
75: "K",
|
||||
76: "L",
|
||||
77: "M",
|
||||
78: "N",
|
||||
79: "O",
|
||||
80: "P",
|
||||
81: "Q",
|
||||
82: "R",
|
||||
83: "S",
|
||||
84: "T",
|
||||
85: "U",
|
||||
86: "V",
|
||||
87: "W",
|
||||
88: "X",
|
||||
89: "Y",
|
||||
90: "Z",
|
||||
91: "Meta",
|
||||
96: "Numpad0",
|
||||
97: "Numpad1",
|
||||
98: "Numpad2",
|
||||
99: "Numpad3",
|
||||
100: "Numpad4",
|
||||
101: "Numpad5",
|
||||
102: "Numpad6",
|
||||
103: "Numpad7",
|
||||
104: "Numpad8",
|
||||
105: "Numpad9",
|
||||
106: "NumpadMultiply",
|
||||
107: "NumpadAdd",
|
||||
109: "NumpadSubtract",
|
||||
110: "NumpadDecimal",
|
||||
111: "NumpadDivide",
|
||||
112: "F1",
|
||||
113: "F2",
|
||||
114: "F3",
|
||||
115: "F4",
|
||||
116: "F5",
|
||||
117: "F6",
|
||||
118: "F7",
|
||||
119: "F8",
|
||||
120: "F9",
|
||||
121: "F10",
|
||||
122: "F11",
|
||||
123: "F12",
|
||||
144: "NumLock",
|
||||
145: "ScrollLock",
|
||||
186: ";",
|
||||
187: "=",
|
||||
188: ",",
|
||||
189: "-",
|
||||
190: ".",
|
||||
191: "/",
|
||||
192: "`",
|
||||
219: "[",
|
||||
220: "\\",
|
||||
221: "]",
|
||||
222: "'",
|
||||
}[keyCode];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,10 +6,10 @@ import { KeyboardOptions, UtilitiesParams } from "../interfaces";
|
||||
*/
|
||||
class Utilities {
|
||||
getOptions: () => KeyboardOptions;
|
||||
getCaretPosition: () => number;
|
||||
getCaretPositionEnd: () => number;
|
||||
getCaretPosition: () => number | null;
|
||||
getCaretPositionEnd: () => number | null;
|
||||
dispatch: any;
|
||||
maxLengthReached: boolean;
|
||||
maxLengthReached!: boolean;
|
||||
|
||||
/**
|
||||
* Creates an instance of the Utility service
|
||||
@@ -106,6 +106,7 @@ class Utilities {
|
||||
"{home}": "home",
|
||||
"{pageup}": "up",
|
||||
"{delete}": "del",
|
||||
"{forwarddelete}": "del",
|
||||
"{end}": "end",
|
||||
"{pagedown}": "down",
|
||||
"{numpadmultiply}": "*",
|
||||
@@ -177,6 +178,11 @@ class Utilities {
|
||||
output.length > 0
|
||||
) {
|
||||
output = this.removeAt(output, ...commonParams);
|
||||
} else if (
|
||||
(button === "{delete}" || button === "{forwarddelete}") &&
|
||||
output.length > 0
|
||||
) {
|
||||
output = this.removeForwardsAt(output, ...commonParams);
|
||||
} else if (button === "{space}")
|
||||
output = this.addStringAt(output, " ", ...commonParams);
|
||||
else if (
|
||||
@@ -243,10 +249,12 @@ class Utilities {
|
||||
const options = this.getOptions();
|
||||
let caretPosition = this.getCaretPosition();
|
||||
|
||||
if (minus) {
|
||||
if (caretPosition > 0) caretPosition = caretPosition - length;
|
||||
} else {
|
||||
caretPosition = caretPosition + length;
|
||||
if (caretPosition != null) {
|
||||
if (minus) {
|
||||
if (caretPosition > 0) caretPosition = caretPosition - length;
|
||||
} else {
|
||||
caretPosition = caretPosition + length;
|
||||
}
|
||||
}
|
||||
|
||||
if (options.debug) {
|
||||
@@ -292,7 +300,13 @@ class Utilities {
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an amount of characters at a given position
|
||||
* Check whether the button is a standard button
|
||||
*/
|
||||
isStandardButton = (button: string) =>
|
||||
button && !(button[0] === "{" && button[button.length - 1] === "}");
|
||||
|
||||
/**
|
||||
* Removes an amount of characters before a given position
|
||||
*
|
||||
* @param {string} source The source input
|
||||
* @param {number} position The (cursor) position from where the characters should be removed
|
||||
@@ -353,6 +367,52 @@ class Utilities {
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an amount of characters after a given position
|
||||
*
|
||||
* @param {string} source The source input
|
||||
* @param {number} position The (cursor) position from where the characters should be removed
|
||||
*/
|
||||
removeForwardsAt(
|
||||
source: string,
|
||||
position: number = source.length,
|
||||
positionEnd: number = source.length,
|
||||
moveCaret = false
|
||||
) {
|
||||
if (!source?.length || position === null) {
|
||||
return source;
|
||||
}
|
||||
|
||||
let output;
|
||||
|
||||
if (position === positionEnd) {
|
||||
const emojiMatchedReg = /([\uD800-\uDBFF][\uDC00-\uDFFF])/g;
|
||||
|
||||
/**
|
||||
* Emojis are made out of two characters, so we must take a custom approach to trim them.
|
||||
* For more info: https://mathiasbynens.be/notes/javascript-unicode
|
||||
*/
|
||||
const nextTwoChars = source.substring(position, position + 2);
|
||||
const emojiMatched = nextTwoChars.match(emojiMatchedReg);
|
||||
|
||||
if (emojiMatched) {
|
||||
output = source.substr(0, position) + source.substr(position + 2);
|
||||
} else {
|
||||
output = source.substr(0, position) + source.substr(position + 1);
|
||||
}
|
||||
} else {
|
||||
output = source.slice(0, position) + source.slice(positionEnd);
|
||||
if (moveCaret) {
|
||||
this.dispatch((instance: any) => {
|
||||
instance.setCaretPosition(position);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the maxLength has been reached. This function is called when the maxLength option it set.
|
||||
*
|
||||
@@ -362,7 +422,7 @@ class Utilities {
|
||||
handleMaxLength(inputObj: KeyboardInput, updatedInput: string) {
|
||||
const options = this.getOptions();
|
||||
const maxLength = options.maxLength;
|
||||
const currentInput = inputObj[options.inputName];
|
||||
const currentInput = inputObj[options.inputName || "default"];
|
||||
const condition = updatedInput.length - 1 >= maxLength;
|
||||
|
||||
if (
|
||||
@@ -393,7 +453,8 @@ class Utilities {
|
||||
}
|
||||
|
||||
if (typeof maxLength === "object") {
|
||||
const condition = updatedInput.length - 1 >= maxLength[options.inputName];
|
||||
const condition =
|
||||
updatedInput.length - 1 >= maxLength[options.inputName || "default"];
|
||||
|
||||
if (options.debug) {
|
||||
console.log("maxLength (obj) reached:", condition);
|
||||
@@ -451,7 +512,7 @@ class Utilities {
|
||||
* @param {string} str The string to transform.
|
||||
*/
|
||||
camelCase(str: string): string {
|
||||
if (!str) return;
|
||||
if (!str) return "";
|
||||
|
||||
return str
|
||||
.toLowerCase()
|
||||
|
||||
@@ -201,4 +201,25 @@ it('PhysicalKeyboard with physicalKeyboardHighlightPress can trigger noop', () =
|
||||
tagName: "input"
|
||||
}
|
||||
}));
|
||||
});
|
||||
|
||||
it('PhysicalKeyboard keyCodeToKey will work', () => {
|
||||
setDOM();
|
||||
|
||||
const keyboard = new Keyboard({
|
||||
physicalKeyboardHighlight: true
|
||||
});
|
||||
|
||||
expect(keyboard.physicalKeyboard.keyCodeToKey(186)).toBe(";");
|
||||
|
||||
const methodTest = spyOn(keyboard.physicalKeyboard, "keyCodeToKey");
|
||||
|
||||
document.dispatchEvent(new KeyboardEvent('keyup', {
|
||||
keyCode: 186,
|
||||
target: {
|
||||
tagName: "input"
|
||||
}
|
||||
}));
|
||||
|
||||
expect(methodTest).toBeCalledWith(186);
|
||||
});
|
||||
@@ -34,6 +34,16 @@ it('Keyboard {bksp} button will work', () => {
|
||||
expect(output).toBe("tes");
|
||||
});
|
||||
|
||||
it('Keyboard {delete} button will work', () => {
|
||||
setDOM();
|
||||
|
||||
const keyboard = new Keyboard();
|
||||
|
||||
const output = keyboard.utilities.getUpdatedInput("{delete}", "test", 1);
|
||||
|
||||
expect(output).toBe("tst");
|
||||
});
|
||||
|
||||
it('Keyboard {space} button will work', () => {
|
||||
setDOM();
|
||||
|
||||
@@ -585,3 +595,167 @@ it('Keyboard camelCase will work with empty strings', () => {
|
||||
const keyboard = new Keyboard();
|
||||
expect(keyboard.utilities.camelCase()).toBeFalsy();
|
||||
});
|
||||
|
||||
it('Keyboard removeForwardsAt will exit out on caretPosition:0', () => {
|
||||
setDOM();
|
||||
|
||||
const keyboard = new Keyboard();
|
||||
|
||||
keyboard.setInput("test");
|
||||
|
||||
keyboard.setCaretPosition(0);
|
||||
keyboard.utilities.removeForwardsAt(keyboard.getInput(), 0);
|
||||
expect(keyboard.getInput()).toBe("test");
|
||||
|
||||
keyboard.setInput("test");
|
||||
|
||||
keyboard.setCaretPosition(5);
|
||||
keyboard.utilities.removeForwardsAt(keyboard.getInput(), 0, 0, true);
|
||||
expect(keyboard.caretPosition).toBe(5);
|
||||
});
|
||||
|
||||
it('Keyboard removeForwardsAt will remove multi-byte unicodes with caretPos>0', () => {
|
||||
setDOM();
|
||||
|
||||
const keyboard = new Keyboard();
|
||||
|
||||
keyboard.setCaretPosition(4);
|
||||
let output = keyboard.utilities.removeForwardsAt("test\uD83D\uDE00", 4, 4);
|
||||
expect(output).toBe("test");
|
||||
|
||||
keyboard.setCaretPosition(4);
|
||||
output = keyboard.utilities.removeForwardsAt("test\uD83D\uDE00", 4, 4, true);
|
||||
expect(keyboard.caretPosition).toBe(4);
|
||||
});
|
||||
|
||||
it('Keyboard removeForwardsAt will not remove multi-byte unicodes with caretPos:0', () => {
|
||||
setDOM();
|
||||
|
||||
const str = "\uD83D\uDE00";
|
||||
const keyboard = new Keyboard();
|
||||
let output = keyboard.utilities.removeForwardsAt(str, 0);
|
||||
expect(output).toBe("");
|
||||
|
||||
output = keyboard.utilities.removeForwardsAt(str, 0, 0, true);
|
||||
expect(output).toBe("");
|
||||
});
|
||||
|
||||
it('Keyboard removeForwardsAt will propagate caretPosition', () => {
|
||||
clearDOM();
|
||||
|
||||
document.body.innerHTML = `
|
||||
<div class="simple-keyboard"></div>
|
||||
<div class="keyboard2"></div>
|
||||
`;
|
||||
|
||||
const keyboard = new Keyboard({
|
||||
useMouseEvents: true,
|
||||
layout: {
|
||||
default: ["{delete}"]
|
||||
}
|
||||
});
|
||||
const keyboard2 = new Keyboard('.keyboard2');
|
||||
|
||||
keyboard.input.default = "hello";
|
||||
keyboard2.input.default = "world"
|
||||
|
||||
keyboard.setCaretPosition(1);
|
||||
expect(keyboard.getCaretPosition()).toBe(1);
|
||||
expect(keyboard.getCaretPositionEnd()).toBe(1);
|
||||
|
||||
keyboard.setCaretPosition(1, 3);
|
||||
expect(keyboard.getCaretPosition()).toBe(1);
|
||||
expect(keyboard.getCaretPositionEnd()).toBe(3);
|
||||
|
||||
keyboard.getButtonElement('{delete}').onclick();
|
||||
|
||||
expect(keyboard.getCaretPosition()).toBe(1);
|
||||
expect(keyboard2.getCaretPosition()).toBe(1);
|
||||
|
||||
expect(keyboard.getInput()).toBe('hlo');
|
||||
expect(keyboard.getCaretPositionEnd()).toBe(1);
|
||||
expect(keyboard2.getCaretPositionEnd()).toBe(1);
|
||||
});
|
||||
|
||||
it('Keyboard removeForwardsAt will propagate caretPosition in a syncInstanceInputs setting', () => {
|
||||
clearDOM();
|
||||
|
||||
document.body.innerHTML = `
|
||||
<div class="simple-keyboard"></div>
|
||||
<div class="keyboard2"></div>
|
||||
`;
|
||||
|
||||
const keyboard = new Keyboard({
|
||||
useMouseEvents: true,
|
||||
syncInstanceInputs: true,
|
||||
layout: {
|
||||
default: ["{delete}"]
|
||||
}
|
||||
});
|
||||
const keyboard2 = new Keyboard('.keyboard2');
|
||||
|
||||
keyboard.input.default = "hello"
|
||||
|
||||
keyboard.setCaretPosition(1);
|
||||
expect(keyboard.getCaretPosition()).toBe(1);
|
||||
expect(keyboard.getCaretPositionEnd()).toBe(1);
|
||||
|
||||
keyboard.setCaretPosition(1, 3);
|
||||
expect(keyboard.getCaretPosition()).toBe(1);
|
||||
expect(keyboard.getCaretPositionEnd()).toBe(3);
|
||||
|
||||
keyboard.getButtonElement('{delete}').onclick();
|
||||
|
||||
expect(keyboard.getCaretPosition()).toBe(1);
|
||||
expect(keyboard2.getCaretPosition()).toBe(1);
|
||||
|
||||
expect(keyboard.getInput()).toBe('hlo');
|
||||
expect(keyboard.getCaretPositionEnd()).toBe(1);
|
||||
expect(keyboard2.getCaretPositionEnd()).toBe(1);
|
||||
});
|
||||
|
||||
it('Keyboard removeForwardsAt will remove regular strings', () => {
|
||||
setDOM();
|
||||
|
||||
const keyboard = new Keyboard({
|
||||
debug: true
|
||||
});
|
||||
|
||||
keyboard.setCaretPosition(6);
|
||||
let output = keyboard.utilities.removeForwardsAt("testie", 5, 5);
|
||||
expect(output).toBe("testi");
|
||||
|
||||
keyboard.setCaretPosition(5);
|
||||
output = keyboard.utilities.removeForwardsAt("testie", 5, 5, true);
|
||||
expect(keyboard.caretPosition).toBe(5);
|
||||
});
|
||||
|
||||
it('Keyboard removeForwardsAt will work with unset or start caretPosition', () => {
|
||||
setDOM();
|
||||
|
||||
const keyboard = new Keyboard({
|
||||
debug: true
|
||||
});
|
||||
|
||||
let output = keyboard.utilities.removeForwardsAt("test", 3);
|
||||
expect(output).toBe("tes");
|
||||
|
||||
output = keyboard.utilities.removeForwardsAt("test", null, null);
|
||||
expect(output).toBe("test");
|
||||
|
||||
output = keyboard.utilities.removeForwardsAt("😀", 0);
|
||||
expect(output).toBe("");
|
||||
|
||||
/**
|
||||
* Will also work with moveCaret
|
||||
*/
|
||||
output = keyboard.utilities.removeForwardsAt("test", null, null, true);
|
||||
expect(output).toBe("test");
|
||||
expect(keyboard.getCaretPosition()).toBe(null);
|
||||
|
||||
keyboard.setCaretPosition(2);
|
||||
const str = "😀";
|
||||
output = keyboard.utilities.removeForwardsAt(str, null, null, true);
|
||||
expect(output).toBe(str);
|
||||
expect(keyboard.getCaretPosition()).toBe(2);
|
||||
});
|
||||
@@ -11,7 +11,8 @@
|
||||
"suppressImplicitAnyIndexErrors": true,
|
||||
"lib": ["es2020", "dom"],
|
||||
"moduleResolution": "node",
|
||||
"downlevelIteration": true
|
||||
"downlevelIteration": true,
|
||||
"strict": true
|
||||
},
|
||||
"include": ["src/lib"],
|
||||
"exclude": ["src/lib/**/tests"],
|
||||
|
||||
@@ -27,6 +27,7 @@ const banner = `
|
||||
module.exports = {
|
||||
mode: "production",
|
||||
entry: './src/lib/index.ts',
|
||||
target: 'es5',
|
||||
output: {
|
||||
filename: 'index.js',
|
||||
path: path.resolve(__dirname, 'build'),
|
||||
|
||||
Reference in New Issue
Block a user