mirror of
https://github.com/hodgef/simple-keyboard.git
synced 2026-02-03 00:06:50 +08:00
Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0c76aef3fb | ||
|
|
d1eabe0167 | ||
|
|
988b8a0f64 | ||
|
|
a097b0f470 | ||
|
|
073a6d467e | ||
|
|
0087765e48 | ||
|
|
21072757cd | ||
|
|
89b0987a34 | ||
|
|
a7f25df1ac | ||
|
|
e94669ac06 | ||
|
|
7e09513534 | ||
|
|
960537d7a6 | ||
|
|
d88be11a42 | ||
|
|
e2ee5a4158 | ||
|
|
4b162eaec7 | ||
|
|
715a9c4a90 | ||
|
|
df32ed440e | ||
|
|
12fe2cca75 | ||
|
|
dddd45a220 | ||
|
|
857942811c |
74
README.md
74
README.md
@@ -2,13 +2,16 @@
|
||||
|
||||
[](https://www.npmjs.com/package/simple-keyboard)
|
||||
[](https://www.npmjs.com/package/simple-keyboard)
|
||||
[](https://www.npmjs.com/package/simple-keyboard)
|
||||
[](https://www.npmjs.com/package/simple-keyboard)
|
||||
|
||||
[](https://npmjs.com/package/simple-keyboard)
|
||||
|
||||
<a href="https://franciscohodge.com/projects/simple-keyboard/"><img src="src/demo/images/simple-keyboard.png" align="center"></a>
|
||||
> An easily customisable and responsive on-screen virtual keyboard for React.js projects.
|
||||
|
||||
<img src="src/demo/images/keyboard.PNG" align="center" width="100%">
|
||||
|
||||
<b>[Live Demo](https://franciscohodge.com/simple-keyboard/demo)</b>
|
||||
|
||||
|
||||
## Installation
|
||||
|
||||
`npm install simple-keyboard --save`
|
||||
@@ -117,9 +120,72 @@ theme={"hg-theme-default"}
|
||||
debug={false}
|
||||
```
|
||||
|
||||
### newLineOnEnter
|
||||
|
||||
> Specifies whether clicking the "ENTER" button will input a newline (`\n`) or not.
|
||||
|
||||
```js
|
||||
newLineOnEnter={false}
|
||||
```
|
||||
|
||||
## Methods
|
||||
|
||||
simple-keybord has a few methods you can use to further control it's behavior.
|
||||
To access these functions, you need a `ref` of the simple-keyboard component, like so:
|
||||
|
||||
```js
|
||||
<Keyboard
|
||||
ref={r => this.keyboard = r}
|
||||
[...]
|
||||
/>
|
||||
|
||||
// Then, on componentDidMount ..
|
||||
this.keyboard.methodName(params);
|
||||
```
|
||||
|
||||
### clearInput
|
||||
|
||||
> Clear the keyboard's input.
|
||||
|
||||
```js
|
||||
this.keyboard.clearInput();
|
||||
```
|
||||
|
||||
### getInput
|
||||
|
||||
> Get the keyboard's input (You can also get it from the _onChange_ prop).
|
||||
|
||||
```js
|
||||
let input = this.keyboard.getInput();
|
||||
```
|
||||
|
||||
### setInput
|
||||
|
||||
> Set the keyboard's input. Useful if you want the keybord to initialize with a default value, for example.
|
||||
|
||||
```js
|
||||
this.keyboard.setInput("Hello World!");
|
||||
```
|
||||
|
||||
It returns a promise, so if you want to run something after it's applied, call it as so:
|
||||
|
||||
```js
|
||||
let inputSetPromise = this.keyboard.setInput("Hello World!");
|
||||
|
||||
inputSetPromise.then((result) => {
|
||||
console.log("Input set");
|
||||
});
|
||||
```
|
||||
|
||||
## Demo
|
||||
|
||||
To run demo on your own computer:
|
||||
<img src="src/demo/images/demo.gif" align="center" width="600">
|
||||
|
||||
### Live demo
|
||||
|
||||
[https://franciscohodge.com/simple-keyboard/demo](https://franciscohodge.com/simple-keyboard/demo)
|
||||
|
||||
### To run demo on your own computer
|
||||
|
||||
* Clone this repository
|
||||
* `npm install`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "simple-keyboard",
|
||||
"version": "1.1.0",
|
||||
"version": "1.1.9",
|
||||
"description": "React.js Virtual Keyboard",
|
||||
"main": "build/index.js",
|
||||
"scripts": {
|
||||
@@ -18,7 +18,7 @@
|
||||
"bugs": {
|
||||
"url": "https://github.com/hodgef/simple-keyboard/issues"
|
||||
},
|
||||
"homepage": "https://github.com/hodgef/simple-keyboard",
|
||||
"homepage": "https://franciscohodge.com/simple-keyboard",
|
||||
"keywords": [
|
||||
"react",
|
||||
"reactjs",
|
||||
@@ -27,8 +27,9 @@
|
||||
"onscreen",
|
||||
"virtual",
|
||||
"component",
|
||||
"simple",
|
||||
"easy"
|
||||
"virtual-keyboard",
|
||||
"touchscreen",
|
||||
"touch-screen"
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {},
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 1.1 KiB |
@@ -19,7 +19,7 @@
|
||||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<title>React App</title>
|
||||
<title>simple-keyboard</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
|
||||
31
src/demo/App.css
Normal file
31
src/demo/App.css
Normal file
@@ -0,0 +1,31 @@
|
||||
.demoPage {
|
||||
font-family: "HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif;
|
||||
max-width: 1000px;
|
||||
margin: 0 auto;
|
||||
padding-top: 20px;
|
||||
}
|
||||
|
||||
.demoPage .screenContainer {
|
||||
background: rgba(0,0,0,0.8);
|
||||
border: 20px solid;
|
||||
height: 300px;
|
||||
border-top-right-radius: 5px;
|
||||
border-top-left-radius: 5px;
|
||||
padding: 10px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.demoPage .inputContainer {
|
||||
color: white;
|
||||
background: transparent;
|
||||
border: none;
|
||||
outline: none;
|
||||
font-family: monospace;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.simple-keyboard.hg-layout-custom {
|
||||
border-top-left-radius: 0px;
|
||||
border-top-right-radius: 0px;
|
||||
}
|
||||
@@ -1,23 +1,60 @@
|
||||
import React, {Component} from 'react';
|
||||
import Keyboard from '../lib';
|
||||
import './App.css';
|
||||
|
||||
class App extends Component {
|
||||
state = {
|
||||
input: '',
|
||||
layoutName: "default"
|
||||
}
|
||||
|
||||
componentDidMount(){
|
||||
this.keyboard.setInput("Hello World!")
|
||||
.then(input => {
|
||||
this.setState({input: input});
|
||||
});
|
||||
}
|
||||
|
||||
onChange = (input) => {
|
||||
console.log("Input changed", input);
|
||||
this.setState({
|
||||
input: input
|
||||
}, () => {
|
||||
console.log("Input changed", input);
|
||||
});
|
||||
}
|
||||
|
||||
onKeyPress = (button) => {
|
||||
console.log("Button pressed", button);
|
||||
|
||||
/**
|
||||
* Shift functionality
|
||||
*/
|
||||
if(button === "{lock}" || button === "{shift}")
|
||||
this.handleShiftButton();
|
||||
|
||||
}
|
||||
|
||||
handleShiftButton = () => {
|
||||
let layoutName = this.state.layoutName;
|
||||
let shiftToggle = layoutName === "default" ? "shift" : "default";
|
||||
|
||||
this.setState({
|
||||
layoutName: shiftToggle
|
||||
});
|
||||
}
|
||||
|
||||
render(){
|
||||
return (
|
||||
<div>
|
||||
<div className={"demoPage"}>
|
||||
<div className={"screenContainer"}>
|
||||
<textarea className={"inputContainer"} value={this.state.input} />
|
||||
</div>
|
||||
<Keyboard
|
||||
ref={r => this.keyboard = r}
|
||||
onChange={input => this.onChange(input)}
|
||||
onKeyPress={button => this.onKeyPress(button)}
|
||||
layoutName={"default"}
|
||||
layoutName={this.state.layoutName}
|
||||
newLineOnEnter={true}
|
||||
layout={{
|
||||
'default': [
|
||||
'` 1 2 3 4 5 6 7 8 9 0 - = {bksp}',
|
||||
|
||||
BIN
src/demo/images/demo.gif
Normal file
BIN
src/demo/images/demo.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.3 MiB |
BIN
src/demo/images/keyboard.PNG
Normal file
BIN
src/demo/images/keyboard.PNG
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
BIN
src/demo/images/simple-keyboard.png
Normal file
BIN
src/demo/images/simple-keyboard.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.7 KiB |
@@ -1,7 +1,5 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import App from './App';
|
||||
import registerServiceWorker from './registerServiceWorker';
|
||||
|
||||
ReactDOM.render(<App />, document.getElementById('root'));
|
||||
registerServiceWorker();
|
||||
|
||||
@@ -1,108 +0,0 @@
|
||||
// In production, we register a service worker to serve assets from local cache.
|
||||
|
||||
// This lets the app load faster on subsequent visits in production, and gives
|
||||
// it offline capabilities. However, it also means that developers (and users)
|
||||
// will only see deployed updates on the "N+1" visit to a page, since previously
|
||||
// cached resources are updated in the background.
|
||||
|
||||
// To learn more about the benefits of this model, read https://goo.gl/KwvDNy.
|
||||
// This link also includes instructions on opting out of this behavior.
|
||||
|
||||
const isLocalhost = Boolean(
|
||||
window.location.hostname === 'localhost' ||
|
||||
// [::1] is the IPv6 localhost address.
|
||||
window.location.hostname === '[::1]' ||
|
||||
// 127.0.0.1/8 is considered localhost for IPv4.
|
||||
window.location.hostname.match(
|
||||
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
|
||||
)
|
||||
);
|
||||
|
||||
export default function register() {
|
||||
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
|
||||
// The URL constructor is available in all browsers that support SW.
|
||||
const publicUrl = new URL(process.env.PUBLIC_URL, window.location);
|
||||
if (publicUrl.origin !== window.location.origin) {
|
||||
// Our service worker won't work if PUBLIC_URL is on a different origin
|
||||
// from what our page is served on. This might happen if a CDN is used to
|
||||
// serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374
|
||||
return;
|
||||
}
|
||||
|
||||
window.addEventListener('load', () => {
|
||||
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
|
||||
|
||||
if (isLocalhost) {
|
||||
// This is running on localhost. Lets check if a service worker still exists or not.
|
||||
checkValidServiceWorker(swUrl);
|
||||
} else {
|
||||
// Is not local host. Just register service worker
|
||||
registerValidSW(swUrl);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function registerValidSW(swUrl) {
|
||||
navigator.serviceWorker
|
||||
.register(swUrl)
|
||||
.then(registration => {
|
||||
registration.onupdatefound = () => {
|
||||
const installingWorker = registration.installing;
|
||||
installingWorker.onstatechange = () => {
|
||||
if (installingWorker.state === 'installed') {
|
||||
if (navigator.serviceWorker.controller) {
|
||||
// At this point, the old content will have been purged and
|
||||
// the fresh content will have been added to the cache.
|
||||
// It's the perfect time to display a "New content is
|
||||
// available; please refresh." message in your web app.
|
||||
console.log('New content is available; please refresh.');
|
||||
} else {
|
||||
// At this point, everything has been precached.
|
||||
// It's the perfect time to display a
|
||||
// "Content is cached for offline use." message.
|
||||
console.log('Content is cached for offline use.');
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error during service worker registration:', error);
|
||||
});
|
||||
}
|
||||
|
||||
function checkValidServiceWorker(swUrl) {
|
||||
// Check if the service worker can be found. If it can't reload the page.
|
||||
fetch(swUrl)
|
||||
.then(response => {
|
||||
// Ensure service worker exists, and that we really are getting a JS file.
|
||||
if (
|
||||
response.status === 404 ||
|
||||
response.headers.get('content-type').indexOf('javascript') === -1
|
||||
) {
|
||||
// No service worker found. Probably a different app. Reload the page.
|
||||
navigator.serviceWorker.ready.then(registration => {
|
||||
registration.unregister().then(() => {
|
||||
window.location.reload();
|
||||
});
|
||||
});
|
||||
} else {
|
||||
// Service worker found. Proceed as normal.
|
||||
registerValidSW(swUrl);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
console.log(
|
||||
'No internet connection found. App is running in offline mode.'
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export function unregister() {
|
||||
if ('serviceWorker' in navigator) {
|
||||
navigator.serviceWorker.ready.then(registration => {
|
||||
registration.unregister();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -36,23 +36,28 @@ body, html {
|
||||
/**
|
||||
* hg-theme-default theme
|
||||
*/
|
||||
.simple-keyboard.hg-theme-default {
|
||||
background-color: rgba(0,0,0,0.2);
|
||||
padding: 5px;
|
||||
border-radius: 5px;
|
||||
.simple-keyboard.hg-theme-default {
|
||||
background-color: rgba(0,0,0,0.2);
|
||||
padding: 5px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.simple-keyboard.hg-theme-default .hg-button {
|
||||
box-shadow: 0px 0px 51px -2px rgba(0,0,0,0.3);
|
||||
.simple-keyboard.hg-theme-default .hg-button {
|
||||
box-shadow: 0px 0px 3px -1px rgba(0,0,0,0.3);
|
||||
height: 40px;
|
||||
border: 1px solid rgba(0,0,0,0.25);
|
||||
border-radius: 5px;
|
||||
box-sizing: border-box;
|
||||
padding: 5px;
|
||||
background: white;
|
||||
border-bottom: 1px solid gray;
|
||||
}
|
||||
|
||||
.simple-keyboard.hg-theme-default.hg-layout-numeric .hg-button {
|
||||
.simple-keyboard.hg-theme-default .hg-button:active {
|
||||
background: #e4e4e4;
|
||||
}
|
||||
|
||||
.simple-keyboard.hg-theme-default.hg-layout-numeric .hg-button {
|
||||
width: 33.3%;
|
||||
height: 60px;
|
||||
align-items: center;
|
||||
|
||||
@@ -23,6 +23,29 @@ class App extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
clearInput = () => {
|
||||
this.setState({
|
||||
input: ''
|
||||
});
|
||||
}
|
||||
|
||||
getInput = () => {
|
||||
return this.state.input;
|
||||
}
|
||||
|
||||
setInput = input => {
|
||||
return new Promise(resolve => {
|
||||
this.setState({
|
||||
input: input
|
||||
}, () => {
|
||||
resolve(input);
|
||||
});
|
||||
})
|
||||
.catch(reason => {
|
||||
console.warn(reason);
|
||||
});
|
||||
}
|
||||
|
||||
handleButtonClicked = (button) => {
|
||||
let debug = this.props.debug;
|
||||
|
||||
@@ -41,7 +64,11 @@ class App extends Component {
|
||||
/**
|
||||
* Updating input
|
||||
*/
|
||||
let updatedInput = Utilities.getUpdatedInput(button, this.state.input);
|
||||
let options = {
|
||||
newLineOnEnter: (this.props.newLineOnEnter === true)
|
||||
}
|
||||
|
||||
let updatedInput = Utilities.getUpdatedInput(button, this.state.input, options);
|
||||
|
||||
if(this.state.input !== updatedInput){
|
||||
this.setState({
|
||||
|
||||
@@ -20,6 +20,8 @@ class Utilities {
|
||||
output = 'dollarsign';
|
||||
else if(string === "=")
|
||||
output = 'equals';
|
||||
else if(string === "+")
|
||||
output = 'plus';
|
||||
else if(string === "-")
|
||||
output = 'minus';
|
||||
else if(string === "'")
|
||||
@@ -68,13 +70,18 @@ class Utilities {
|
||||
return display[button] || button;
|
||||
}
|
||||
|
||||
static getUpdatedInput = (button, input) => {
|
||||
static getUpdatedInput = (button, input, options) => {
|
||||
let output = input;
|
||||
let newLineOnEnter = options.newLineOnEnter;
|
||||
|
||||
if(button === "{bksp}" && output.length > 0)
|
||||
output = output.slice(0, -1);
|
||||
else if(button === "{space}")
|
||||
output = output + ' ';
|
||||
else if(button === "{tab}")
|
||||
output = output + "\t";
|
||||
else if(button === "{enter}" && newLineOnEnter)
|
||||
output = output + "\n";
|
||||
else if(!button.includes("{") && !button.includes("{"))
|
||||
output = output + button;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user