mirror of
https://github.com/hodgef/simple-keyboard.git
synced 2026-02-03 00:06:50 +08:00
Compare commits
200 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d186fb1e09 | ||
|
|
29677485a9 | ||
|
|
37ade80dd6 | ||
|
|
eab88f7de3 | ||
|
|
26e6747b0f | ||
|
|
88c462ddf3 | ||
|
|
5452367733 | ||
|
|
bdbf82828d | ||
|
|
e4b3622291 | ||
|
|
181dd240a0 | ||
|
|
628d27cf18 | ||
|
|
de8dd3f204 | ||
|
|
c696606b11 | ||
|
|
9e17a1e0df | ||
|
|
d1c9a0ead7 | ||
|
|
b054ee45a3 | ||
|
|
c459ab60c1 | ||
|
|
f2c690e010 | ||
|
|
e567c3f19b | ||
|
|
e6f8f47975 | ||
|
|
0541693901 | ||
|
|
cb598712c6 | ||
|
|
020aa04894 | ||
|
|
618800f455 | ||
|
|
74bb07d46a | ||
|
|
f158a603c9 | ||
|
|
23da05ee60 | ||
|
|
331687de5b | ||
|
|
1ac8bf00ef | ||
|
|
eb68681f2a | ||
|
|
312c9db7f7 | ||
|
|
7317ae147e | ||
|
|
a4c0b824ea | ||
|
|
a23b1ab6cd | ||
|
|
485ae27dad | ||
|
|
ed9c0744bd | ||
|
|
bc24494ec1 | ||
|
|
9952ecd8be | ||
|
|
419ca818a1 | ||
|
|
cab2599dd1 | ||
|
|
c59251694d | ||
|
|
e333ebacde | ||
|
|
71b5af63b8 | ||
|
|
a24bcc4775 | ||
|
|
fa984e93e8 | ||
|
|
42be0e3f86 | ||
|
|
e59afc0b0c | ||
|
|
96eaa2c1d3 | ||
|
|
983d27c11c | ||
|
|
cb67a69f9d | ||
|
|
abc03918be | ||
|
|
8029e1fce9 | ||
|
|
946fdbb417 | ||
|
|
f5ceef195e | ||
|
|
82ad303159 | ||
|
|
75d8167fad | ||
|
|
32af9739be | ||
|
|
89c8e39d0c | ||
|
|
434215cdef | ||
|
|
e513c8b49a | ||
|
|
6fa48db14e | ||
|
|
d29f82e1ed | ||
|
|
4b77a07565 | ||
|
|
4af477f749 | ||
|
|
f9439493d2 | ||
|
|
c23ff225c0 | ||
|
|
cda4e404f5 | ||
|
|
f2c4d2d3df | ||
|
|
dd73ebaaa9 | ||
|
|
7e2f5ac61d | ||
|
|
988871a5f8 | ||
|
|
d79cf5a591 | ||
|
|
d8745a31c7 | ||
|
|
2888562b65 | ||
|
|
83367c56cd | ||
|
|
258d410dee | ||
|
|
2b06589259 | ||
|
|
f83b592a61 | ||
|
|
7a2fc73ac3 | ||
|
|
ae70a518e5 | ||
|
|
5f5ba69277 | ||
|
|
f08e9d1031 | ||
|
|
dbd4d3d7d2 | ||
|
|
d8a350ae01 | ||
|
|
ce50ab2c4e | ||
|
|
f16a3c7eff | ||
|
|
35cede2de1 | ||
|
|
d05bdd600b | ||
|
|
c4ebf1ada0 | ||
|
|
fa7b7c0f59 | ||
|
|
3188c5859d | ||
|
|
c9cb6cfbb3 | ||
|
|
fdee26e841 | ||
|
|
1384b6e0ff | ||
|
|
36163aceec | ||
|
|
80919dd755 | ||
|
|
2197bb3abc | ||
|
|
3a7088ec20 | ||
|
|
cba9754672 | ||
|
|
f251e6ee35 | ||
|
|
2113229d61 | ||
|
|
460633a00d | ||
|
|
d4e9f51182 | ||
|
|
8ba6f879d0 | ||
|
|
16f26bd6d9 | ||
|
|
5239521964 | ||
|
|
b0a7748351 | ||
|
|
9f42786deb | ||
|
|
910a9f5292 | ||
|
|
3ad37394f2 | ||
|
|
2bc496e370 | ||
|
|
f6104e214f | ||
|
|
26ce879b3a | ||
|
|
4b68c236e2 | ||
|
|
04705619a4 | ||
|
|
ab1facf287 | ||
|
|
30c95a8264 | ||
|
|
07ac2fef5a | ||
|
|
665a120a0b | ||
|
|
be69873219 | ||
|
|
a5281dba1c | ||
|
|
72733e57be | ||
|
|
ea84c35e32 | ||
|
|
bb36b32e5b | ||
|
|
a44cca5ee9 | ||
|
|
c9ec9716b7 | ||
|
|
c49d6f858b | ||
|
|
d4e54508f5 | ||
|
|
b64a0383dc | ||
|
|
d5adc5025c | ||
|
|
d39a2804cb | ||
|
|
09c1f8b56a | ||
|
|
dc01ad5e3a | ||
|
|
c3a565fd20 | ||
|
|
3cf2764001 | ||
|
|
fe3a012f7f | ||
|
|
70cb19f1ef | ||
|
|
316647bdac | ||
|
|
8e07afda65 | ||
|
|
25f81ab1c7 | ||
|
|
78f578f787 | ||
|
|
692f773c31 | ||
|
|
b95670e8c4 | ||
|
|
c4c1e8b181 | ||
|
|
9e8b28b124 | ||
|
|
665262bf93 | ||
|
|
5830c34263 | ||
|
|
458e7be599 | ||
|
|
9e96f186c8 | ||
|
|
f2169a4cd0 | ||
|
|
003d44325c | ||
|
|
e193745029 | ||
|
|
dfa4686be6 | ||
|
|
9618f96c2b | ||
|
|
7f0cf50d79 | ||
|
|
7d501807ff | ||
|
|
891d3a4daf | ||
|
|
a5451f73fa | ||
|
|
9d94f0a802 | ||
|
|
d5bdb3e917 | ||
|
|
b0722f6871 | ||
|
|
2865389267 | ||
|
|
a3cfb8e104 | ||
|
|
f7cc244eb6 | ||
|
|
e11c8c4aa0 | ||
|
|
71b3a70b33 | ||
|
|
64a63db2f3 | ||
|
|
0f6da50b34 | ||
|
|
990efcda62 | ||
|
|
5954dec8a3 | ||
|
|
5566c09cba | ||
|
|
b9f8e95a15 | ||
|
|
205004f4b3 | ||
|
|
7adfe713a3 | ||
|
|
c715499007 | ||
|
|
da36eddc43 | ||
|
|
d73ecb2e59 | ||
|
|
4b93c7def2 | ||
|
|
9f8eaf5fb8 | ||
|
|
5002fb8ae5 | ||
|
|
bfd6774fdc | ||
|
|
8f334ea429 | ||
|
|
780a097ac8 | ||
|
|
90efb5bb08 | ||
|
|
5b20248ac5 | ||
|
|
fc137e4a31 | ||
|
|
356a71c006 | ||
|
|
e0093fc64a | ||
|
|
a2fb28a88b | ||
|
|
539fa0fffd | ||
|
|
fa40e7a5cf | ||
|
|
d61813102e | ||
|
|
1d543bb8ea | ||
|
|
8b95cb0c93 | ||
|
|
b82136f248 | ||
|
|
ccb952f6c5 | ||
|
|
d387d7a37c | ||
|
|
f3665eeea3 | ||
|
|
1facc21b0a | ||
|
|
1d9be6dae5 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -17,6 +17,7 @@
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
.vscode
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
|
||||
46
README.md
46
README.md
@@ -1,14 +1,34 @@
|
||||
<div align="center">
|
||||
<a href="https://simple-keyboard.com/demo" title="View Demo" target="_blank"><img src="https://franciscohodge.com/project-pages/simple-keyboard/images/simplekeyboard-banner_B.png" align="center" width="100%"></a>
|
||||
<a href="https://simple-keyboard.com/demo" title="View Demo" target="_blank"><img src="https://franciscohodge.com/project-pages/simple-keyboard/images/skdemo-4x2.gif" align="center" width="100%"></a>
|
||||
<blockquote>The easily customisable and responsive on-screen virtual keyboard for Javascript projects.</blockquote>
|
||||
<a href="https://www.npmjs.com/package/simple-keyboard" target="_blank"><img src="https://img.shields.io/npm/v/simple-keyboard.svg?style=flat" alt="npm version"></a>
|
||||
<a href="https://travis-ci.org/hodgef/simple-keyboard" target="_blank"><img src="https://travis-ci.org/hodgef/simple-keyboard.svg?branch=master" alt="Build Status"></a>
|
||||
<img src="https://img.shields.io/david/hodgef/js-library-boilerplate.svg" />
|
||||
<a href="https://codecov.io/gh/hodgef/simple-keyboard" target="_blank"><img src="https://img.shields.io/codecov/c/github/hodgef/simple-keyboard/master.svg?style=flat" alt="Coverage Status"></a>
|
||||
<a href="https://www.codacy.com/app/hodgef/simple-keyboard?utm_source=github.com&utm_medium=referral&utm_content=hodgef/simple-keyboard&utm_campaign=Badge_Grade" target="_blank"><img src="https://api.codacy.com/project/badge/Grade/5778fccc6a894701853d9a1f2fb44a76" alt="Codacy Badge"></a>
|
||||
</p>
|
||||
</div>
|
||||
<p>
|
||||
<a href="https://simple-keyboard.com/demo">
|
||||
<img alt="simple-keyboard: Javscript Virtual Keyboard" src="https://i.imgur.com/Po6659n.gif">
|
||||
</a>
|
||||
|
||||
<a href="https://www.npmjs.com/package/simple-keyboard">
|
||||
<img src="https://badgen.net/npm/v/simple-keyboard?color=blue" alt="npm version">
|
||||
</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://travis-ci.org/hodgef/simple-keyboard">
|
||||
<img src="https://api.travis-ci.org/hodgef/simple-keyboard.svg?branch=master"
|
||||
alt="build status">
|
||||
</a>
|
||||
|
||||
<a href="https://david-dm.org/hodgef/simple-keyboard">
|
||||
<img src="https://badgen.net/david/dep/hodgef/simple-keyboard" alt="coverage">
|
||||
</a>
|
||||
|
||||
<a href="https://codecov.io/gh/hodgef/simple-keyboard">
|
||||
<img src="https://badgen.net/codecov/c/github/hodgef/simple-keyboard" alt="coverage">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
> The slick virtual keyboard for Javascript. Compatible with your JS, React, Angular or Vue projects.
|
||||
|
||||
## 🚀 Demo
|
||||
[Demo Showcase (Vanilla, Angular, React, Vue)](https://simple-keyboard.com/demo)
|
||||
|
||||
## 📦 Installation & Usage
|
||||
You can use simple-keyboard as a `<script>` tag from a CDN, or install it from npm.
|
||||
@@ -20,10 +40,6 @@ Check out the [simple-keyboard documentation](https://simple-keyboard.com/docume
|
||||
|
||||
Feel free to browse the [Q&A / Use-cases](https://simple-keyboard.com/qa-use-cases/) page for advanced use-cases.
|
||||
|
||||
## 🚀 Demo
|
||||
|
||||
[https://simple-keyboard.com/demo](https://simple-keyboard.com/demo)
|
||||
|
||||
|
||||
### To run demo on your own computer
|
||||
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
console.log('\x1b[36m%s\x1b[0m', `
|
||||
______________________________________\n`);
|
||||
|
||||
console.log('\x1b[33m%s\x1b[0m', `Thank you for installing simple-keyboard !`);
|
||||
|
||||
console.log('\x1b[36m%s\x1b[0m', `
|
||||
Please consider starring the repository so others can also find it.
|
||||
https://github.com/hodgef/simple-keyboard/stargazers
|
||||
______________________________________
|
||||
`);
|
||||
@@ -1,6 +1,6 @@
|
||||
/*!
|
||||
*
|
||||
* simple-keyboard v2.23.0
|
||||
* simple-keyboard v2.26.0
|
||||
* https://github.com/hodgef/simple-keyboard
|
||||
*
|
||||
* Copyright (c) Francisco Hodge (https://github.com/hodgef)
|
||||
@@ -8,5 +8,5 @@
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
*/body,html{margin:0;padding:0}.simple-keyboard{font-family:HelveticaNeue-Light,Helvetica Neue Light,Helvetica Neue,Helvetica,Arial,Lucida Grande,sans-serif;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}.simple-keyboard .hg-row{display:flex}.simple-keyboard .hg-row:not(:last-child){margin-bottom:5px}.simple-keyboard .hg-row .hg-button-container,.simple-keyboard .hg-row .hg-button:not(:last-child){margin-right:5px}.simple-keyboard .hg-row>div:last-child{margin-right:0}.simple-keyboard .hg-row .hg-button-container{display:flex}.simple-keyboard .hg-button{display:inline-block;flex-grow:1;cursor:pointer}.simple-keyboard .hg-button span{pointer-events:none}.simple-keyboard.hg-theme-default{background-color:rgba(0,0,0,.1);padding:5px;border-radius:5px}.simple-keyboard.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}.simple-keyboard button.hg-button{border-width:0;outline:0;font-size:inherit}.simple-keyboard.hg-theme-default:not(.hg-touch-events) .hg-button:active{background:#e4e4e4}.simple-keyboard.hg-theme-default.hg-layout-numeric .hg-button{width:33.3%;height:60px;align-items:center;display:flex;justify-content:center}.simple-keyboard.hg-theme-default .hg-button.hg-button-numpadadd,.simple-keyboard.hg-theme-default .hg-button.hg-button-numpadenter{height:85px}.simple-keyboard.hg-theme-default .hg-button.hg-button-numpad0{width:105px}.simple-keyboard.hg-theme-default .hg-button.hg-button-com{max-width:85px}.simple-keyboard.hg-theme-default .hg-button.hg-standardBtn.hg-button-at{max-width:45px}.simple-keyboard.hg-theme-default .hg-button.hg-selectedButton{background:rgba(5,25,70,.53);color:#fff}.simple-keyboard.hg-theme-default .hg-button.hg-standardBtn[data-skbtn=".com"]{max-width:82px}.simple-keyboard.hg-theme-default .hg-button.hg-standardBtn[data-skbtn="@"]{max-width:60px}
|
||||
*/body,html{margin:0;padding:0}.simple-keyboard{font-family:HelveticaNeue-Light,Helvetica Neue Light,Helvetica Neue,Helvetica,Arial,Lucida Grande,sans-serif;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}.simple-keyboard .hg-row{display:flex}.simple-keyboard .hg-row:not(:last-child){margin-bottom:5px}.simple-keyboard .hg-row .hg-button-container,.simple-keyboard .hg-row .hg-button:not(:last-child){margin-right:5px}.simple-keyboard .hg-row>div:last-child{margin-right:0}.simple-keyboard .hg-row .hg-button-container{display:flex}.simple-keyboard .hg-button{display:inline-block;flex-grow:1;cursor:pointer}.simple-keyboard .hg-button span{pointer-events:none}.simple-keyboard.hg-theme-default{background-color:rgba(0,0,0,.1);padding:5px;border-radius:5px}.simple-keyboard.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}.simple-keyboard button.hg-button{border-width:0;outline:0;font-size:inherit}.simple-keyboard.hg-theme-default.hg-layout-numeric .hg-button{width:33.3%;height:60px;align-items:center;display:flex;justify-content:center}.simple-keyboard.hg-theme-default .hg-button.hg-button-numpadadd,.simple-keyboard.hg-theme-default .hg-button.hg-button-numpadenter{height:85px}.simple-keyboard.hg-theme-default .hg-button.hg-button-numpad0{width:105px}.simple-keyboard.hg-theme-default .hg-button.hg-button-com{max-width:85px}.simple-keyboard.hg-theme-default .hg-button.hg-standardBtn.hg-button-at{max-width:45px}.simple-keyboard.hg-theme-default .hg-button.hg-selectedButton{background:rgba(5,25,70,.53);color:#fff}.simple-keyboard.hg-theme-default .hg-button.hg-standardBtn[data-skbtn=".com"]{max-width:82px}.simple-keyboard.hg-theme-default .hg-button.hg-standardBtn[data-skbtn="@"]{max-width:60px}
|
||||
/*# sourceMappingURL=index.css.map */
|
||||
File diff suppressed because one or more lines are too long
@@ -1,11 +0,0 @@
|
||||
/*!
|
||||
*
|
||||
* simple-keyboard v2.23.0 (Non-minified build)
|
||||
* https://github.com/hodgef/simple-keyboard
|
||||
*
|
||||
* Copyright (c) Francisco Hodge (https://github.com/hodgef)
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
*/body,html{margin:0;padding:0}.simple-keyboard{font-family:HelveticaNeue-Light,Helvetica Neue Light,Helvetica Neue,Helvetica,Arial,Lucida Grande,sans-serif;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}.simple-keyboard .hg-row{display:flex}.simple-keyboard .hg-row:not(:last-child){margin-bottom:5px}.simple-keyboard .hg-row .hg-button-container,.simple-keyboard .hg-row .hg-button:not(:last-child){margin-right:5px}.simple-keyboard .hg-row>div:last-child{margin-right:0}.simple-keyboard .hg-row .hg-button-container{display:flex}.simple-keyboard .hg-button{display:inline-block;flex-grow:1;cursor:pointer}.simple-keyboard .hg-button span{pointer-events:none}.simple-keyboard.hg-theme-default{background-color:rgba(0,0,0,.1);padding:5px;border-radius:5px}.simple-keyboard.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}.simple-keyboard button.hg-button{border-width:0;outline:0;font-size:inherit}.simple-keyboard.hg-theme-default:not(.hg-touch-events) .hg-button:active{background:#e4e4e4}.simple-keyboard.hg-theme-default.hg-layout-numeric .hg-button{width:33.3%;height:60px;align-items:center;display:flex;justify-content:center}.simple-keyboard.hg-theme-default .hg-button.hg-button-numpadadd,.simple-keyboard.hg-theme-default .hg-button.hg-button-numpadenter{height:85px}.simple-keyboard.hg-theme-default .hg-button.hg-button-numpad0{width:105px}.simple-keyboard.hg-theme-default .hg-button.hg-button-com{max-width:85px}.simple-keyboard.hg-theme-default .hg-button.hg-standardBtn.hg-button-at{max-width:45px}.simple-keyboard.hg-theme-default .hg-button.hg-selectedButton{background:rgba(5,25,70,.53);color:#fff}.simple-keyboard.hg-theme-default .hg-button.hg-standardBtn[data-skbtn=".com"]{max-width:82px}.simple-keyboard.hg-theme-default .hg-button.hg-standardBtn[data-skbtn="@"]{max-width:60px}
|
||||
30
build/index.d.ts
vendored
30
build/index.d.ts
vendored
@@ -10,6 +10,12 @@ declare module 'simple-keyboard' {
|
||||
buttons: string;
|
||||
}
|
||||
|
||||
interface KeyboardButtonAttributes {
|
||||
attribute: string;
|
||||
value: string;
|
||||
buttons: string;
|
||||
}
|
||||
|
||||
interface KeyboardOptions {
|
||||
/**
|
||||
* Modify the keyboard layout.
|
||||
@@ -41,6 +47,11 @@ declare module 'simple-keyboard' {
|
||||
*/
|
||||
buttonTheme?: KeyboardButtonTheme[];
|
||||
|
||||
/**
|
||||
* A prop to add your own attributes to one or several buttons.
|
||||
*/
|
||||
buttonAttributes?: KeyboardButtonAttributes[];
|
||||
|
||||
/**
|
||||
* Runs a `console.log` every time a key is pressed. Displays the buttons pressed and the current input.
|
||||
*/
|
||||
@@ -133,6 +144,11 @@ declare module 'simple-keyboard' {
|
||||
*/
|
||||
onKeyPress?: (button: string) => any;
|
||||
|
||||
/**
|
||||
* Executes the callback function on key release.
|
||||
*/
|
||||
onKeyReleased?: (button: string) => any;
|
||||
|
||||
/**
|
||||
* Executes the callback function on input change. Returns the current input's string.
|
||||
*/
|
||||
@@ -162,11 +178,20 @@ declare module 'simple-keyboard' {
|
||||
* Executes the callback function on input change. Returns the input object with all defined inputs.
|
||||
*/
|
||||
onChangeAll?: (inputs: any) => any;
|
||||
|
||||
/**
|
||||
* Executes the callback function on key release.
|
||||
*/
|
||||
onKeyReleased?: () => void;
|
||||
}
|
||||
|
||||
class Keyboard {
|
||||
constructor(selector: string, options: KeyboardOptions);
|
||||
constructor(options: KeyboardOptions);
|
||||
|
||||
/**
|
||||
* Options
|
||||
*/
|
||||
options: KeyboardOptions;
|
||||
|
||||
/**
|
||||
@@ -174,6 +199,11 @@ declare module 'simple-keyboard' {
|
||||
*/
|
||||
utilities?: any;
|
||||
|
||||
/**
|
||||
* caretPosition
|
||||
*/
|
||||
caretPosition?: number;
|
||||
|
||||
/**
|
||||
* Adds/Modifies an entry to the `buttonTheme`. Basically a way to add a class to a button.
|
||||
* @param {string} buttons List of buttons to select (separated by a space).
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
const path = require('path');
|
||||
const camelcase = require('camelcase');
|
||||
|
||||
// This is a custom Jest transformer turning file imports into filenames.
|
||||
// http://facebook.github.io/jest/docs/en/webpack.html
|
||||
@@ -10,19 +11,27 @@ module.exports = {
|
||||
const assetFilename = JSON.stringify(path.basename(filename));
|
||||
|
||||
if (filename.match(/\.svg$/)) {
|
||||
// Based on how SVGR generates a component name:
|
||||
// https://github.com/smooth-code/svgr/blob/01b194cf967347d43d4cbe6b434404731b87cf27/packages/core/src/state.js#L6
|
||||
const pascalCaseFileName = camelcase(path.parse(filename).name, {
|
||||
pascalCase: true,
|
||||
});
|
||||
const componentName = `Svg${pascalCaseFileName}`;
|
||||
return `const React = require('react');
|
||||
module.exports = {
|
||||
__esModule: true,
|
||||
default: ${assetFilename},
|
||||
ReactComponent: React.forwardRef((props, ref) => ({
|
||||
$$typeof: Symbol.for('react.element'),
|
||||
type: 'svg',
|
||||
ref: ref,
|
||||
key: null,
|
||||
props: Object.assign({}, props, {
|
||||
children: ${assetFilename}
|
||||
})
|
||||
})),
|
||||
ReactComponent: React.forwardRef(function ${componentName}(props, ref) {
|
||||
return {
|
||||
$$typeof: Symbol.for('react.element'),
|
||||
type: 'svg',
|
||||
ref: ref,
|
||||
key: null,
|
||||
props: Object.assign({}, props, {
|
||||
children: ${assetFilename}
|
||||
})
|
||||
};
|
||||
}),
|
||||
};`;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ const fs = require('fs');
|
||||
const path = require('path');
|
||||
const paths = require('./paths');
|
||||
const chalk = require('react-dev-utils/chalk');
|
||||
const resolve = require('resolve');
|
||||
|
||||
/**
|
||||
* Get the baseUrl of a compilerOptions object.
|
||||
@@ -63,7 +64,10 @@ function getModules() {
|
||||
// TypeScript project and set up the config
|
||||
// based on tsconfig.json
|
||||
if (hasTsConfig) {
|
||||
config = require(paths.appTsConfig);
|
||||
const ts = require(resolve.sync('typescript', {
|
||||
basedir: paths.appNodeModules,
|
||||
}));
|
||||
config = ts.readConfigFile(paths.appTsConfig, ts.sys.readFile).config;
|
||||
// Otherwise we'll check if there is jsconfig.json
|
||||
// for non TS projects.
|
||||
} else if (hasJsConfig) {
|
||||
|
||||
35
config/pnpTs.js
Normal file
35
config/pnpTs.js
Normal file
@@ -0,0 +1,35 @@
|
||||
'use strict';
|
||||
|
||||
const { resolveModuleName } = require('ts-pnp');
|
||||
|
||||
exports.resolveModuleName = (
|
||||
typescript,
|
||||
moduleName,
|
||||
containingFile,
|
||||
compilerOptions,
|
||||
resolutionHost
|
||||
) => {
|
||||
return resolveModuleName(
|
||||
moduleName,
|
||||
containingFile,
|
||||
compilerOptions,
|
||||
resolutionHost,
|
||||
typescript.resolveModuleName
|
||||
);
|
||||
};
|
||||
|
||||
exports.resolveTypeReferenceDirective = (
|
||||
typescript,
|
||||
moduleName,
|
||||
containingFile,
|
||||
compilerOptions,
|
||||
resolutionHost
|
||||
) => {
|
||||
return resolveModuleName(
|
||||
moduleName,
|
||||
containingFile,
|
||||
compilerOptions,
|
||||
resolutionHost,
|
||||
typescript.resolveTypeReferenceDirective
|
||||
);
|
||||
};
|
||||
@@ -25,15 +25,23 @@ const getClientEnvironment = require('./env');
|
||||
const ModuleNotFoundPlugin = require('react-dev-utils/ModuleNotFoundPlugin');
|
||||
const ForkTsCheckerWebpackPlugin = require('react-dev-utils/ForkTsCheckerWebpackPlugin');
|
||||
const typescriptFormatter = require('react-dev-utils/typescriptFormatter');
|
||||
const getPackageJson = require('./getPackageJson');
|
||||
const eslint = require('eslint');
|
||||
|
||||
const postcssNormalize = require('postcss-normalize');
|
||||
|
||||
const appPackageJson = require(paths.appPackageJson);
|
||||
|
||||
// Source maps are resource heavy and can cause out of memory issue for large source files.
|
||||
const shouldUseSourceMap = process.env.GENERATE_SOURCEMAP !== 'false';
|
||||
// Some apps do not need the benefits of saving a web request, so not inlining the chunk
|
||||
// makes for a smoother build process.
|
||||
const shouldInlineRuntimeChunk = process.env.INLINE_RUNTIME_CHUNK !== 'false';
|
||||
|
||||
const imageInlineSizeLimit = parseInt(
|
||||
process.env.IMAGE_INLINE_SIZE_LIMIT || '10000'
|
||||
);
|
||||
|
||||
// Check if TypeScript is setup
|
||||
const useTypeScript = fs.existsSync(paths.appTsConfig);
|
||||
|
||||
@@ -46,6 +54,7 @@ const sassModuleRegex = /\.module\.(scss|sass)$/;
|
||||
// This is the production and development configuration.
|
||||
// It is focused on developer experience, fast rebuilds, and a minimal bundle.
|
||||
module.exports = function(webpackEnv) {
|
||||
const isEnvDevelopment = webpackEnv === 'development';
|
||||
const isEnvProduction = webpackEnv === 'production';
|
||||
|
||||
// Webpack uses `publicPath` to determine where the app is being served from.
|
||||
@@ -66,12 +75,10 @@ module.exports = function(webpackEnv) {
|
||||
// common function to get style loaders
|
||||
const getStyleLoaders = (cssOptions, preProcessor) => {
|
||||
const loaders = [
|
||||
isEnvDevelopment && require.resolve('style-loader'),
|
||||
isEnvProduction && {
|
||||
loader: MiniCssExtractPlugin.loader,
|
||||
options: Object.assign(
|
||||
{},
|
||||
shouldUseRelativeAssetPaths ? { publicPath: '../../' } : undefined
|
||||
),
|
||||
options: shouldUseRelativeAssetPaths ? { publicPath: '../../' } : {},
|
||||
},
|
||||
{
|
||||
loader: require.resolve('css-loader'),
|
||||
@@ -104,12 +111,20 @@ module.exports = function(webpackEnv) {
|
||||
},
|
||||
].filter(Boolean);
|
||||
if (preProcessor) {
|
||||
loaders.push({
|
||||
loader: require.resolve(preProcessor),
|
||||
loaders.push(
|
||||
{
|
||||
loader: require.resolve('resolve-url-loader'),
|
||||
options: {
|
||||
sourceMap: isEnvProduction && shouldUseSourceMap,
|
||||
},
|
||||
});
|
||||
},
|
||||
{
|
||||
loader: require.resolve(preProcessor),
|
||||
options: {
|
||||
sourceMap: true,
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
return loaders;
|
||||
};
|
||||
@@ -118,10 +133,26 @@ module.exports = function(webpackEnv) {
|
||||
mode: 'production',
|
||||
// Stop compilation early in production
|
||||
bail: isEnvProduction,
|
||||
devtool: shouldUseSourceMap ? 'source-map' : false,
|
||||
devtool: isEnvProduction
|
||||
? shouldUseSourceMap
|
||||
? 'source-map'
|
||||
: false
|
||||
: isEnvDevelopment && 'cheap-module-source-map',
|
||||
// These are the "entry points" to our application.
|
||||
// This means they will be the "root" imports that are included in JS bundle.
|
||||
entry: isEnvProduction ? [paths.appDemoIndexJs] : [
|
||||
// Include an alternative client for WebpackDevServer. A client's job is to
|
||||
// connect to WebpackDevServer by a socket and get notified about changes.
|
||||
// When you save a file, the client will either apply hot updates (in case
|
||||
// of CSS changes), or refresh the page (in case of JS changes). When you
|
||||
// make a syntax error, this client will display a syntax error overlay.
|
||||
// Note: instead of the default WebpackDevServer client, we use a custom one
|
||||
// to bring better experience for Create React App users. You can replace
|
||||
// the line below with these two lines if you prefer the stock client:
|
||||
// require.resolve('webpack-dev-server/client') + '?/',
|
||||
// require.resolve('webpack/hot/dev-server'),
|
||||
isEnvDevelopment &&
|
||||
require.resolve('react-dev-utils/webpackHotDevClient'),
|
||||
// Finally, this is your app's code:
|
||||
paths.appDemoIndexJs,
|
||||
// We include the app code last so that if there is a runtime error during
|
||||
@@ -132,6 +163,7 @@ module.exports = function(webpackEnv) {
|
||||
// The build folder.
|
||||
path: isEnvProduction ? paths.appDemo : undefined,
|
||||
// Add /* filename */ comments to generated require()s in the output.
|
||||
pathinfo: isEnvDevelopment,
|
||||
// There will be one main bundle, and one file per asynchronous chunk.
|
||||
// In development, it does not produce real files.
|
||||
filename: 'index.js',
|
||||
@@ -148,7 +180,11 @@ module.exports = function(webpackEnv) {
|
||||
path
|
||||
.relative(paths.appSrcDemo, info.absoluteResourcePath)
|
||||
.replace(/\\/g, '/')
|
||||
: null,
|
||||
: isEnvDevelopment &&
|
||||
(info => path.resolve(info.absoluteResourcePath).replace(/\\/g, '/')),
|
||||
// Prevents conflicts when multiple Webpack runtimes (from different apps)
|
||||
// are used on the same page.
|
||||
jsonpFunction: `webpackJsonp${appPackageJson.name}`,
|
||||
},
|
||||
optimization: {
|
||||
minimize: isEnvProduction,
|
||||
@@ -157,8 +193,8 @@ module.exports = function(webpackEnv) {
|
||||
new TerserPlugin({
|
||||
terserOptions: {
|
||||
parse: {
|
||||
// we want terser to parse ecma 8 code. However, we don't want it
|
||||
// to apply any minfication steps that turns valid ecma 5 code
|
||||
// We want terser to parse ecma 8 code. However, we don't want it
|
||||
// to apply any minification steps that turns valid ecma 5 code
|
||||
// into invalid ecma 5 code. This is why the 'compress' and 'output'
|
||||
// sections only apply transformations that are ecma 5 safe
|
||||
// https://github.com/facebook/create-react-app/pull/4234
|
||||
@@ -174,7 +210,7 @@ module.exports = function(webpackEnv) {
|
||||
comparisons: false,
|
||||
// Disabled because of an issue with Terser breaking valid code:
|
||||
// https://github.com/facebook/create-react-app/issues/5250
|
||||
// Pending futher investigation:
|
||||
// Pending further investigation:
|
||||
// https://github.com/terser-js/terser/issues/120
|
||||
inline: 2,
|
||||
},
|
||||
@@ -236,7 +272,9 @@ module.exports = function(webpackEnv) {
|
||||
// We placed these paths second because we want `node_modules` to "win"
|
||||
// if there are any conflicts. This matches Node resolution mechanism.
|
||||
// https://github.com/facebook/create-react-app/issues/253
|
||||
modules: ['node_modules', paths.appNodeModules].concat(modules.additionalModulePaths || []),
|
||||
modules: ['node_modules', paths.appNodeModules].concat(
|
||||
modules.additionalModulePaths || []
|
||||
),
|
||||
// These are the reasonable defaults supported by the Node ecosystem.
|
||||
// We also include JSX as a common component filename extension to support
|
||||
// some tools, although we do not recommend using it, see:
|
||||
@@ -286,6 +324,7 @@ module.exports = function(webpackEnv) {
|
||||
options: {
|
||||
formatter: require.resolve('react-dev-utils/eslintFormatter'),
|
||||
eslintPath: require.resolve('eslint'),
|
||||
resolvePluginsRelativeTo: __dirname,
|
||||
|
||||
},
|
||||
loader: require.resolve('eslint-loader'),
|
||||
@@ -305,7 +344,7 @@ module.exports = function(webpackEnv) {
|
||||
test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/],
|
||||
loader: require.resolve('url-loader'),
|
||||
options: {
|
||||
limit: 10000,
|
||||
limit: imageInlineSizeLimit,
|
||||
name: 'static/media/[name].[hash:8].[ext]',
|
||||
},
|
||||
},
|
||||
@@ -355,8 +394,6 @@ module.exports = function(webpackEnv) {
|
||||
require.resolve('babel-preset-react-app/dependencies'),
|
||||
{ helpers: true },
|
||||
],
|
||||
"@babel/preset-env",
|
||||
"@babel/preset-react"
|
||||
],
|
||||
plugins: [
|
||||
[
|
||||
@@ -461,24 +498,34 @@ module.exports = function(webpackEnv) {
|
||||
},
|
||||
plugins: [
|
||||
// Generates an `index.html` file with the <script> injected.
|
||||
new HtmlWebpackPlugin({
|
||||
inject: true,
|
||||
template: paths.appHtml,
|
||||
minify: {
|
||||
removeComments: true,
|
||||
collapseWhitespace: true,
|
||||
removeRedundantAttributes: true,
|
||||
useShortDoctype: true,
|
||||
removeEmptyAttributes: true,
|
||||
removeStyleLinkTypeAttributes: true,
|
||||
keepClosingSlash: true,
|
||||
minifyJS: true,
|
||||
minifyCSS: true,
|
||||
minifyURLs: true,
|
||||
},
|
||||
}),
|
||||
new HtmlWebpackPlugin(
|
||||
Object.assign(
|
||||
{},
|
||||
{
|
||||
inject: true,
|
||||
template: paths.appHtml
|
||||
},
|
||||
isEnvProduction
|
||||
? {
|
||||
minify: {
|
||||
removeComments: true,
|
||||
collapseWhitespace: true,
|
||||
removeRedundantAttributes: true,
|
||||
useShortDoctype: true,
|
||||
removeEmptyAttributes: true,
|
||||
removeStyleLinkTypeAttributes: true,
|
||||
keepClosingSlash: true,
|
||||
minifyJS: true,
|
||||
minifyCSS: true,
|
||||
minifyURLs: true,
|
||||
},
|
||||
}
|
||||
: undefined
|
||||
)
|
||||
),
|
||||
// Inlines the webpack runtime script. This script is too small to warrant
|
||||
// a network request.
|
||||
isEnvProduction &&
|
||||
shouldInlineRuntimeChunk &&
|
||||
new InlineChunkHtmlPlugin(HtmlWebpackPlugin, [/runtime~.+[.]js/]),
|
||||
// Makes some environment variables available in index.html.
|
||||
@@ -497,6 +544,18 @@ module.exports = function(webpackEnv) {
|
||||
// during a production build.
|
||||
// Otherwise React will be compiled in the very slow development mode.
|
||||
new webpack.DefinePlugin(env.stringified),
|
||||
// This is necessary to emit hot updates (currently CSS only):
|
||||
isEnvDevelopment && new webpack.HotModuleReplacementPlugin(),
|
||||
// Watcher doesn't work well if you mistype casing in a path so we use
|
||||
// a plugin that prints an error when you attempt to do this.
|
||||
// See https://github.com/facebook/create-react-app/issues/240
|
||||
isEnvDevelopment && new CaseSensitivePathsPlugin(),
|
||||
// If you require a missing module and then `npm install` it, you still have
|
||||
// to restart the development server for Webpack to discover it. This plugin
|
||||
// makes the discovery automatic so you don't have to restart.
|
||||
// See https://github.com/facebook/create-react-app/issues/186
|
||||
isEnvDevelopment &&
|
||||
new WatchMissingNodeModulesPlugin(paths.appNodeModules),
|
||||
isEnvProduction &&
|
||||
new MiniCssExtractPlugin({
|
||||
// Options similar to the same options in webpackOptions.output
|
||||
@@ -551,6 +610,12 @@ module.exports = function(webpackEnv) {
|
||||
}),
|
||||
useTypescriptIncrementalApi: true,
|
||||
checkSyntacticErrors: true,
|
||||
resolveModuleNameModule: process.versions.pnp
|
||||
? `${__dirname}/pnpTs.js`
|
||||
: undefined,
|
||||
resolveTypeReferenceDirectiveModule: process.versions.pnp
|
||||
? `${__dirname}/pnpTs.js`
|
||||
: undefined,
|
||||
tsconfig: paths.appTsConfig,
|
||||
reportFiles: [
|
||||
'**',
|
||||
|
||||
@@ -13,7 +13,9 @@ const TerserPlugin = require('terser-webpack-plugin');
|
||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
|
||||
const safePostCssParser = require('postcss-safe-parser');
|
||||
const ManifestPlugin = require('webpack-manifest-plugin');
|
||||
const InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin');
|
||||
const WorkboxWebpackPlugin = require('workbox-webpack-plugin');
|
||||
const WatchMissingNodeModulesPlugin = require('react-dev-utils/WatchMissingNodeModulesPlugin');
|
||||
const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin');
|
||||
const getCSSModuleLocalIdent = require('react-dev-utils/getCSSModuleLocalIdent');
|
||||
@@ -26,15 +28,22 @@ const typescriptFormatter = require('react-dev-utils/typescriptFormatter');
|
||||
const CopyWebpackPlugin = require('copy-webpack-plugin');
|
||||
const getPackageJson = require('./getPackageJson');
|
||||
const PrettierPlugin = require("prettier-webpack-plugin");
|
||||
const eslint = require('eslint');
|
||||
|
||||
const postcssNormalize = require('postcss-normalize');
|
||||
|
||||
const appPackageJson = require(paths.appPackageJson);
|
||||
|
||||
// Source maps are resource heavy and can cause out of memory issue for large source files.
|
||||
const shouldUseSourceMap = process.env.GENERATE_SOURCEMAP !== 'false';
|
||||
// Some apps do not need the benefits of saving a web request, so not inlining the chunk
|
||||
// makes for a smoother build process.
|
||||
const shouldInlineRuntimeChunk = process.env.INLINE_RUNTIME_CHUNK !== 'false';
|
||||
|
||||
const imageInlineSizeLimit = parseInt(
|
||||
process.env.IMAGE_INLINE_SIZE_LIMIT || '10000'
|
||||
);
|
||||
|
||||
// Check if TypeScript is setup
|
||||
const useTypeScript = fs.existsSync(paths.appTsConfig);
|
||||
|
||||
@@ -93,10 +102,7 @@ module.exports = function(webpackEnv) {
|
||||
isEnvDevelopment && require.resolve('style-loader'),
|
||||
isEnvProduction && {
|
||||
loader: MiniCssExtractPlugin.loader,
|
||||
options: Object.assign(
|
||||
{},
|
||||
shouldUseRelativeAssetPaths ? { publicPath: '../../' } : undefined
|
||||
),
|
||||
options: shouldUseRelativeAssetPaths ? { publicPath: '../../' } : {},
|
||||
},
|
||||
{
|
||||
loader: require.resolve('css-loader'),
|
||||
@@ -129,12 +135,20 @@ module.exports = function(webpackEnv) {
|
||||
},
|
||||
].filter(Boolean);
|
||||
if (preProcessor) {
|
||||
loaders.push({
|
||||
loader: require.resolve(preProcessor),
|
||||
loaders.push(
|
||||
{
|
||||
loader: require.resolve('resolve-url-loader'),
|
||||
options: {
|
||||
sourceMap: isEnvProduction && shouldUseSourceMap,
|
||||
},
|
||||
});
|
||||
},
|
||||
{
|
||||
loader: require.resolve(preProcessor),
|
||||
options: {
|
||||
sourceMap: true,
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
return loaders;
|
||||
};
|
||||
@@ -199,6 +213,9 @@ module.exports = function(webpackEnv) {
|
||||
.replace(/\\/g, '/')
|
||||
: isEnvDevelopment &&
|
||||
(info => path.resolve(info.absoluteResourcePath).replace(/\\/g, '/')),
|
||||
// Prevents conflicts when multiple Webpack runtimes (from different apps)
|
||||
// are used on the same page.
|
||||
jsonpFunction: `webpackJsonp${appPackageJson.name}`,
|
||||
},
|
||||
optimization: {
|
||||
minimize: isEnvProduction,
|
||||
@@ -207,8 +224,8 @@ module.exports = function(webpackEnv) {
|
||||
new TerserPlugin({
|
||||
terserOptions: {
|
||||
parse: {
|
||||
// we want terser to parse ecma 8 code. However, we don't want it
|
||||
// to apply any minfication steps that turns valid ecma 5 code
|
||||
// We want terser to parse ecma 8 code. However, we don't want it
|
||||
// to apply any minification steps that turns valid ecma 5 code
|
||||
// into invalid ecma 5 code. This is why the 'compress' and 'output'
|
||||
// sections only apply transformations that are ecma 5 safe
|
||||
// https://github.com/facebook/create-react-app/pull/4234
|
||||
@@ -224,14 +241,14 @@ module.exports = function(webpackEnv) {
|
||||
comparisons: false,
|
||||
// Disabled because of an issue with Terser breaking valid code:
|
||||
// https://github.com/facebook/create-react-app/issues/5250
|
||||
// Pending futher investigation:
|
||||
// Pending further investigation:
|
||||
// https://github.com/terser-js/terser/issues/120
|
||||
inline: 2,
|
||||
},
|
||||
mangle: {
|
||||
safari10: true,
|
||||
//keep_classnames: true,
|
||||
//keep_fnames: true,
|
||||
keep_classnames: true,
|
||||
keep_fnames: true,
|
||||
module: true
|
||||
},
|
||||
output: {
|
||||
@@ -289,7 +306,9 @@ module.exports = function(webpackEnv) {
|
||||
// We placed these paths second because we want `node_modules` to "win"
|
||||
// if there are any conflicts. This matches Node resolution mechanism.
|
||||
// https://github.com/facebook/create-react-app/issues/253
|
||||
modules: ['node_modules', paths.appNodeModules].concat(modules.additionalModulePaths || []),
|
||||
modules: ['node_modules', paths.appNodeModules].concat(
|
||||
modules.additionalModulePaths || []
|
||||
),
|
||||
// These are the reasonable defaults supported by the Node ecosystem.
|
||||
// We also include JSX as a common component filename extension to support
|
||||
// some tools, although we do not recommend using it, see:
|
||||
@@ -339,6 +358,7 @@ module.exports = function(webpackEnv) {
|
||||
options: {
|
||||
formatter: require.resolve('react-dev-utils/eslintFormatter'),
|
||||
eslintPath: require.resolve('eslint'),
|
||||
resolvePluginsRelativeTo: __dirname,
|
||||
|
||||
},
|
||||
loader: require.resolve('eslint-loader'),
|
||||
@@ -358,7 +378,7 @@ module.exports = function(webpackEnv) {
|
||||
test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/],
|
||||
loader: require.resolve('url-loader'),
|
||||
options: {
|
||||
limit: 10000,
|
||||
limit: imageInlineSizeLimit,
|
||||
name: 'static/media/[name].[hash:8].[ext]',
|
||||
},
|
||||
},
|
||||
@@ -408,8 +428,6 @@ module.exports = function(webpackEnv) {
|
||||
require.resolve('babel-preset-react-app/dependencies'),
|
||||
{ helpers: true },
|
||||
],
|
||||
"@babel/preset-env",
|
||||
"@babel/preset-react"
|
||||
],
|
||||
plugins: [
|
||||
[
|
||||
@@ -514,12 +532,8 @@ module.exports = function(webpackEnv) {
|
||||
},
|
||||
plugins: [
|
||||
isEnvDevelopment &&
|
||||
new HtmlWebpackPlugin({
|
||||
inject: true,
|
||||
template: paths.appHtml,
|
||||
}),
|
||||
// Generates an `index.html` file with the <script> injected.
|
||||
/*new HtmlWebpackPlugin(
|
||||
new HtmlWebpackPlugin(
|
||||
Object.assign(
|
||||
{},
|
||||
{
|
||||
@@ -543,7 +557,7 @@ module.exports = function(webpackEnv) {
|
||||
}
|
||||
: undefined
|
||||
)
|
||||
),*/
|
||||
),
|
||||
// Inlines the webpack runtime script. This script is too small to warrant
|
||||
// a network request.
|
||||
isEnvProduction &&
|
||||
@@ -639,6 +653,12 @@ module.exports = function(webpackEnv) {
|
||||
async: isEnvDevelopment,
|
||||
useTypescriptIncrementalApi: true,
|
||||
checkSyntacticErrors: true,
|
||||
resolveModuleNameModule: process.versions.pnp
|
||||
? `${__dirname}/pnpTs.js`
|
||||
: undefined,
|
||||
resolveTypeReferenceDirectiveModule: process.versions.pnp
|
||||
? `${__dirname}/pnpTs.js`
|
||||
: undefined,
|
||||
tsconfig: paths.appTsConfig,
|
||||
reportFiles: [
|
||||
'**',
|
||||
|
||||
4591
package-lock.json
generated
4591
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
119
package.json
119
package.json
@@ -1,16 +1,15 @@
|
||||
{
|
||||
"name": "simple-keyboard",
|
||||
"version": "2.23.0",
|
||||
"version": "2.26.0",
|
||||
"description": "On-screen Javascript Virtual Keyboard",
|
||||
"main": "build/index.js",
|
||||
"types": "build/index.d.ts",
|
||||
"scripts": {
|
||||
"start": "node scripts/start.js",
|
||||
"build": "node scripts/build.js && node scripts/buildUnminified.js",
|
||||
"build": "node scripts/build.js",
|
||||
"demo": "node scripts/demo.js",
|
||||
"test": "node scripts/test.js",
|
||||
"coverage": "node scripts/test.js --coverage --watchAll=false",
|
||||
"postinstall": "node bin/postinstall",
|
||||
"prepare": "npm run build"
|
||||
},
|
||||
"repository": {
|
||||
@@ -40,81 +39,74 @@
|
||||
],
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@babel/core": "7.4.5",
|
||||
"@babel/core": "7.5.5",
|
||||
"@babel/plugin-proposal-class-properties": "^7.4.4",
|
||||
"@babel/preset-env": "^7.4.4",
|
||||
"@babel/preset-react": "^7.0.0",
|
||||
"@babel/runtime": "^7.4.4",
|
||||
"@svgr/webpack": "4.3.0",
|
||||
"@typescript-eslint/eslint-plugin": "1.9.0",
|
||||
"@typescript-eslint/parser": "1.9.0",
|
||||
"babel-eslint": "10.0.1",
|
||||
"babel-jest": "24.8.0",
|
||||
"@svgr/webpack": "4.3.2",
|
||||
"@typescript-eslint/eslint-plugin": "1.13.0",
|
||||
"@typescript-eslint/parser": "1.13.0",
|
||||
"babel-eslint": "10.0.2",
|
||||
"babel-jest": "^24.8.0",
|
||||
"babel-loader": "8.0.6",
|
||||
"babel-plugin-named-asset-import": "^0.3.2",
|
||||
"babel-preset-react-app": "^9.0.0",
|
||||
"babel-plugin-named-asset-import": "^0.3.3",
|
||||
"babel-preset-react-app": "^9.0.1",
|
||||
"camelcase": "^5.2.0",
|
||||
"case-sensitive-paths-webpack-plugin": "2.2.0",
|
||||
"copy-webpack-plugin": "^5.0.3",
|
||||
"css-loader": "2.1.1",
|
||||
"dotenv": "8.0.0",
|
||||
"dotenv-expand": "5.1.0",
|
||||
"eslint": "^5.16.0",
|
||||
"eslint-config-react-app": "^4.0.1",
|
||||
"eslint-loader": "2.1.2",
|
||||
"eslint-plugin-flowtype": "3.9.1",
|
||||
"eslint-plugin-import": "2.17.3",
|
||||
"eslint-plugin-jsx-a11y": "6.2.1",
|
||||
"eslint-plugin-react": "7.13.0",
|
||||
"eslint-plugin-react-hooks": "^1.5.0",
|
||||
"dotenv": "6.2.0",
|
||||
"dotenv-expand": "4.2.0",
|
||||
"eslint": "^6.1.0",
|
||||
"eslint-config-react-app": "^5.0.1",
|
||||
"eslint-loader": "2.2.1",
|
||||
"eslint-plugin-flowtype": "3.13.0",
|
||||
"eslint-plugin-import": "2.18.2",
|
||||
"eslint-plugin-jsx-a11y": "6.2.3",
|
||||
"eslint-plugin-react": "7.14.3",
|
||||
"eslint-plugin-react-hooks": "^1.6.1",
|
||||
"file-loader": "3.0.1",
|
||||
"fs-extra": "8.0.1",
|
||||
"fs-extra": "7.0.1",
|
||||
"html-webpack-plugin": "4.0.0-beta.5",
|
||||
"identity-obj-proxy": "3.0.0",
|
||||
"is-wsl": "^2.0.0",
|
||||
"is-wsl": "^1.1.0",
|
||||
"jest": "24.8.0",
|
||||
"jest-environment-jsdom-fourteen": "0.1.0",
|
||||
"jest-resolve": "24.8.0",
|
||||
"jest-resolve": "24.9.0",
|
||||
"jest-watch-typeahead": "0.3.1",
|
||||
"mini-css-extract-plugin": "0.7.0",
|
||||
"optimize-css-assets-webpack-plugin": "5.0.1",
|
||||
"pnp-webpack-plugin": "1.4.3",
|
||||
"postcss-flexbugs-fixes": "4.1.0",
|
||||
"postcss-loader": "3.0.0",
|
||||
"postcss-normalize": "7.0.0",
|
||||
"postcss-preset-env": "6.6.0",
|
||||
"postcss-safe-parser": "4.0.1",
|
||||
"mini-css-extract-plugin": "0.5.0",
|
||||
"optimize-css-assets-webpack-plugin": "5.0.3",
|
||||
"pnp-webpack-plugin": "1.5.0",
|
||||
"prettier": "^1.17.0",
|
||||
"prettier-webpack-plugin": "^1.2.0",
|
||||
"react": "^16.8.6",
|
||||
"react-app-polyfill": "^1.0.1",
|
||||
"react-dev-utils": "^9.0.1",
|
||||
"react-dom": "^16.8.6",
|
||||
"resolve": "1.11.1",
|
||||
"sass-loader": "7.1.0",
|
||||
"semver": "6.1.1",
|
||||
"style-loader": "0.23.1",
|
||||
"terser-webpack-plugin": "1.3.0",
|
||||
"postcss-flexbugs-fixes": "4.1.0",
|
||||
"postcss-loader": "3.0.0",
|
||||
"postcss-normalize": "7.0.1",
|
||||
"postcss-preset-env": "6.7.0",
|
||||
"postcss-safe-parser": "4.0.1",
|
||||
"react": "^16.9.0",
|
||||
"react-app-polyfill": "^1.0.2",
|
||||
"react-dev-utils": "^9.0.3",
|
||||
"react-dom": "^16.9.0",
|
||||
"resolve": "1.12.0",
|
||||
"resolve-url-loader": "3.1.0",
|
||||
"sass-loader": "7.2.0",
|
||||
"semver": "6.3.0",
|
||||
"style-loader": "1.0.0",
|
||||
"terser-webpack-plugin": "1.4.1",
|
||||
"ts-pnp": "1.1.2",
|
||||
"uglifyjs-webpack-plugin": "^2.1.2",
|
||||
"url-loader": "1.1.2",
|
||||
"webpack": "4.33.0",
|
||||
"webpack-dev-server": "3.5.1",
|
||||
"url-loader": "2.1.0",
|
||||
"webpack": "4.39.1",
|
||||
"webpack-dev-server": "3.2.1",
|
||||
"webpack-manifest-plugin": "2.0.4",
|
||||
"workbox-webpack-plugin": "4.3.1"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": "react-app",
|
||||
"settings": {
|
||||
"react": {
|
||||
"pragma": "React",
|
||||
"version": "detect"
|
||||
}
|
||||
}
|
||||
"extends": "react-app"
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not ie <= 11",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
@@ -124,20 +116,25 @@
|
||||
]
|
||||
},
|
||||
"jest": {
|
||||
"roots": [
|
||||
"<rootDir>/src"
|
||||
],
|
||||
"collectCoverageFrom": [
|
||||
"src/**/*.{js,jsx,ts,tsx}",
|
||||
"!src/**/*.d.ts",
|
||||
"!src/lib/index.js",
|
||||
"!src/demo/index.js",
|
||||
"!src/utils/**",
|
||||
"!src/**/*.d.ts",
|
||||
"!**/tests/**"
|
||||
],
|
||||
"resolver": "jest-pnp-resolver",
|
||||
"setupFiles": [
|
||||
"react-app-polyfill/jsdom"
|
||||
],
|
||||
"setupFilesAfterEnv": [],
|
||||
"testMatch": [
|
||||
"<rootDir>/src/**/tests/**/*.{js,jsx,ts,tsx}"
|
||||
"<rootDir>/src/**/__tests__/**/*.{js,jsx,ts,tsx}",
|
||||
"<rootDir>/src/**/*.{spec,test}.{js,jsx,ts,tsx}"
|
||||
],
|
||||
"testEnvironment": "jest-environment-jsdom-fourteen",
|
||||
"transform": {
|
||||
@@ -173,15 +170,11 @@
|
||||
},
|
||||
"babel": {
|
||||
"presets": [
|
||||
"@babel/preset-env",
|
||||
"@babel/preset-react"
|
||||
"react-app"
|
||||
],
|
||||
"plugins": [
|
||||
[
|
||||
"@babel/plugin-proposal-class-properties",
|
||||
{
|
||||
"loose": true
|
||||
}
|
||||
"@babel/plugin-proposal-class-properties"
|
||||
]
|
||||
]
|
||||
}
|
||||
|
||||
@@ -23,8 +23,6 @@
|
||||
<noscript>
|
||||
You need to enable JavaScript to run this app.
|
||||
</noscript>
|
||||
<div id="root">
|
||||
<div class="simple-keyboard"></div>
|
||||
</div>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -106,8 +106,8 @@ checkBrowsers(paths.appPath, isInteractive)
|
||||
);
|
||||
},
|
||||
err => {
|
||||
console.log(chalk.red('Failed to compile.\n'));
|
||||
printBuildError(err);
|
||||
throw new Error('Failed to compile');
|
||||
process.exit(1);
|
||||
}
|
||||
)
|
||||
|
||||
@@ -1,190 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
// Do this as the first thing so that any code reading it knows the right env.
|
||||
process.env.BABEL_ENV = 'production';
|
||||
process.env.NODE_ENV = 'production';
|
||||
|
||||
// Makes the script crash on unhandled rejections instead of silently
|
||||
// ignoring them. In the future, promise rejections that are not handled will
|
||||
// terminate the Node.js process with a non-zero exit code.
|
||||
process.on('unhandledRejection', err => {
|
||||
throw err;
|
||||
});
|
||||
|
||||
// Ensure environment variables are read.
|
||||
require('../config/env');
|
||||
|
||||
|
||||
const path = require('path');
|
||||
const chalk = require('react-dev-utils/chalk');
|
||||
const fs = require('fs-extra');
|
||||
const webpack = require('webpack');
|
||||
const configFactory = require('../config/webpack.config.unminified');
|
||||
const paths = require('../config/paths');
|
||||
const checkRequiredFiles = require('react-dev-utils/checkRequiredFiles');
|
||||
const formatWebpackMessages = require('react-dev-utils/formatWebpackMessages');
|
||||
const printHostingInstructions = require('react-dev-utils/printHostingInstructions');
|
||||
const FileSizeReporter = require('react-dev-utils/FileSizeReporter');
|
||||
const printBuildError = require('react-dev-utils/printBuildError');
|
||||
|
||||
const measureFileSizesBeforeBuild =
|
||||
FileSizeReporter.measureFileSizesBeforeBuild;
|
||||
const printFileSizesAfterBuild = FileSizeReporter.printFileSizesAfterBuild;
|
||||
const useYarn = fs.existsSync(paths.yarnLockFile);
|
||||
|
||||
// These sizes are pretty large. We'll warn for bundles exceeding them.
|
||||
const WARN_AFTER_BUNDLE_GZIP_SIZE = 512 * 1024;
|
||||
const WARN_AFTER_CHUNK_GZIP_SIZE = 1024 * 1024;
|
||||
|
||||
const isInteractive = process.stdout.isTTY;
|
||||
|
||||
// Warn and crash if required files are missing
|
||||
if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Generate configuration
|
||||
const config = configFactory('production');
|
||||
|
||||
// We require that you explicitly set browsers and do not fall back to
|
||||
// browserslist defaults.
|
||||
const { checkBrowsers } = require('react-dev-utils/browsersHelper');
|
||||
checkBrowsers(paths.appPath, isInteractive)
|
||||
.then(() => {
|
||||
// First, read the current file sizes in build directory.
|
||||
// This lets us display how much they changed later.
|
||||
return measureFileSizesBeforeBuild(paths.appBuild);
|
||||
})
|
||||
.then(previousFileSizes => {
|
||||
// Remove all content but keep the directory so that
|
||||
// if you're in it, you don't end up in Trash
|
||||
//fs.emptyDirSync(paths.appBuild);
|
||||
// Merge with the public folder
|
||||
//copyPublicFolder();
|
||||
// Start the webpack build
|
||||
return build(previousFileSizes);
|
||||
})
|
||||
.then(
|
||||
({ stats, previousFileSizes, warnings }) => {
|
||||
if (warnings.length) {
|
||||
console.log(chalk.yellow('Compiled with warnings.\n'));
|
||||
console.log(warnings.join('\n\n'));
|
||||
console.log(
|
||||
'\nSearch for the ' +
|
||||
chalk.underline(chalk.yellow('keywords')) +
|
||||
' to learn more about each warning.'
|
||||
);
|
||||
console.log(
|
||||
'To ignore, add ' +
|
||||
chalk.cyan('// eslint-disable-next-line') +
|
||||
' to the line before.\n'
|
||||
);
|
||||
} else {
|
||||
console.log(chalk.green('Compiled successfully.\n'));
|
||||
}
|
||||
|
||||
console.log('File sizes after gzip:\n');
|
||||
printFileSizesAfterBuild(
|
||||
stats,
|
||||
previousFileSizes,
|
||||
paths.appBuild,
|
||||
WARN_AFTER_BUNDLE_GZIP_SIZE,
|
||||
WARN_AFTER_CHUNK_GZIP_SIZE
|
||||
);
|
||||
console.log();
|
||||
|
||||
const appPackage = require(paths.appPackageJson);
|
||||
const publicUrl = paths.publicUrl;
|
||||
const publicPath = config.output.publicPath;
|
||||
const buildFolder = path.relative(process.cwd(), paths.appBuild);
|
||||
printHostingInstructions(
|
||||
appPackage,
|
||||
publicUrl,
|
||||
publicPath,
|
||||
buildFolder,
|
||||
useYarn
|
||||
);
|
||||
},
|
||||
err => {
|
||||
console.log(chalk.red('Failed to compile.\n'));
|
||||
printBuildError(err);
|
||||
process.exit(1);
|
||||
}
|
||||
)
|
||||
.catch(err => {
|
||||
if (err && err.message) {
|
||||
console.log(err.message);
|
||||
}
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
// Create the production build and print the deployment instructions.
|
||||
function build(previousFileSizes) {
|
||||
console.log('Creating a non-minified production build...');
|
||||
// We used to support resolving modules according to `NODE_PATH`.
|
||||
// This now has been deprecated in favor of jsconfig/tsconfig.json
|
||||
// This lets you use absolute paths in imports inside large monorepos:
|
||||
if (process.env.NODE_PATH) {
|
||||
console.log(
|
||||
chalk.yellow(
|
||||
'Setting NODE_PATH to resolve modules absolutely has been deprecated in favor of setting baseUrl in jsconfig.json (or tsconfig.json if you are using TypeScript) and will be removed in a future major release of create-react-app.'
|
||||
)
|
||||
);
|
||||
console.log();
|
||||
}
|
||||
|
||||
const compiler = webpack(config);
|
||||
return new Promise((resolve, reject) => {
|
||||
compiler.run((err, stats) => {
|
||||
let messages;
|
||||
if (err) {
|
||||
if (!err.message) {
|
||||
return reject(err);
|
||||
}
|
||||
messages = formatWebpackMessages({
|
||||
errors: [err.message],
|
||||
warnings: [],
|
||||
});
|
||||
} else {
|
||||
messages = formatWebpackMessages(
|
||||
stats.toJson({ all: false, warnings: true, errors: true })
|
||||
);
|
||||
}
|
||||
if (messages.errors.length) {
|
||||
// Only keep the first error. Others are often indicative
|
||||
// of the same problem, but confuse the reader with noise.
|
||||
if (messages.errors.length > 1) {
|
||||
messages.errors.length = 1;
|
||||
}
|
||||
return reject(new Error(messages.errors.join('\n\n')));
|
||||
}
|
||||
if (
|
||||
process.env.CI &&
|
||||
(typeof process.env.CI !== 'string' ||
|
||||
process.env.CI.toLowerCase() !== 'false') &&
|
||||
messages.warnings.length
|
||||
) {
|
||||
console.log(
|
||||
chalk.yellow(
|
||||
'\nTreating warnings as errors because process.env.CI = true.\n' +
|
||||
'Most CI servers set it automatically.\n'
|
||||
)
|
||||
);
|
||||
return reject(new Error(messages.warnings.join('\n\n')));
|
||||
}
|
||||
|
||||
return resolve({
|
||||
stats,
|
||||
previousFileSizes,
|
||||
warnings: messages.warnings,
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function copyPublicFolder() {
|
||||
fs.copySync(paths.appPublic, paths.appBuild, {
|
||||
dereference: true,
|
||||
filter: file => file !== paths.appHtml,
|
||||
});
|
||||
}
|
||||
@@ -106,8 +106,8 @@ checkBrowsers(paths.appPath, isInteractive)
|
||||
);
|
||||
},
|
||||
err => {
|
||||
console.log(chalk.red('Failed to compile.\n'));
|
||||
printBuildError(err);
|
||||
throw new Error('Failed to compile');
|
||||
process.exit(1);
|
||||
}
|
||||
)
|
||||
|
||||
@@ -41,7 +41,8 @@ function isInMercurialRepository() {
|
||||
// Watch unless on CI or explicitly running all tests
|
||||
if (
|
||||
!process.env.CI &&
|
||||
argv.indexOf('--watchAll') === -1
|
||||
argv.indexOf('--watchAll') === -1 &&
|
||||
argv.indexOf('--watchAll=false') === -1
|
||||
) {
|
||||
// https://github.com/facebook/create-react-app/issues/5210
|
||||
const hasSourceControl = isInGitRepository() || isInMercurialRepository();
|
||||
|
||||
@@ -1,87 +0,0 @@
|
||||
import Keyboard from "../lib";
|
||||
import "./css/App.css";
|
||||
|
||||
/**
|
||||
* simple-keyboard demo
|
||||
*/
|
||||
class App {
|
||||
/**
|
||||
* Instantiates the demo class
|
||||
*/
|
||||
constructor() {
|
||||
document.addEventListener("DOMContentLoaded", this.onDOMLoaded);
|
||||
|
||||
/**
|
||||
* Default input name
|
||||
* @type {string}
|
||||
*/
|
||||
this.layoutName = "default";
|
||||
}
|
||||
|
||||
/**
|
||||
* Executed when the DOM is ready
|
||||
*/
|
||||
onDOMLoaded = () => {
|
||||
/**
|
||||
* Creates a new simple-keyboard instance
|
||||
*/
|
||||
this.keyboard = new Keyboard({
|
||||
debug: true,
|
||||
layoutName: this.layoutName,
|
||||
onChange: input => this.onChange(input),
|
||||
onKeyPress: button => this.onKeyPress(button),
|
||||
newLineOnEnter: true,
|
||||
physicalKeyboardHighlight: true
|
||||
});
|
||||
|
||||
/**
|
||||
* Adding preview (demo only)
|
||||
*/
|
||||
document.querySelector(".simple-keyboard").insertAdjacentHTML(
|
||||
"beforebegin",
|
||||
`
|
||||
<div class="simple-keyboard-preview">
|
||||
<textarea class="input"></textarea>
|
||||
</div>
|
||||
`
|
||||
);
|
||||
|
||||
document.querySelector(".input").addEventListener("input", event => {
|
||||
this.keyboard.setInput(event.target.value);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles shift functionality
|
||||
*/
|
||||
handleShiftButton = () => {
|
||||
let layoutName = this.layoutName;
|
||||
let shiftToggle = (this.layoutName =
|
||||
layoutName === "default" ? "shift" : "default");
|
||||
|
||||
this.keyboard.setOptions({
|
||||
layoutName: shiftToggle
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Called when simple-keyboard input has changed
|
||||
*/
|
||||
onChange = input => {
|
||||
document.querySelector(".input").value = input;
|
||||
};
|
||||
|
||||
/**
|
||||
* Called when a simple-keyboard key is pressed
|
||||
*/
|
||||
onKeyPress = button => {
|
||||
console.log("Button pressed", button);
|
||||
|
||||
/**
|
||||
* Shift functionality
|
||||
*/
|
||||
if (button === "{lock}" || button === "{shift}") this.handleShiftButton();
|
||||
};
|
||||
}
|
||||
|
||||
export default App;
|
||||
55
src/demo/BasicDemo.js
Normal file
55
src/demo/BasicDemo.js
Normal file
@@ -0,0 +1,55 @@
|
||||
import Keyboard from "../lib";
|
||||
import "./css/BasicDemo.css";
|
||||
|
||||
const setDOM = () => {
|
||||
document.querySelector("#root").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)
|
||||
});
|
||||
|
||||
/**
|
||||
* Update simple-keyboard when input is changed directly
|
||||
*/
|
||||
document.querySelector(".input").addEventListener("input", event => {
|
||||
this.keyboard.setInput(event.target.value);
|
||||
});
|
||||
}
|
||||
|
||||
onChange(input) {
|
||||
document.querySelector(".input").value = input;
|
||||
console.log("Input changed", input);
|
||||
}
|
||||
|
||||
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() {
|
||||
let currentLayout = this.keyboard.options.layoutName;
|
||||
let shiftToggle = currentLayout === "default" ? "shift" : "default";
|
||||
|
||||
this.keyboard.setOptions({
|
||||
layoutName: shiftToggle
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default Demo;
|
||||
73
src/demo/ButtonThemeDemo.js
Normal file
73
src/demo/ButtonThemeDemo.js
Normal file
@@ -0,0 +1,73 @@
|
||||
import Keyboard from "../lib";
|
||||
import "./css/ButtonThemeDemo.css";
|
||||
|
||||
const setDOM = () => {
|
||||
document.querySelector("#root").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({
|
||||
theme: "hg-theme-default my-theme",
|
||||
onChange: input => this.onChange(input),
|
||||
onKeyPress: button => this.onKeyPress(button),
|
||||
buttonTheme: [
|
||||
{
|
||||
class: "my-button",
|
||||
buttons: "{enter} {bksp} q Q"
|
||||
},
|
||||
{
|
||||
class: "my-button-outline",
|
||||
buttons: "q Q b B"
|
||||
}
|
||||
],
|
||||
buttonAttributes: [
|
||||
{
|
||||
attribute: "aria-label",
|
||||
value: "bee",
|
||||
buttons: "b B"
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
/**
|
||||
* Update simple-keyboard when input is changed directly
|
||||
*/
|
||||
document.querySelector(".input").addEventListener("input", event => {
|
||||
this.keyboard.setInput(event.target.value);
|
||||
});
|
||||
}
|
||||
|
||||
onChange(input) {
|
||||
document.querySelector(".input").value = input;
|
||||
console.log("Input changed", input);
|
||||
}
|
||||
|
||||
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() {
|
||||
let currentLayout = this.keyboard.options.layoutName;
|
||||
let shiftToggle = currentLayout === "default" ? "shift" : "default";
|
||||
|
||||
this.keyboard.setOptions({
|
||||
layoutName: shiftToggle
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default Demo;
|
||||
157
src/demo/FullKeyboardDemo.js
Normal file
157
src/demo/FullKeyboardDemo.js
Normal file
@@ -0,0 +1,157 @@
|
||||
import Keyboard from "../lib";
|
||||
import "./css/FullKeyboardDemo.css";
|
||||
|
||||
const setDOM = () => {
|
||||
document.querySelector("#root").innerHTML = `
|
||||
<input class="input" placeholder="Tap on the virtual keyboard to start" />
|
||||
<div class="keyboardContainer">
|
||||
<div class="simple-keyboard-main"></div>
|
||||
|
||||
<div class="controlArrows">
|
||||
<div class="simple-keyboard-control"></div>
|
||||
<div class="simple-keyboard-arrows"></div>
|
||||
</div>
|
||||
|
||||
<div class="numPad">
|
||||
<div class="simple-keyboard-numpad"></div>
|
||||
<div class="simple-keyboard-numpadEnd"></div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
};
|
||||
|
||||
class Demo {
|
||||
constructor() {
|
||||
setDOM();
|
||||
|
||||
/**
|
||||
* Demo Start
|
||||
*/
|
||||
let commonKeyboardOptions = {
|
||||
onChange: input => this.onChange(input),
|
||||
onKeyPress: button => this.onKeyPress(button),
|
||||
theme: "simple-keyboard hg-theme-default hg-layout-default",
|
||||
physicalKeyboardHighlight: true,
|
||||
syncInstanceInputs: true,
|
||||
mergeDisplay: true,
|
||||
debug: true
|
||||
};
|
||||
|
||||
this.keyboard = new Keyboard(".simple-keyboard-main", {
|
||||
...commonKeyboardOptions,
|
||||
/**
|
||||
* Layout by:
|
||||
* Sterling Butters (https://github.com/SterlingButters)
|
||||
*/
|
||||
layout: {
|
||||
default: [
|
||||
"{escape} {f1} {f2} {f3} {f4} {f5} {f6} {f7} {f8} {f9} {f10} {f11} {f12}",
|
||||
"` 1 2 3 4 5 6 7 8 9 0 - = {backspace}",
|
||||
"{tab} q w e r t y u i o p [ ] \\",
|
||||
"{capslock} a s d f g h j k l ; ' {enter}",
|
||||
"{shiftleft} z x c v b n m , . / {shiftright}",
|
||||
"{controlleft} {altleft} {metaleft} {space} {metaright} {altright}"
|
||||
],
|
||||
shift: [
|
||||
"{escape} {f1} {f2} {f3} {f4} {f5} {f6} {f7} {f8} {f9} {f10} {f11} {f12}",
|
||||
"~ ! @ # $ % ^ & * ( ) _ + {backspace}",
|
||||
"{tab} Q W E R T Y U I O P { } |",
|
||||
'{capslock} A S D F G H J K L : " {enter}',
|
||||
"{shiftleft} Z X C V B N M < > ? {shiftright}",
|
||||
"{controlleft} {altleft} {metaleft} {space} {metaright} {altright}"
|
||||
]
|
||||
},
|
||||
display: {
|
||||
"{escape}": "esc ⎋",
|
||||
"{tab}": "tab ⇥",
|
||||
"{backspace}": "backspace ⌫",
|
||||
"{enter}": "enter ↵",
|
||||
"{capslock}": "caps lock ⇪",
|
||||
"{shiftleft}": "shift ⇧",
|
||||
"{shiftright}": "shift ⇧",
|
||||
"{controlleft}": "ctrl ⌃",
|
||||
"{controlright}": "ctrl ⌃",
|
||||
"{altleft}": "alt ⌥",
|
||||
"{altright}": "alt ⌥",
|
||||
"{metaleft}": "cmd ⌘",
|
||||
"{metaright}": "cmd ⌘"
|
||||
}
|
||||
});
|
||||
|
||||
this.keyboardControlPad = new Keyboard(".simple-keyboard-control", {
|
||||
...commonKeyboardOptions,
|
||||
layout: {
|
||||
default: [
|
||||
"{prtscr} {scrolllock} {pause}",
|
||||
"{insert} {home} {pageup}",
|
||||
"{delete} {end} {pagedown}"
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
this.keyboardArrows = new Keyboard(".simple-keyboard-arrows", {
|
||||
...commonKeyboardOptions,
|
||||
layout: {
|
||||
default: ["{arrowup}", "{arrowleft} {arrowdown} {arrowright}"]
|
||||
}
|
||||
});
|
||||
|
||||
this.keyboardNumPad = new Keyboard(".simple-keyboard-numpad", {
|
||||
...commonKeyboardOptions,
|
||||
layout: {
|
||||
default: [
|
||||
"{numlock} {numpaddivide} {numpadmultiply}",
|
||||
"{numpad7} {numpad8} {numpad9}",
|
||||
"{numpad4} {numpad5} {numpad6}",
|
||||
"{numpad1} {numpad2} {numpad3}",
|
||||
"{numpad0} {numpaddecimal}"
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
this.keyboardNumPadEnd = new Keyboard(".simple-keyboard-numpadEnd", {
|
||||
...commonKeyboardOptions,
|
||||
layout: {
|
||||
default: ["{numpadsubtract}", "{numpadadd}", "{numpadenter}"]
|
||||
}
|
||||
});
|
||||
|
||||
document.querySelector(".input").addEventListener("input", event => {
|
||||
let input = document.querySelector(".input").value;
|
||||
this.keyboard.setInput(input);
|
||||
});
|
||||
}
|
||||
|
||||
onChange(input) {
|
||||
document.querySelector(".input").value = input;
|
||||
this.keyboard.setInput(input);
|
||||
|
||||
console.log("Input changed", input);
|
||||
}
|
||||
|
||||
onKeyPress(button) {
|
||||
console.log("Button pressed", button);
|
||||
|
||||
/**
|
||||
* If you want to handle the shift and caps lock buttons
|
||||
*/
|
||||
if (
|
||||
button === "{shift}" ||
|
||||
button === "{shiftleft}" ||
|
||||
button === "{shiftright}" ||
|
||||
button === "{capslock}"
|
||||
)
|
||||
this.handleShift();
|
||||
}
|
||||
|
||||
handleShift() {
|
||||
let currentLayout = this.keyboard.options.layoutName;
|
||||
let shiftToggle = currentLayout === "default" ? "shift" : "default";
|
||||
|
||||
this.keyboard.setOptions({
|
||||
layoutName: shiftToggle
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default Demo;
|
||||
@@ -1,33 +0,0 @@
|
||||
#root {
|
||||
font-family: "HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue",
|
||||
Helvetica, Arial, "Lucida Grande", sans-serif;
|
||||
max-width: 850px;
|
||||
margin: 0 auto;
|
||||
padding-top: 20px;
|
||||
}
|
||||
|
||||
#root .simple-keyboard-preview {
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
border: 20px solid rgba(0, 0, 0, 0.1);
|
||||
height: 200px;
|
||||
border-top-right-radius: 5px;
|
||||
border-top-left-radius: 5px;
|
||||
padding: 10px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
#root .input {
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
background: transparent;
|
||||
border: none;
|
||||
outline: none;
|
||||
font-family: monospace;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.simple-keyboard.hg-layout-custom {
|
||||
border-top-left-radius: 0px;
|
||||
border-top-right-radius: 0px;
|
||||
}
|
||||
12
src/demo/css/BasicDemo.css
Normal file
12
src/demo/css/BasicDemo.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;
|
||||
}
|
||||
26
src/demo/css/ButtonThemeDemo.css
Normal file
26
src/demo/css/ButtonThemeDemo.css
Normal file
@@ -0,0 +1,26 @@
|
||||
input {
|
||||
width: 100%;
|
||||
height: 100px;
|
||||
padding: 20px;
|
||||
font-size: 20px;
|
||||
border: none;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.simple-keyboard {
|
||||
max-width: 850px;
|
||||
}
|
||||
|
||||
.simple-keyboard.my-theme .hg-button.my-button {
|
||||
background: blue;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.simple-keyboard.my-theme .hg-button[aria-label] {
|
||||
background: black;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.simple-keyboard.my-theme .hg-button.my-button-outline {
|
||||
border: 2px solid red;
|
||||
}
|
||||
132
src/demo/css/FullKeyboardDemo.css
Normal file
132
src/demo/css/FullKeyboardDemo.css
Normal file
@@ -0,0 +1,132 @@
|
||||
input {
|
||||
width: 100%;
|
||||
height: 100px;
|
||||
padding: 20px;
|
||||
font-size: 20px;
|
||||
border: none;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.keyboardContainer {
|
||||
display: flex;
|
||||
background-color: rgba(0, 0, 0, 0.1);
|
||||
justify-content: center;
|
||||
width: 1024px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.simple-keyboard.hg-theme-default {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.simple-keyboard-main.simple-keyboard {
|
||||
width: 640px;
|
||||
min-width: 640px;
|
||||
background: none;
|
||||
}
|
||||
|
||||
.simple-keyboard-main.simple-keyboard .hg-row:first-child {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.simple-keyboard-arrows.simple-keyboard {
|
||||
align-self: flex-end;
|
||||
background: none;
|
||||
}
|
||||
|
||||
.simple-keyboard .hg-button.selectedButton {
|
||||
background: rgba(5, 25, 70, 0.53);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.simple-keyboard .hg-button.emptySpace {
|
||||
pointer-events: none;
|
||||
background: none;
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.simple-keyboard-arrows .hg-row {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.simple-keyboard-arrows .hg-button {
|
||||
width: 50px;
|
||||
flex-grow: 0;
|
||||
justify-content: center;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.controlArrows {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex-flow: column;
|
||||
}
|
||||
|
||||
.simple-keyboard-control.simple-keyboard {
|
||||
background: none;
|
||||
}
|
||||
|
||||
.simple-keyboard-control.simple-keyboard .hg-row:first-child {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.simple-keyboard-control .hg-button {
|
||||
width: 50px;
|
||||
flex-grow: 0;
|
||||
justify-content: center;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.numPad {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.simple-keyboard-numpad.simple-keyboard {
|
||||
background: none;
|
||||
}
|
||||
|
||||
.simple-keyboard-numpad.simple-keyboard {
|
||||
width: 160px;
|
||||
}
|
||||
|
||||
.simple-keyboard-numpad.simple-keyboard .hg-button {
|
||||
width: 50px;
|
||||
justify-content: center;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.simple-keyboard-numpadEnd.simple-keyboard {
|
||||
width: 50px;
|
||||
background: none;
|
||||
margin: 0;
|
||||
padding: 5px 5px 5px 0;
|
||||
}
|
||||
|
||||
.simple-keyboard-numpadEnd.simple-keyboard .hg-button {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.simple-keyboard-numpadEnd .hg-button.hg-standardBtn.hg-button-plus {
|
||||
height: 85px;
|
||||
}
|
||||
|
||||
.simple-keyboard-numpadEnd.simple-keyboard .hg-button.hg-button-enter {
|
||||
height: 85px;
|
||||
}
|
||||
|
||||
.simple-keyboard.hg-theme-default .hg-button.hg-selectedButton {
|
||||
background: rgba(5, 25, 70, 0.53);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.hg-button.hg-functionBtn.hg-button-space {
|
||||
width: 350px;
|
||||
}
|
||||
3
src/demo/css/index.css
Normal file
3
src/demo/css/index.css
Normal file
@@ -0,0 +1,3 @@
|
||||
#root {
|
||||
padding: 0px 40px;
|
||||
}
|
||||
@@ -1,6 +1,18 @@
|
||||
import App from "./App";
|
||||
import "./css/index.css";
|
||||
|
||||
/**
|
||||
* Initializing demo
|
||||
* Demos
|
||||
*/
|
||||
new App();
|
||||
import BasicDemo from "./BasicDemo";
|
||||
//import FullKeyboardDemo from "./FullKeyboardDemo";
|
||||
//import ButtonThemeDemo from "./ButtonThemeDemo";
|
||||
|
||||
/**
|
||||
* Selected demo
|
||||
*/
|
||||
const SELECTED_DEMO = BasicDemo;
|
||||
|
||||
/**
|
||||
* Bootstrap
|
||||
*/
|
||||
new SELECTED_DEMO();
|
||||
|
||||
@@ -1,20 +1,18 @@
|
||||
import TestUtility from '../../utils/TestUtility';
|
||||
import Index from '../index';
|
||||
import App from '../App';
|
||||
import BasicDemo from '../BasicDemo';
|
||||
|
||||
let testUtil = new TestUtility();
|
||||
|
||||
it('Demo will load', () => {
|
||||
testUtil.setDOM();
|
||||
|
||||
let demo = new App();
|
||||
let demo = new BasicDemo();
|
||||
});
|
||||
|
||||
it('Demo onDOMLoaded will work', () => {
|
||||
testUtil.setDOM();
|
||||
|
||||
let demo = new App();
|
||||
demo.onDOMLoaded();
|
||||
let demo = new BasicDemo();
|
||||
|
||||
expect(demo.keyboard).toBeTruthy();
|
||||
});
|
||||
@@ -22,8 +20,7 @@ it('Demo onDOMLoaded will work', () => {
|
||||
it('Demo onChange will work', () => {
|
||||
testUtil.setDOM();
|
||||
|
||||
let demo = new App();
|
||||
demo.onDOMLoaded();
|
||||
let demo = new BasicDemo();
|
||||
|
||||
demo.onChange("test");
|
||||
|
||||
@@ -33,8 +30,7 @@ it('Demo onChange will work', () => {
|
||||
it('Demo onChange will work', () => {
|
||||
testUtil.setDOM();
|
||||
|
||||
let demo = new App();
|
||||
demo.onDOMLoaded();
|
||||
let demo = new BasicDemo();
|
||||
|
||||
demo.keyboard.getButtonElement("q").onclick();
|
||||
|
||||
@@ -44,8 +40,7 @@ it('Demo onChange will work', () => {
|
||||
it('Demo input change will work', () => {
|
||||
testUtil.setDOM();
|
||||
|
||||
let demo = new App();
|
||||
demo.onDOMLoaded();
|
||||
let demo = new BasicDemo();
|
||||
|
||||
document.body.querySelector('.input').value = "test";
|
||||
document.body.querySelector('.input').dispatchEvent(new Event('input'));
|
||||
@@ -56,8 +51,7 @@ it('Demo input change will work', () => {
|
||||
it('Demo handleShiftButton will work', () => {
|
||||
testUtil.setDOM();
|
||||
|
||||
let demo = new App();
|
||||
demo.onDOMLoaded();
|
||||
let demo = new BasicDemo();
|
||||
|
||||
demo.keyboard.getButtonElement("{shift}")[0].onclick();
|
||||
expect(demo.keyboard.options.layoutName).toBe("shift");
|
||||
77
src/demo/tests/ButtonThemeDemo.test.js
Normal file
77
src/demo/tests/ButtonThemeDemo.test.js
Normal file
@@ -0,0 +1,77 @@
|
||||
import TestUtility from '../../utils/TestUtility';
|
||||
import ButtonThemeDemo from '../ButtonThemeDemo';
|
||||
|
||||
let testUtil = new TestUtility();
|
||||
|
||||
it('Demo will load', () => {
|
||||
testUtil.setDOM();
|
||||
|
||||
let demo = new ButtonThemeDemo();
|
||||
});
|
||||
|
||||
it('Demo onDOMLoaded will work', () => {
|
||||
testUtil.setDOM();
|
||||
|
||||
let demo = new ButtonThemeDemo();
|
||||
|
||||
expect(demo.keyboard).toBeTruthy();
|
||||
});
|
||||
|
||||
it('Demo onChange will work', () => {
|
||||
testUtil.setDOM();
|
||||
|
||||
let demo = new ButtonThemeDemo();
|
||||
|
||||
demo.onChange("test");
|
||||
|
||||
expect(document.body.querySelector('.input').value).toBe("test");
|
||||
});
|
||||
|
||||
it('Demo onChange will work', () => {
|
||||
testUtil.setDOM();
|
||||
|
||||
let demo = new ButtonThemeDemo();
|
||||
|
||||
demo.keyboard.getButtonElement("q").onclick();
|
||||
|
||||
expect(document.body.querySelector('.input').value).toBe("q");
|
||||
});
|
||||
|
||||
it('Demo input change will work', () => {
|
||||
testUtil.setDOM();
|
||||
|
||||
let demo = new ButtonThemeDemo();
|
||||
|
||||
document.body.querySelector('.input').value = "test";
|
||||
document.body.querySelector('.input').dispatchEvent(new Event('input'));
|
||||
|
||||
expect(demo.keyboard.getInput()).toBe("test");
|
||||
});
|
||||
|
||||
it('Demo handleShiftButton will work', () => {
|
||||
testUtil.setDOM();
|
||||
|
||||
let demo = new ButtonThemeDemo();
|
||||
|
||||
demo.keyboard.getButtonElement("{shift}")[0].onclick();
|
||||
expect(demo.keyboard.options.layoutName).toBe("shift");
|
||||
|
||||
demo.keyboard.getButtonElement("{shift}")[0].onclick();
|
||||
expect(demo.keyboard.options.layoutName).toBe("default");
|
||||
});
|
||||
|
||||
it('Demo buttons will have proper attributes and classes', () => {
|
||||
testUtil.setDOM();
|
||||
|
||||
let demo = new ButtonThemeDemo();
|
||||
|
||||
let buttonDOM = demo.keyboard.getButtonElement("b");
|
||||
|
||||
console.log("buttonDOM", buttonDOM.outerHTML);
|
||||
|
||||
let hasAttribute = buttonDOM.hasAttribute("aria-label");
|
||||
expect(hasAttribute).toBeTruthy();
|
||||
|
||||
let hasClass = buttonDOM.classList.contains("my-button-outline");
|
||||
expect(hasClass).toBeTruthy();
|
||||
});
|
||||
62
src/demo/tests/FullKeyboardDemo.test.js
Normal file
62
src/demo/tests/FullKeyboardDemo.test.js
Normal file
@@ -0,0 +1,62 @@
|
||||
import TestUtility from '../../utils/TestUtility';
|
||||
import FullKeyboardDemo from '../FullKeyboardDemo';
|
||||
|
||||
let testUtil = new TestUtility();
|
||||
|
||||
it('Demo will load', () => {
|
||||
testUtil.setDOM();
|
||||
|
||||
let demo = new FullKeyboardDemo();
|
||||
});
|
||||
|
||||
it('Demo onDOMLoaded will work', () => {
|
||||
testUtil.setDOM();
|
||||
|
||||
let demo = new FullKeyboardDemo();
|
||||
|
||||
expect(demo.keyboard).toBeTruthy();
|
||||
});
|
||||
|
||||
it('Demo onChange will work', () => {
|
||||
testUtil.setDOM();
|
||||
|
||||
let demo = new FullKeyboardDemo();
|
||||
|
||||
demo.onChange("test");
|
||||
|
||||
expect(document.body.querySelector('.input').value).toBe("test");
|
||||
});
|
||||
|
||||
it('Demo onChange will work', () => {
|
||||
testUtil.setDOM();
|
||||
|
||||
let demo = new FullKeyboardDemo();
|
||||
|
||||
demo.keyboard.getButtonElement("q").onclick();
|
||||
|
||||
expect(document.body.querySelector('.input').value).toBe("q");
|
||||
});
|
||||
|
||||
it('Demo input change will work', () => {
|
||||
testUtil.setDOM();
|
||||
|
||||
let demo = new FullKeyboardDemo();
|
||||
|
||||
document.body.querySelector('.input').value = "test";
|
||||
document.body.querySelector('.input').dispatchEvent(new Event('input'));
|
||||
|
||||
expect(demo.keyboard.getInput()).toBe("test");
|
||||
expect(demo.keyboardNumPad.getInput()).toBe("test");
|
||||
});
|
||||
|
||||
it('Demo handleShiftButton will work', () => {
|
||||
testUtil.setDOM();
|
||||
|
||||
let demo = new FullKeyboardDemo();
|
||||
|
||||
demo.keyboard.getButtonElement("{shiftleft}").onclick();
|
||||
expect(demo.keyboard.options.layoutName).toBe("shift");
|
||||
|
||||
demo.keyboard.getButtonElement("{shiftright}").onclick();
|
||||
expect(demo.keyboard.options.layoutName).toBe("default");
|
||||
});
|
||||
30
src/lib/@types/index.d.ts
vendored
30
src/lib/@types/index.d.ts
vendored
@@ -10,6 +10,12 @@ declare module 'simple-keyboard' {
|
||||
buttons: string;
|
||||
}
|
||||
|
||||
interface KeyboardButtonAttributes {
|
||||
attribute: string;
|
||||
value: string;
|
||||
buttons: string;
|
||||
}
|
||||
|
||||
interface KeyboardOptions {
|
||||
/**
|
||||
* Modify the keyboard layout.
|
||||
@@ -41,6 +47,11 @@ declare module 'simple-keyboard' {
|
||||
*/
|
||||
buttonTheme?: KeyboardButtonTheme[];
|
||||
|
||||
/**
|
||||
* A prop to add your own attributes to one or several buttons.
|
||||
*/
|
||||
buttonAttributes?: KeyboardButtonAttributes[];
|
||||
|
||||
/**
|
||||
* Runs a `console.log` every time a key is pressed. Displays the buttons pressed and the current input.
|
||||
*/
|
||||
@@ -133,6 +144,11 @@ declare module 'simple-keyboard' {
|
||||
*/
|
||||
onKeyPress?: (button: string) => any;
|
||||
|
||||
/**
|
||||
* Executes the callback function on key release.
|
||||
*/
|
||||
onKeyReleased?: (button: string) => any;
|
||||
|
||||
/**
|
||||
* Executes the callback function on input change. Returns the current input's string.
|
||||
*/
|
||||
@@ -162,11 +178,20 @@ declare module 'simple-keyboard' {
|
||||
* Executes the callback function on input change. Returns the input object with all defined inputs.
|
||||
*/
|
||||
onChangeAll?: (inputs: any) => any;
|
||||
|
||||
/**
|
||||
* Executes the callback function on key release.
|
||||
*/
|
||||
onKeyReleased?: () => void;
|
||||
}
|
||||
|
||||
class Keyboard {
|
||||
constructor(selector: string, options: KeyboardOptions);
|
||||
constructor(options: KeyboardOptions);
|
||||
|
||||
/**
|
||||
* Options
|
||||
*/
|
||||
options: KeyboardOptions;
|
||||
|
||||
/**
|
||||
@@ -174,6 +199,11 @@ declare module 'simple-keyboard' {
|
||||
*/
|
||||
utilities?: any;
|
||||
|
||||
/**
|
||||
* caretPosition
|
||||
*/
|
||||
caretPosition?: number;
|
||||
|
||||
/**
|
||||
* Adds/Modifies an entry to the `buttonTheme`. Basically a way to add a class to a button.
|
||||
* @param {string} buttons List of buttons to select (separated by a space).
|
||||
|
||||
@@ -78,10 +78,6 @@ html {
|
||||
font-size: inherit;
|
||||
}
|
||||
|
||||
.simple-keyboard.hg-theme-default:not(.hg-touch-events) .hg-button:active {
|
||||
background: #e4e4e4;
|
||||
}
|
||||
|
||||
.simple-keyboard.hg-theme-default.hg-layout-numeric .hg-button {
|
||||
width: 33.3%;
|
||||
height: 60px;
|
||||
|
||||
@@ -27,7 +27,16 @@ class SimpleKeyboard {
|
||||
/**
|
||||
* Initializing Utilities
|
||||
*/
|
||||
this.utilities = new Utilities(this);
|
||||
this.utilities = new Utilities({
|
||||
getOptions: this.getOptions,
|
||||
getCaretPosition: this.getCaretPosition,
|
||||
dispatch: this.dispatch
|
||||
});
|
||||
|
||||
/**
|
||||
* Caret position
|
||||
*/
|
||||
this.caretPosition = null;
|
||||
|
||||
/**
|
||||
* Processing options
|
||||
@@ -42,6 +51,7 @@ class SimpleKeyboard {
|
||||
* @property {boolean} mergeDisplay By default, when you set the display property, you replace the default one. This setting merges them instead.
|
||||
* @property {string} theme A prop to add your own css classes to the keyboard wrapper. You can add multiple classes separated by a space.
|
||||
* @property {Array} buttonTheme A prop to add your own css classes to one or several buttons.
|
||||
* @property {Array} buttonAttributes A prop to add your own attributes to one or several buttons.
|
||||
* @property {boolean} debug Runs a console.log every time a key is pressed. Displays the buttons pressed and the current input.
|
||||
* @property {boolean} newLineOnEnter Specifies whether clicking the “ENTER” button will input a newline (\n) or not.
|
||||
* @property {boolean} tabCharOnTab Specifies whether clicking the “TAB” button will input a tab character (\t) or not.
|
||||
@@ -66,6 +76,7 @@ class SimpleKeyboard {
|
||||
* @property {boolean} useMouseEvents Opt out of PointerEvents handling, falling back to the prior mouse event logic.
|
||||
* @property {function} destroy Clears keyboard listeners and DOM elements.
|
||||
* @property {boolean} disableButtonHold Disable button hold action.
|
||||
* @property {function} onKeyReleased Executes the callback function on key release.
|
||||
*/
|
||||
this.options = options;
|
||||
this.options.layoutName = this.options.layoutName || "default";
|
||||
@@ -112,16 +123,7 @@ class SimpleKeyboard {
|
||||
this.buttonElements = {};
|
||||
|
||||
/**
|
||||
* Rendering keyboard
|
||||
*/
|
||||
if (this.keyboardDOM) this.render();
|
||||
else {
|
||||
console.warn(`"${keyboardDOMQuery}" was not found in the DOM.`);
|
||||
throw new Error("KEYBOARD_DOM_ERROR");
|
||||
}
|
||||
|
||||
/**
|
||||
* Saving instance
|
||||
* Simple-keyboard Instances
|
||||
* This enables multiple simple-keyboard support with easier management
|
||||
*/
|
||||
if (!window["SimpleKeyboardInstances"])
|
||||
@@ -131,10 +133,31 @@ class SimpleKeyboard {
|
||||
this.utilities.camelCase(this.keyboardDOMClass)
|
||||
] = this;
|
||||
|
||||
/**
|
||||
* Instance vars
|
||||
*/
|
||||
this.allKeyboardInstances = window["SimpleKeyboardInstances"];
|
||||
this.currentInstanceName = this.utilities.camelCase(this.keyboardDOMClass);
|
||||
this.keyboardInstanceNames = Object.keys(window["SimpleKeyboardInstances"]);
|
||||
this.isFirstKeyboardInstance =
|
||||
this.keyboardInstanceNames[0] === this.currentInstanceName;
|
||||
|
||||
/**
|
||||
* Physical Keyboard support
|
||||
*/
|
||||
this.physicalKeyboardInterface = new PhysicalKeyboard(this);
|
||||
this.physicalKeyboard = new PhysicalKeyboard({
|
||||
dispatch: this.dispatch,
|
||||
getOptions: this.getOptions
|
||||
});
|
||||
|
||||
/**
|
||||
* Rendering keyboard
|
||||
*/
|
||||
if (this.keyboardDOM) this.render();
|
||||
else {
|
||||
console.warn(`"${keyboardDOMQuery}" was not found in the DOM.`);
|
||||
throw new Error("KEYBOARD_DOM_ERROR");
|
||||
}
|
||||
|
||||
/**
|
||||
* Modules
|
||||
@@ -143,6 +166,12 @@ class SimpleKeyboard {
|
||||
this.loadModules();
|
||||
}
|
||||
|
||||
/**
|
||||
* Getters
|
||||
*/
|
||||
getOptions = () => this.options;
|
||||
getCaretPosition = () => this.caretPosition;
|
||||
|
||||
/**
|
||||
* Handles clicks made to keyboard buttons
|
||||
* @param {string} button The button's layout name.
|
||||
@@ -167,7 +196,6 @@ class SimpleKeyboard {
|
||||
let updatedInput = this.utilities.getUpdatedInput(
|
||||
button,
|
||||
this.input[this.options.inputName],
|
||||
this.options,
|
||||
this.caretPosition
|
||||
);
|
||||
|
||||
@@ -185,7 +213,7 @@ class SimpleKeyboard {
|
||||
*/
|
||||
if (
|
||||
this.options.maxLength &&
|
||||
this.utilities.handleMaxLength(this.input, this.options, updatedInput)
|
||||
this.utilities.handleMaxLength(this.input, updatedInput)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
@@ -193,7 +221,6 @@ class SimpleKeyboard {
|
||||
this.input[this.options.inputName] = this.utilities.getUpdatedInput(
|
||||
button,
|
||||
this.input[this.options.inputName],
|
||||
this.options,
|
||||
this.caretPosition,
|
||||
true
|
||||
);
|
||||
@@ -269,9 +296,15 @@ class SimpleKeyboard {
|
||||
/**
|
||||
* Handles button mouseup
|
||||
*/
|
||||
handleButtonMouseUp() {
|
||||
handleButtonMouseUp(button) {
|
||||
this.isMouseHold = false;
|
||||
if (this.holdInteractionTimeout) clearTimeout(this.holdInteractionTimeout);
|
||||
|
||||
/**
|
||||
* Calling onKeyReleased
|
||||
*/
|
||||
if (button && typeof this.options.onKeyReleased === "function")
|
||||
this.options.onKeyReleased(button);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -584,33 +617,63 @@ class SimpleKeyboard {
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the current cursor position within a input or textarea (if any)
|
||||
* Handles simple-keyboard event listeners
|
||||
*/
|
||||
handleCaret() {
|
||||
setEventListeners() {
|
||||
/**
|
||||
* Only first instance should insall the caret handling events
|
||||
* Only first instance should set the event listeners
|
||||
*/
|
||||
this.caretPosition = null;
|
||||
let simpleKeyboardInstances = window["SimpleKeyboardInstances"];
|
||||
|
||||
if (
|
||||
(simpleKeyboardInstances &&
|
||||
Object.keys(simpleKeyboardInstances)[0] ===
|
||||
this.utilities.camelCase(this.keyboardDOMClass)) ||
|
||||
!simpleKeyboardInstances
|
||||
) {
|
||||
if (this.isFirstKeyboardInstance || !this.allKeyboardInstances) {
|
||||
if (this.options.debug) {
|
||||
console.log(`Caret handling started (${this.keyboardDOMClass})`);
|
||||
}
|
||||
|
||||
document.addEventListener("keyup", this.caretEventHandler);
|
||||
document.addEventListener("mouseup", this.caretEventHandler);
|
||||
document.addEventListener("touchend", this.caretEventHandler);
|
||||
/**
|
||||
* Event Listeners
|
||||
*/
|
||||
document.addEventListener("keyup", this.handleKeyUp);
|
||||
document.addEventListener("keydown", this.handleKeyDown);
|
||||
document.addEventListener("mouseup", this.handleMouseUp);
|
||||
document.addEventListener("touchend", this.handleTouchEnd);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by {@link handleCaret} when an event that warrants a cursor position update is triggered
|
||||
* Event Handler: KeyUp
|
||||
*/
|
||||
handleKeyUp(event) {
|
||||
this.caretEventHandler(event);
|
||||
|
||||
if (this.options.physicalKeyboardHighlight) {
|
||||
this.physicalKeyboard.handleHighlightKeyUp(event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Event Handler: KeyDown
|
||||
*/
|
||||
handleKeyDown(event) {
|
||||
if (this.options.physicalKeyboardHighlight) {
|
||||
this.physicalKeyboard.handleHighlightKeyDown(event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Event Handler: MouseUp
|
||||
*/
|
||||
handleMouseUp(event) {
|
||||
this.caretEventHandler(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Event Handler: TouchEnd
|
||||
*/
|
||||
handleTouchEnd(event) {
|
||||
this.caretEventHandler(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by {@link setEventListeners} when an event that warrants a cursor position update is triggered
|
||||
*/
|
||||
caretEventHandler(event) {
|
||||
let targetTagName;
|
||||
@@ -657,9 +720,10 @@ class SimpleKeyboard {
|
||||
/**
|
||||
* Remove listeners
|
||||
*/
|
||||
document.removeEventListener("keyup", this.caretEventHandler);
|
||||
document.removeEventListener("mouseup", this.caretEventHandler);
|
||||
document.removeEventListener("touchend", this.caretEventHandler);
|
||||
document.removeEventListener("keyup", this.handleKeyUp);
|
||||
document.removeEventListener("keydown", this.handleKeyDown);
|
||||
document.removeEventListener("mouseup", this.handleMouseUp);
|
||||
document.removeEventListener("touchend", this.handleTouchEnd);
|
||||
|
||||
/**
|
||||
* Clear DOM
|
||||
@@ -670,47 +734,62 @@ class SimpleKeyboard {
|
||||
/**
|
||||
* Process buttonTheme option
|
||||
*/
|
||||
getButtonTheme() {
|
||||
let buttonThemesParsed = {};
|
||||
getButtonThemeClasses(button) {
|
||||
let buttonTheme = this.options.buttonTheme;
|
||||
let buttonClasses = [];
|
||||
|
||||
this.options.buttonTheme.forEach(themeObj => {
|
||||
if (themeObj.buttons && themeObj.class) {
|
||||
let themeButtons;
|
||||
if (Array.isArray(buttonTheme)) {
|
||||
buttonTheme.forEach(themeObj => {
|
||||
if (
|
||||
themeObj.class &&
|
||||
typeof themeObj.class === "string" &&
|
||||
(themeObj.buttons && typeof themeObj.buttons === "string")
|
||||
) {
|
||||
let themeObjClasses = themeObj.class.split(" ");
|
||||
let themeObjButtons = themeObj.buttons.split(" ");
|
||||
|
||||
if (typeof themeObj.buttons === "string") {
|
||||
themeButtons = themeObj.buttons.split(" ");
|
||||
if (themeObjButtons.includes(button)) {
|
||||
buttonClasses = [...buttonClasses, ...themeObjClasses];
|
||||
}
|
||||
} else {
|
||||
console.warn(
|
||||
`Incorrect "buttonTheme". Please check the documentation.`,
|
||||
themeObj
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (themeButtons) {
|
||||
themeButtons.forEach(themeButton => {
|
||||
let themeParsed = buttonThemesParsed[themeButton];
|
||||
return buttonClasses;
|
||||
}
|
||||
|
||||
// If the button has already been added
|
||||
if (themeParsed) {
|
||||
// Making sure we don't add duplicate classes, even when buttonTheme has duplicates
|
||||
if (
|
||||
!this.utilities.countInArray(
|
||||
themeParsed.split(" "),
|
||||
themeObj.class
|
||||
)
|
||||
) {
|
||||
buttonThemesParsed[themeButton] = `${themeParsed} ${
|
||||
themeObj.class
|
||||
}`;
|
||||
}
|
||||
} else {
|
||||
buttonThemesParsed[themeButton] = themeObj.class;
|
||||
}
|
||||
});
|
||||
/**
|
||||
* Process buttonAttributes option
|
||||
*/
|
||||
setDOMButtonAttributes(button, callback) {
|
||||
let buttonAttributes = this.options.buttonAttributes;
|
||||
|
||||
if (Array.isArray(buttonAttributes)) {
|
||||
buttonAttributes.forEach(attrObj => {
|
||||
if (
|
||||
attrObj.attribute &&
|
||||
typeof attrObj.attribute === "string" &&
|
||||
(attrObj.value && typeof attrObj.value === "string") &&
|
||||
(attrObj.buttons && typeof attrObj.buttons === "string")
|
||||
) {
|
||||
let attrObjButtons = attrObj.buttons.split(" ");
|
||||
|
||||
if (attrObjButtons.includes(button)) {
|
||||
callback(attrObj.attribute, attrObj.value);
|
||||
}
|
||||
} else {
|
||||
console.warn(
|
||||
`Incorrect "buttonAttributes". Please check the documentation.`,
|
||||
attrObj
|
||||
);
|
||||
}
|
||||
} else {
|
||||
console.warn(
|
||||
`buttonTheme row is missing the "buttons" or the "class". Please check the documentation.`
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
return buttonThemesParsed;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onTouchDeviceDetected() {
|
||||
@@ -763,9 +842,9 @@ class SimpleKeyboard {
|
||||
}
|
||||
|
||||
/**
|
||||
* Caret handling
|
||||
* setEventListeners
|
||||
*/
|
||||
this.handleCaret();
|
||||
this.setEventListeners();
|
||||
|
||||
if (typeof this.options.onInit === "function") this.options.onInit();
|
||||
}
|
||||
@@ -788,6 +867,7 @@ class SimpleKeyboard {
|
||||
* Notify about PointerEvents usage
|
||||
*/
|
||||
if (
|
||||
this.isFirstKeyboardInstance &&
|
||||
this.utilities.pointerEventsSupported() &&
|
||||
!this.options.useTouchEvents &&
|
||||
!this.options.useMouseEvents
|
||||
@@ -922,9 +1002,7 @@ class SimpleKeyboard {
|
||||
*/
|
||||
let containerDOM = document.createElement("div");
|
||||
containerDOM.className += "hg-button-container";
|
||||
let containerUID = `${
|
||||
this.options.layoutName
|
||||
}-r${rowIndex}c${arrIndex}`;
|
||||
let containerUID = `${this.options.layoutName}-r${rowIndex}c${arrIndex}`;
|
||||
containerDOM.setAttribute("data-skUID", containerUID);
|
||||
|
||||
/**
|
||||
@@ -999,19 +1077,10 @@ class SimpleKeyboard {
|
||||
let useMouseEvents = this.options.useMouseEvents || false;
|
||||
let disableRowButtonContainers = this.options.disableRowButtonContainers;
|
||||
|
||||
/**
|
||||
* Account for buttonTheme, if set
|
||||
*/
|
||||
let buttonThemesParsed = Array.isArray(this.options.buttonTheme)
|
||||
? this.getButtonTheme()
|
||||
: {};
|
||||
|
||||
/**
|
||||
* Adding themeClass, layoutClass to keyboardDOM
|
||||
*/
|
||||
this.keyboardDOM.className += ` ${this.options.theme} ${layoutClass} ${
|
||||
this.keyboardPluginClasses
|
||||
} ${useTouchEventsClass}`;
|
||||
this.keyboardDOM.className += ` ${this.options.theme} ${layoutClass} ${this.keyboardPluginClasses} ${useTouchEventsClass}`;
|
||||
|
||||
/**
|
||||
* Iterating through each row
|
||||
@@ -1072,7 +1141,6 @@ class SimpleKeyboard {
|
||||
* Processing button options
|
||||
*/
|
||||
let fctBtnClass = this.utilities.getButtonClass(button);
|
||||
let buttonThemeClass = buttonThemesParsed[button];
|
||||
let buttonDisplayName = this.utilities.getButtonDisplayName(
|
||||
button,
|
||||
this.options.display,
|
||||
@@ -1084,9 +1152,19 @@ class SimpleKeyboard {
|
||||
*/
|
||||
let buttonType = this.options.useButtonTag ? "button" : "div";
|
||||
let buttonDOM = document.createElement(buttonType);
|
||||
buttonDOM.className += `hg-button ${fctBtnClass}${
|
||||
buttonThemeClass ? " " + buttonThemeClass : ""
|
||||
}`;
|
||||
buttonDOM.className += `hg-button ${fctBtnClass}`;
|
||||
|
||||
/**
|
||||
* Adding buttonTheme
|
||||
*/
|
||||
buttonDOM.classList.add(...this.getButtonThemeClasses(button));
|
||||
|
||||
/**
|
||||
* Adding buttonAttributes
|
||||
*/
|
||||
this.setDOMButtonAttributes(button, (attribute, value) => {
|
||||
buttonDOM.setAttribute(attribute, value);
|
||||
});
|
||||
|
||||
/**
|
||||
* Handle button click event
|
||||
@@ -1104,10 +1182,8 @@ class SimpleKeyboard {
|
||||
this.handleButtonClicked(button);
|
||||
this.handleButtonMouseDown(button, e);
|
||||
};
|
||||
buttonDOM.onpointerup = e => {
|
||||
this.handleButtonMouseUp();
|
||||
};
|
||||
buttonDOM.onpointercancel = e => this.handleButtonMouseUp();
|
||||
buttonDOM.onpointerup = () => this.handleButtonMouseUp(button);
|
||||
buttonDOM.onpointercancel = () => this.handleButtonMouseUp(button);
|
||||
} else {
|
||||
/**
|
||||
* Fallback for browsers not supporting PointerEvents
|
||||
@@ -1120,8 +1196,8 @@ class SimpleKeyboard {
|
||||
this.handleButtonClicked(button);
|
||||
this.handleButtonMouseDown(button, e);
|
||||
};
|
||||
buttonDOM.ontouchend = e => this.handleButtonMouseUp();
|
||||
buttonDOM.ontouchcancel = e => this.handleButtonMouseUp();
|
||||
buttonDOM.ontouchend = () => this.handleButtonMouseUp(button);
|
||||
buttonDOM.ontouchcancel = () => this.handleButtonMouseUp(button);
|
||||
} else {
|
||||
/**
|
||||
* Handle mouse events
|
||||
@@ -1130,9 +1206,8 @@ class SimpleKeyboard {
|
||||
this.isMouseHold = false;
|
||||
this.handleButtonClicked(button);
|
||||
};
|
||||
buttonDOM.onmousedown = e => {
|
||||
this.handleButtonMouseDown(button, e);
|
||||
};
|
||||
buttonDOM.onmousedown = e => this.handleButtonMouseDown(button, e);
|
||||
buttonDOM.onmouseup = () => this.handleButtonMouseUp(button);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1148,11 +1223,6 @@ class SimpleKeyboard {
|
||||
let buttonUID = `${this.options.layoutName}-r${rIndex}b${bIndex}`;
|
||||
buttonDOM.setAttribute("data-skBtnUID", buttonUID);
|
||||
|
||||
/**
|
||||
* Adding display label
|
||||
*/
|
||||
buttonDOM.setAttribute("data-displayLabel", buttonDisplayName);
|
||||
|
||||
/**
|
||||
* Adding button label to button
|
||||
*/
|
||||
@@ -1214,8 +1284,8 @@ class SimpleKeyboard {
|
||||
/**
|
||||
* Handling ontouchend, ontouchcancel
|
||||
*/
|
||||
document.ontouchend = e => this.handleButtonMouseUp();
|
||||
document.ontouchcancel = e => this.handleButtonMouseUp();
|
||||
document.ontouchend = () => this.handleButtonMouseUp();
|
||||
document.ontouchcancel = () => this.handleButtonMouseUp();
|
||||
} else if (!useTouchEvents) {
|
||||
/**
|
||||
* Handling mouseup
|
||||
|
||||
@@ -1242,4 +1242,87 @@ it('Keyboard disableButtonHold will work', () => {
|
||||
});
|
||||
|
||||
expect(keyboard.options.disableButtonHold).toBe(true);
|
||||
});
|
||||
|
||||
it('Keyboard caretEventHandler will be triggered on mouseup and ontouchend', () => {
|
||||
testUtil.setDOM();
|
||||
|
||||
let keyboard = new Keyboard({
|
||||
disableCaretPositioning: true
|
||||
});
|
||||
|
||||
keyboard.caretPosition = 6;
|
||||
|
||||
document.dispatchEvent(new MouseEvent('mouseup', {
|
||||
target: {
|
||||
tagName: "input"
|
||||
}
|
||||
}));
|
||||
|
||||
expect(keyboard.caretPosition).toBe(null);
|
||||
|
||||
keyboard.setOptions({
|
||||
disableCaretPositioning: false
|
||||
})
|
||||
|
||||
keyboard.caretPosition = 10;
|
||||
|
||||
document.dispatchEvent(new TouchEvent('touchend', {
|
||||
target: {
|
||||
tagName: "input"
|
||||
}
|
||||
}));
|
||||
|
||||
expect(keyboard.caretPosition).toBe(10);
|
||||
});
|
||||
|
||||
it('Keyboard onKeyReleased will work', () => {
|
||||
testUtil.setDOM();
|
||||
|
||||
let pressed = false;
|
||||
let firedTimes = 0;
|
||||
let buttonPressed;
|
||||
|
||||
let keyboard = new Keyboard({
|
||||
onKeyReleased: button => {
|
||||
pressed = true;
|
||||
buttonPressed = button;
|
||||
firedTimes++;
|
||||
},
|
||||
debug: true
|
||||
});
|
||||
|
||||
keyboard.getButtonElement("q").onpointerdown();
|
||||
keyboard.getButtonElement("q").onpointerup();
|
||||
|
||||
expect(pressed).toBeTruthy();
|
||||
expect(firedTimes).toBe(1);
|
||||
expect(buttonPressed).toBe("q");
|
||||
});
|
||||
|
||||
it('Keyboard buttonAttribute will work', () => {
|
||||
testUtil.setDOM();
|
||||
|
||||
let keyboard = new Keyboard({
|
||||
buttonAttributes: [
|
||||
{
|
||||
attribute: "aria-label",
|
||||
value: "bee",
|
||||
buttons: "b B"
|
||||
}
|
||||
]
|
||||
});
|
||||
});
|
||||
|
||||
it('Keyboard buttonAttribute will warn about invalid entries', () => {
|
||||
testUtil.setDOM();
|
||||
|
||||
let keyboard = new Keyboard({
|
||||
buttonAttributes: [
|
||||
{
|
||||
attribute: false,
|
||||
value: null
|
||||
}
|
||||
]
|
||||
});
|
||||
});
|
||||
@@ -1,3 +1,5 @@
|
||||
import Utilities from "../services/Utilities";
|
||||
|
||||
/**
|
||||
* Physical Keyboard Service
|
||||
*/
|
||||
@@ -5,66 +7,47 @@ class PhysicalKeyboard {
|
||||
/**
|
||||
* Creates an instance of the PhysicalKeyboard service
|
||||
*/
|
||||
constructor(simpleKeyboardInstance) {
|
||||
constructor({ dispatch, getOptions }) {
|
||||
/**
|
||||
* @type {object} A simple-keyboard instance
|
||||
*/
|
||||
this.simpleKeyboardInstance = simpleKeyboardInstance;
|
||||
this.dispatch = dispatch;
|
||||
this.getOptions = getOptions;
|
||||
|
||||
/**
|
||||
* Bindings
|
||||
*/
|
||||
this.initKeyboardListener = this.initKeyboardListener.bind(this);
|
||||
this.getSimpleKeyboardLayoutKey = this.getSimpleKeyboardLayoutKey.bind(
|
||||
this
|
||||
);
|
||||
|
||||
/**
|
||||
* Initialize key listeners
|
||||
*/
|
||||
this.initKeyboardListener();
|
||||
Utilities.bindMethods(PhysicalKeyboard, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes key event listeners
|
||||
*/
|
||||
initKeyboardListener() {
|
||||
// Adding button style on keydown
|
||||
document.addEventListener("keydown", event => {
|
||||
if (this.simpleKeyboardInstance.options.physicalKeyboardHighlight) {
|
||||
let buttonPressed = this.getSimpleKeyboardLayoutKey(event);
|
||||
handleHighlightKeyDown(event) {
|
||||
let options = this.getOptions();
|
||||
let buttonPressed = this.getSimpleKeyboardLayoutKey(event);
|
||||
|
||||
this.simpleKeyboardInstance.dispatch(instance => {
|
||||
let buttonDOM =
|
||||
instance.getButtonElement(buttonPressed) ||
|
||||
instance.getButtonElement(`{${buttonPressed}}`);
|
||||
this.dispatch(instance => {
|
||||
let buttonDOM =
|
||||
instance.getButtonElement(buttonPressed) ||
|
||||
instance.getButtonElement(`{${buttonPressed}}`);
|
||||
|
||||
if (buttonDOM) {
|
||||
buttonDOM.style.backgroundColor =
|
||||
this.simpleKeyboardInstance.options
|
||||
.physicalKeyboardHighlightBgColor || "#9ab4d0";
|
||||
buttonDOM.style.color =
|
||||
this.simpleKeyboardInstance.options
|
||||
.physicalKeyboardHighlightTextColor || "white";
|
||||
}
|
||||
});
|
||||
if (buttonDOM) {
|
||||
buttonDOM.style.backgroundColor =
|
||||
options.physicalKeyboardHighlightBgColor || "#9ab4d0";
|
||||
buttonDOM.style.color =
|
||||
options.physicalKeyboardHighlightTextColor || "white";
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Removing button style on keyup
|
||||
document.addEventListener("keyup", event => {
|
||||
if (this.simpleKeyboardInstance.options.physicalKeyboardHighlight) {
|
||||
let buttonPressed = this.getSimpleKeyboardLayoutKey(event);
|
||||
handleHighlightKeyUp(event) {
|
||||
let buttonPressed = this.getSimpleKeyboardLayoutKey(event);
|
||||
|
||||
this.simpleKeyboardInstance.dispatch(instance => {
|
||||
let buttonDOM =
|
||||
instance.getButtonElement(buttonPressed) ||
|
||||
instance.getButtonElement(`{${buttonPressed}}`);
|
||||
this.dispatch(instance => {
|
||||
let buttonDOM =
|
||||
instance.getButtonElement(buttonPressed) ||
|
||||
instance.getButtonElement(`{${buttonPressed}}`);
|
||||
|
||||
if (buttonDOM && buttonDOM.removeAttribute) {
|
||||
buttonDOM.removeAttribute("style");
|
||||
}
|
||||
});
|
||||
if (buttonDOM && buttonDOM.removeAttribute) {
|
||||
buttonDOM.removeAttribute("style");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -5,11 +5,10 @@ class Utilities {
|
||||
/**
|
||||
* Creates an instance of the Utility service
|
||||
*/
|
||||
constructor(simpleKeyboardInstance) {
|
||||
/**
|
||||
* @type {object} A simple-keyboard instance
|
||||
*/
|
||||
this.simpleKeyboardInstance = simpleKeyboardInstance;
|
||||
constructor({ getOptions, getCaretPosition, dispatch }) {
|
||||
this.getOptions = getOptions;
|
||||
this.getCaretPosition = getCaretPosition;
|
||||
this.dispatch = dispatch;
|
||||
|
||||
/**
|
||||
* Bindings
|
||||
@@ -125,11 +124,11 @@ class Utilities {
|
||||
*
|
||||
* @param {string} button The button's layout name
|
||||
* @param {string} input The input string
|
||||
* @param {object} options The simple-keyboard options object
|
||||
* @param {number} caretPos The cursor's current position
|
||||
* @param {boolean} moveCaret Whether to update simple-keyboard's cursor
|
||||
*/
|
||||
getUpdatedInput(button, input, options, caretPos, moveCaret) {
|
||||
getUpdatedInput(button, input, caretPos, moveCaret) {
|
||||
let options = this.getOptions();
|
||||
let output = input;
|
||||
|
||||
if (
|
||||
@@ -187,43 +186,34 @@ class Utilities {
|
||||
* @param {boolean} minus Whether the cursor should be moved to the left or not.
|
||||
*/
|
||||
updateCaretPos(length, minus) {
|
||||
let newCaretPos = this.updateCaretPosAction(
|
||||
this.simpleKeyboardInstance,
|
||||
length,
|
||||
minus
|
||||
);
|
||||
let newCaretPos = this.updateCaretPosAction(length, minus);
|
||||
|
||||
if (this.simpleKeyboardInstance.options.syncInstanceInputs) {
|
||||
this.simpleKeyboardInstance.dispatch(instance => {
|
||||
instance.caretPosition = newCaretPos;
|
||||
});
|
||||
}
|
||||
this.dispatch(instance => {
|
||||
instance.caretPosition = newCaretPos;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Action method of updateCaretPos
|
||||
*
|
||||
* @param {object} instance The instance whose position should be updated
|
||||
* @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(instance, length, minus) {
|
||||
updateCaretPosAction(length, minus) {
|
||||
let options = this.getOptions();
|
||||
let caretPosition = this.getCaretPosition();
|
||||
|
||||
if (minus) {
|
||||
if (instance.caretPosition > 0)
|
||||
instance.caretPosition = instance.caretPosition - length;
|
||||
if (caretPosition > 0) caretPosition = caretPosition - length;
|
||||
} else {
|
||||
instance.caretPosition = instance.caretPosition + length;
|
||||
caretPosition = caretPosition + length;
|
||||
}
|
||||
|
||||
if (this.simpleKeyboardInstance.options.debug) {
|
||||
console.log(
|
||||
"Caret at:",
|
||||
instance.caretPosition,
|
||||
`(${instance.keyboardDOMClass})`
|
||||
);
|
||||
if (options.debug) {
|
||||
console.log("Caret at:", caretPosition, `(${this.keyboardDOMClass})`);
|
||||
}
|
||||
|
||||
return instance.caretPosition;
|
||||
return caretPosition;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -263,7 +253,9 @@ class Utilities {
|
||||
* @param {boolean} moveCaret Whether to update simple-keyboard's cursor
|
||||
*/
|
||||
removeAt(source, position, moveCaret) {
|
||||
if (this.simpleKeyboardInstance.caretPosition === 0) {
|
||||
let caretPosition = this.getCaretPosition();
|
||||
|
||||
if (caretPosition === 0) {
|
||||
return source;
|
||||
}
|
||||
|
||||
@@ -306,10 +298,10 @@ class Utilities {
|
||||
* Determines whether the maxLength has been reached. This function is called when the maxLength option it set.
|
||||
*
|
||||
* @param {object} inputObj
|
||||
* @param {object} options
|
||||
* @param {string} updatedInput
|
||||
*/
|
||||
handleMaxLength(inputObj, options, updatedInput) {
|
||||
handleMaxLength(inputObj, updatedInput) {
|
||||
let options = this.getOptions();
|
||||
let maxLength = options.maxLength;
|
||||
let currentInput = inputObj[options.inputName];
|
||||
let condition = currentInput.length === maxLength;
|
||||
@@ -399,6 +391,8 @@ class Utilities {
|
||||
* @param {string} string The string to transform.
|
||||
*/
|
||||
camelCase(string) {
|
||||
if (!string) return false;
|
||||
|
||||
return string
|
||||
.toLowerCase()
|
||||
.trim()
|
||||
@@ -407,16 +401,6 @@ class Utilities {
|
||||
word.length ? string + word[0].toUpperCase() + word.slice(1) : string
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Counts the number of duplicates in a given array
|
||||
*
|
||||
* @param {Array} array The haystack to search in
|
||||
* @param {string} value The needle to search for
|
||||
*/
|
||||
countInArray(array, value) {
|
||||
return array.reduce((n, x) => n + (x === value), 0);
|
||||
}
|
||||
}
|
||||
|
||||
export default Utilities;
|
||||
|
||||
@@ -13,7 +13,7 @@ it('Keyboard mergeDisplay will work', () => {
|
||||
}
|
||||
});
|
||||
|
||||
expect(keyboard.getButtonElement("q").getAttribute("data-displaylabel")).toBe("qreplaced");
|
||||
expect(keyboard.getButtonElement("q").querySelector("span").innerHTML).toBe("qreplaced");
|
||||
});
|
||||
|
||||
it('Keyboard function buttons will work', () => {
|
||||
@@ -31,7 +31,7 @@ it('Keyboard {bksp} button will work', () => {
|
||||
|
||||
let keyboard = new Keyboard();
|
||||
|
||||
let output = keyboard.utilities.getUpdatedInput("{bksp}", "test", keyboard.options);
|
||||
let output = keyboard.utilities.getUpdatedInput("{bksp}", "test");
|
||||
|
||||
expect(output).toBe("tes");
|
||||
});
|
||||
@@ -41,7 +41,7 @@ it('Keyboard {space} button will work', () => {
|
||||
|
||||
let keyboard = new Keyboard();
|
||||
|
||||
let output = keyboard.utilities.getUpdatedInput("{space}", "test", keyboard.options);
|
||||
let output = keyboard.utilities.getUpdatedInput("{space}", "test");
|
||||
|
||||
expect(output).toBe("test ");
|
||||
});
|
||||
@@ -51,7 +51,7 @@ it('Keyboard {tab} button will work', () => {
|
||||
|
||||
let keyboard = new Keyboard();
|
||||
|
||||
let output = keyboard.utilities.getUpdatedInput("{tab}", "test", keyboard.options);
|
||||
let output = keyboard.utilities.getUpdatedInput("{tab}", "test");
|
||||
|
||||
expect(output).toBe("test\t");
|
||||
});
|
||||
@@ -63,7 +63,7 @@ it('Keyboard {tab} button will work with tabCharOnTab:false', () => {
|
||||
tabCharOnTab: false
|
||||
});
|
||||
|
||||
let output = keyboard.utilities.getUpdatedInput("{tab}", "test", keyboard.options);
|
||||
let output = keyboard.utilities.getUpdatedInput("{tab}", "test");
|
||||
|
||||
expect(output).toBe("test");
|
||||
});
|
||||
@@ -73,7 +73,7 @@ it('Keyboard {enter} button will work', () => {
|
||||
|
||||
let keyboard = new Keyboard();
|
||||
|
||||
let output = keyboard.utilities.getUpdatedInput("{enter}", "test", keyboard.options);
|
||||
let output = keyboard.utilities.getUpdatedInput("{enter}", "test");
|
||||
|
||||
expect(output).toBe("test");
|
||||
});
|
||||
@@ -85,7 +85,7 @@ it('Keyboard {enter} button will work with newLineOnEnter:true', () => {
|
||||
newLineOnEnter: true
|
||||
});
|
||||
|
||||
let output = keyboard.utilities.getUpdatedInput("{enter}", "test", keyboard.options);
|
||||
let output = keyboard.utilities.getUpdatedInput("{enter}", "test");
|
||||
|
||||
expect(output).toBe("test\n");
|
||||
});
|
||||
@@ -96,7 +96,7 @@ it('Keyboard {numpadX} buttons will work', () => {
|
||||
let keyboard = new Keyboard();
|
||||
|
||||
for(let i = 0;i<=9;i++){
|
||||
let output = keyboard.utilities.getUpdatedInput(`{numpad${i}}`, "test", keyboard.options);
|
||||
let output = keyboard.utilities.getUpdatedInput(`{numpad${i}}`, "test");
|
||||
expect(output).toBe(`test${i}`);
|
||||
}
|
||||
});
|
||||
@@ -106,7 +106,7 @@ it('Keyboard {numpaddivide} button will work', () => {
|
||||
|
||||
let keyboard = new Keyboard();
|
||||
|
||||
let output = keyboard.utilities.getUpdatedInput("{numpaddivide}", "test", keyboard.options);
|
||||
let output = keyboard.utilities.getUpdatedInput("{numpaddivide}", "test");
|
||||
|
||||
expect(output).toBe("test/");
|
||||
});
|
||||
@@ -116,7 +116,7 @@ it('Keyboard {numpadmultiply} button will work', () => {
|
||||
|
||||
let keyboard = new Keyboard();
|
||||
|
||||
let output = keyboard.utilities.getUpdatedInput("{numpadmultiply}", "test", keyboard.options);
|
||||
let output = keyboard.utilities.getUpdatedInput("{numpadmultiply}", "test");
|
||||
|
||||
expect(output).toBe("test*");
|
||||
});
|
||||
@@ -126,7 +126,7 @@ it('Keyboard {numpadsubtract} button will work', () => {
|
||||
|
||||
let keyboard = new Keyboard();
|
||||
|
||||
let output = keyboard.utilities.getUpdatedInput("{numpadsubtract}", "test", keyboard.options);
|
||||
let output = keyboard.utilities.getUpdatedInput("{numpadsubtract}", "test");
|
||||
|
||||
expect(output).toBe("test-");
|
||||
});
|
||||
@@ -136,7 +136,7 @@ it('Keyboard {numpadadd} button will work', () => {
|
||||
|
||||
let keyboard = new Keyboard();
|
||||
|
||||
let output = keyboard.utilities.getUpdatedInput("{numpadadd}", "test", keyboard.options);
|
||||
let output = keyboard.utilities.getUpdatedInput("{numpadadd}", "test");
|
||||
|
||||
expect(output).toBe("test+");
|
||||
});
|
||||
@@ -146,7 +146,7 @@ it('Keyboard {numpadadd} button will work', () => {
|
||||
|
||||
let keyboard = new Keyboard();
|
||||
|
||||
let output = keyboard.utilities.getUpdatedInput("{numpadadd}", "test", keyboard.options);
|
||||
let output = keyboard.utilities.getUpdatedInput("{numpadadd}", "test");
|
||||
|
||||
expect(output).toBe("test+");
|
||||
});
|
||||
@@ -156,7 +156,7 @@ it('Keyboard {numpaddecimal} button will work', () => {
|
||||
|
||||
let keyboard = new Keyboard();
|
||||
|
||||
let output = keyboard.utilities.getUpdatedInput("{numpaddecimal}", "test", keyboard.options);
|
||||
let output = keyboard.utilities.getUpdatedInput("{numpaddecimal}", "test");
|
||||
|
||||
expect(output).toBe("test.");
|
||||
});
|
||||
@@ -172,7 +172,7 @@ it('Keyboard custom function buttons will work', () => {
|
||||
}
|
||||
});
|
||||
|
||||
let output = keyboard.utilities.getUpdatedInput("{randombuttontest}", "test", keyboard.options);
|
||||
let output = keyboard.utilities.getUpdatedInput("{randombuttontest}", "test");
|
||||
|
||||
expect(output).toBe("test");
|
||||
expect(keyboard.getButtonElement("{randombuttontest}").onclick).toBeTruthy();
|
||||
@@ -183,7 +183,7 @@ it('Keyboard "{" button will work', () => {
|
||||
|
||||
let keyboard = new Keyboard();
|
||||
|
||||
let output = keyboard.utilities.getUpdatedInput("{", "test", keyboard.options);
|
||||
let output = keyboard.utilities.getUpdatedInput("{", "test");
|
||||
|
||||
expect(output).toBe("test{");
|
||||
});
|
||||
@@ -193,7 +193,7 @@ it('Keyboard "}" button will work', () => {
|
||||
|
||||
let keyboard = new Keyboard();
|
||||
|
||||
let output = keyboard.utilities.getUpdatedInput("}", "test", keyboard.options);
|
||||
let output = keyboard.utilities.getUpdatedInput("}", "test");
|
||||
|
||||
expect(output).toBe("test}");
|
||||
});
|
||||
@@ -205,7 +205,7 @@ it('Keyboard standard button will affect input', () => {
|
||||
|
||||
for (let i = 65; i <= 90; i++) {
|
||||
let char = String.fromCharCode(i);
|
||||
let output = keyboard.utilities.getUpdatedInput(char, "test", keyboard.options);
|
||||
let output = keyboard.utilities.getUpdatedInput(char, "test");
|
||||
expect(output).toBe(`test${char}`);
|
||||
}
|
||||
});
|
||||
@@ -213,8 +213,9 @@ it('Keyboard standard button will affect input', () => {
|
||||
it('Keyboard updateCaretPos will work with minus', () => {
|
||||
testUtil.setDOM();
|
||||
|
||||
let keyboard = new Keyboard();
|
||||
keyboard.options.syncInstanceInputs = true;
|
||||
let keyboard = new Keyboard({
|
||||
syncInstanceInputs: true
|
||||
});
|
||||
|
||||
keyboard.caretPosition = 5;
|
||||
keyboard.utilities.updateCaretPos(2, true);
|
||||
@@ -282,7 +283,7 @@ it('Keyboard addStringAt will respect maxLength', () => {
|
||||
keyboard.setInput("test");
|
||||
keyboard.caretPosition = 4;
|
||||
|
||||
keyboard.utilities.handleMaxLength(keyboard.input, keyboard.options, "testq")
|
||||
keyboard.utilities.handleMaxLength(keyboard.input, "testq")
|
||||
keyboard.utilities.addStringAt("test", "q", 4);
|
||||
|
||||
expect(keyboard.caretPosition).toBe(4);
|
||||
@@ -297,7 +298,7 @@ it('Keyboard handleMaxLength will exit out on same updatedInput', () => {
|
||||
|
||||
keyboard.setInput("test");
|
||||
|
||||
let output = keyboard.utilities.handleMaxLength(keyboard.input, keyboard.options, "test")
|
||||
let output = keyboard.utilities.handleMaxLength(keyboard.input, "test")
|
||||
|
||||
expect(output).toBeFalsy();
|
||||
});
|
||||
@@ -313,7 +314,7 @@ it('Keyboard handleMaxLength will work with object maxLength', () => {
|
||||
|
||||
keyboard.setInput("test");
|
||||
|
||||
let output = keyboard.utilities.handleMaxLength(keyboard.input, keyboard.options, "testq");
|
||||
let output = keyboard.utilities.handleMaxLength(keyboard.input, "testq");
|
||||
|
||||
expect(output).toBeTruthy();
|
||||
});
|
||||
@@ -330,7 +331,7 @@ it('Keyboard handleMaxLength will work with object maxLength and debug', () => {
|
||||
|
||||
keyboard.setInput("test");
|
||||
|
||||
let output = keyboard.utilities.handleMaxLength(keyboard.input, keyboard.options, "testq");
|
||||
let output = keyboard.utilities.handleMaxLength(keyboard.input, "testq");
|
||||
|
||||
expect(output).toBeTruthy();
|
||||
});
|
||||
@@ -346,7 +347,7 @@ it('Keyboard handleMaxLength will return false if obj maxLength not reached', ()
|
||||
|
||||
keyboard.setInput("test");
|
||||
|
||||
let output = keyboard.utilities.handleMaxLength(keyboard.input, keyboard.options, "testq");
|
||||
let output = keyboard.utilities.handleMaxLength(keyboard.input, "testq");
|
||||
|
||||
expect(output).toBeFalsy();
|
||||
});
|
||||
@@ -361,7 +362,7 @@ it('Keyboard handleMaxLength will work without debug', () => {
|
||||
|
||||
keyboard.setInput("test");
|
||||
|
||||
let output = keyboard.utilities.handleMaxLength(keyboard.input, keyboard.options, "testq");
|
||||
let output = keyboard.utilities.handleMaxLength(keyboard.input, "testq");
|
||||
|
||||
expect(output).toBeTruthy();
|
||||
});
|
||||
@@ -376,7 +377,7 @@ it('Keyboard handleMaxLength will work with numeric maxLength', () => {
|
||||
|
||||
keyboard.setInput("test");
|
||||
|
||||
let output = keyboard.utilities.handleMaxLength(keyboard.input, keyboard.options, "testq");
|
||||
let output = keyboard.utilities.handleMaxLength(keyboard.input, "testq");
|
||||
|
||||
expect(output).toBeFalsy();
|
||||
});
|
||||
@@ -390,7 +391,7 @@ it('Keyboard handleMaxLength wont work with non numeric or object maxLength', ()
|
||||
|
||||
keyboard.setInput("test");
|
||||
|
||||
let output = keyboard.utilities.handleMaxLength(keyboard.input, keyboard.options, "testq");
|
||||
let output = keyboard.utilities.handleMaxLength(keyboard.input, "testq");
|
||||
|
||||
expect(output).toBeFalsy();
|
||||
});
|
||||
@@ -405,7 +406,7 @@ it('Keyboard handleMaxLength wont work with non numeric or object maxLength (wit
|
||||
|
||||
keyboard.setInput("test");
|
||||
|
||||
let output = keyboard.utilities.handleMaxLength(keyboard.input, keyboard.options, "testq");
|
||||
let output = keyboard.utilities.handleMaxLength(keyboard.input, "testq");
|
||||
|
||||
expect(output).toBeFalsy();
|
||||
});
|
||||
@@ -483,4 +484,10 @@ it('Keyboard will work with custom (and weird) class', () => {
|
||||
testUtil.setDOM("my--weird--class");
|
||||
let keyboard = new Keyboard(".my--weird--class");
|
||||
expect(keyboard.keyboardDOMClass).toBe("my--weird--class");
|
||||
});
|
||||
|
||||
it('Keyboard camelCase will work with empty strings', () => {
|
||||
testUtil.setDOM();
|
||||
let keyboard = new Keyboard();
|
||||
expect(keyboard.utilities.camelCase()).toBeFalsy();
|
||||
});
|
||||
@@ -7,9 +7,14 @@ export default class TestUtility {
|
||||
*/
|
||||
setDOM = (divClass) => {
|
||||
this.clear();
|
||||
const div = document.createElement('div');
|
||||
div.className += divClass || "simple-keyboard";
|
||||
document.body.appendChild(div);
|
||||
const wrapperDOM = document.createElement('div');
|
||||
wrapperDOM.setAttribute("id", "root");
|
||||
|
||||
const keyboardDOM = document.createElement('div');
|
||||
keyboardDOM.className = divClass || "simple-keyboard";
|
||||
|
||||
wrapperDOM.appendChild(keyboardDOM);
|
||||
document.body.appendChild(wrapperDOM);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user