├── .babelrc ├── .editorconfig ├── .eslintrc ├── .gitignore ├── .npmignore ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── examples ├── bundle.js ├── index.html ├── index.js └── webpack.config.js ├── keyboard.png ├── lib ├── Keyboard.css ├── Keyboard.css.map ├── Keyboard.js ├── Keyboard.scss ├── KeyboardButton.js ├── KeyboardedInput.js ├── icons │ ├── BackspaceIcon.js │ ├── DraggableIcon.js │ ├── LanguageIcon.js │ └── ShiftIcon.js ├── index.js └── layouts │ ├── CyrillicLayout.js │ ├── FrenchLayout.js │ ├── GermanLayout.js │ ├── LatinLayout.js │ └── SymbolsLayout.js ├── package.json └── src ├── Keyboard.js ├── Keyboard.scss ├── KeyboardButton.js ├── KeyboardedInput.js ├── icons ├── BackspaceIcon.js ├── DraggableIcon.js ├── LanguageIcon.js └── ShiftIcon.js ├── index.js └── layouts ├── CyrillicLayout.js ├── FrenchLayout.js ├── GermanLayout.js ├── LatinLayout.js └── SymbolsLayout.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "stage-2", "react"], 3 | "plugins": [ 4 | "add-module-exports" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain 2 | # consistent coding styles between different editors and IDEs. 3 | 4 | root = true 5 | 6 | [*] 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | indent_style = space 12 | indent_size = 2 13 | 14 | [*.md] 15 | trim_trailing_whitespace = false 16 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "extends": "airbnb", 4 | "env": { 5 | "browser": true 6 | }, 7 | "rules": { 8 | "import/extensions": [ 9 | "error", 10 | "always", 11 | { 12 | "js": "never", 13 | "jsx": "never", 14 | "mjs": "never" 15 | } 16 | ], 17 | "import/no-extraneous-dependencies": "off", 18 | "no-console": [ 19 | "error", 20 | { 21 | "allow": [ 22 | "warn", 23 | "error", 24 | "info" 25 | ] 26 | } 27 | ], 28 | "import/no-unresolved": "off", 29 | "react/jsx-filename-extension": "off", 30 | "react/prefer-stateless-function": "off", 31 | "no-useless-constructor": "off", 32 | "react/forbid-prop-types": "off" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Build files 30 | dist 31 | 32 | # Dependency directories 33 | node_modules 34 | jspm_packages 35 | 36 | # Optional npm cache directory 37 | .npm 38 | 39 | # Optional REPL history 40 | .node_repl_history 41 | 42 | # Editors 43 | .idea 44 | 45 | package-lock.json 46 | yarn.lock -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Thanks for your interest in react-touch-screen-keyboard. All forms of contribution are 4 | welcome, from issue reports to PRs and documentation / write-ups. 5 | 6 | Before you open a PR: 7 | 8 | * In development, run `npm start` to build (+watch) the project source, and run 9 | the [development server](http://localhost:8080). 10 | * Please ensure all the examples work correctly after your change. If you're 11 | adding a major new use-case, add a new example demonstrating its use. 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 WiaczeslawP 4 | Copyright (c) 2017 xTrinch 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![npm version](https://badge.fury.io/js/react-touch-screen-keyboard.svg)](https://badge.fury.io/js/react-touch-screen-keyboard) 2 | [![npm](https://img.shields.io/npm/dt/react-touch-screen-keyboard.svg)]() 3 | # react-touch-screen-keyboard 4 | 5 | ![alt tag](https://raw.githubusercontent.com/xTrinch/react-touch-screen-keyboard/master/keyboard.png) 6 | 7 | ## Quick start 8 | 9 | This library will render a draggable virtual keyboard on the bottom of the screen when the input is selected. The usage is very simple - instead of using the input tag, use the KeyboardedInput tag from the library. You can control the input's value via the callback function you give to its props. 10 | 11 | Currently supported keyboards: us, de, ru, fr 12 | 13 | **Installing via npm** 14 | 15 | ``` 16 | npm install react-touch-screen-keyboard 17 | ``` 18 | 19 | **Installing via npm (React v15)** 20 | 21 | ``` 22 | npm install react-touch-screen-keyboard@0.3.15 23 | ``` 24 | 25 | ### Example 26 | 27 | ```js 28 | 29 | import React from 'react'; 30 | import KeyboardedInput from 'react-touch-screen-keyboard'; 31 | import 'react-touch-screen-keyboard/lib/Keyboard.css'; // if you just want css 32 | import 'react-touch-screen-keyboard/lib/Keyboard.scss'; // if you've got sass-loader 33 | 34 | class Input extends React.Component { 35 | render() { 36 | return ( 37 | 60 | ); 61 | } 62 | } 63 | export default Input; 64 | 65 | ``` 66 | 67 | #### Use Custom Keyboard 68 | 69 | You can pass a Nx3 sized array into `defaultKeyboard` prop to render a customize layout. 70 | 71 | You can place functional keys using the following placeholders: 72 | 73 | | Placeholder | Key | 74 | |---|---| 75 | | *sh | Shift | 76 | | *bs | Backspace | 77 | 78 | ```js 79 | 80 | import React from 'react'; 81 | import KeyboardedInput from 'react-touch-screen-keyboard'; 82 | import 'react-touch-screen-keyboard/lib/Keyboard.css'; 83 | 84 | class Input extends React.Component { 85 | render() { 86 | const CustomMapping = [ 87 | ['q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p'], 88 | ['a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', '@'], 89 | ['z', 'x', 'c', 'v', 'b', 'n', 'm', '.com'] 90 | ]; 91 | 92 | return ( 93 | 100 | ); 101 | } 102 | } 103 | export default Input; 104 | 105 | ``` 106 | 107 | #### Managing focus programmatically 108 | 109 | The `focus()` function can be used to set the focus on the input field and show the keyboard. You can [access the component via its ref](https://reactjs.org/docs/refs-and-the-dom.html). 110 | 111 | For example, to focus the input field when clicking on a button, first set the ref: 112 | 113 | ```jsx 114 | { this.myInput = ref; }} 116 | ... 117 | ``` 118 | 119 | Then call the `focus()` function in the click handler of a button: 120 | 121 | ```jsx 122 | 125 | 126 | ``` 127 | 128 | See the [examples](examples/index.js) for a more complete sample. 129 | 130 | #### Running Local Example 131 | 132 | # NPM 133 | $ npm run-script start 134 | 135 | # Yarn 136 | $ yarn start 137 | -------------------------------------------------------------------------------- /examples/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Custom React Module Demo 5 | 16 | 17 | 18 |
Custom React Module Demo…
19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /examples/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDom from 'react-dom'; 3 | import KeyboardedInput from '../src'; 4 | import '../src/Keyboard.scss'; 5 | 6 | export default class MainComponent extends React.Component { 7 | constructor(props) { 8 | super(props); 9 | this.state = { 10 | value: '', 11 | value1: '', 12 | value2: '', 13 | value3: '', 14 | value4: '', 15 | value5: '', 16 | value6: '', 17 | value7: '', 18 | value8: '', 19 | value9: '', 20 | value10: '', 21 | value11: '', 22 | customMapping: [ 23 | ['q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p'], 24 | ['a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', '@', '*sh'], 25 | ['z', 'x', 'c', 'v', 'b', 'n', 'm', '.com', '*bs'], 26 | ], 27 | }; 28 | this.handleValueChange = this.handleValueChange.bind(this); 29 | this.handleValue1Change = this.handleValue1Change.bind(this); 30 | this.handleValue2Change = this.handleValue2Change.bind(this); 31 | this.handleValue3Change = this.handleValue3Change.bind(this); 32 | this.handleValue4Change = this.handleValue4Change.bind(this); 33 | this.handleValue5Change = this.handleValue5Change.bind(this); 34 | this.handleValue6Change = this.handleValue6Change.bind(this); 35 | this.handleValue7Change = this.handleValue7Change.bind(this); 36 | this.handleValue8Change = this.handleValue8Change.bind(this); 37 | this.handleValue9Change = this.handleValue9Change.bind(this); 38 | this.handleValue10Change = this.handleValue10Change.bind(this); 39 | this.handleFocusButtonClicked = this.handleFocusButtonClicked.bind(this); 40 | this.handleOnFocus = this.handleOnFocus.bind(this); 41 | 42 | this.input7ref = null; 43 | } 44 | 45 | handleValueChange(val) { 46 | this.setState({ value: val }); 47 | } 48 | 49 | handleValue1Change(val) { 50 | this.setState({ value1: val }); 51 | } 52 | 53 | handleValue2Change(val) { 54 | this.setState({ value2: val }); 55 | } 56 | 57 | handleValue3Change(val) { 58 | this.setState({ value3: val }); 59 | } 60 | 61 | handleValue4Change(val) { 62 | this.setState({ value4: val }); 63 | } 64 | 65 | handleValue5Change(val) { 66 | this.setState({ value5: val }); 67 | } 68 | 69 | handleValue6Change(val) { 70 | this.setState({ value6: val }); 71 | } 72 | 73 | handleValue7Change(val) { 74 | this.setState({ value7: val }); 75 | } 76 | 77 | handleValue8Change(val) { 78 | this.setState({ value8: val }); 79 | } 80 | 81 | handleValue9Change(val) { 82 | this.setState({ value9: val }); 83 | } 84 | 85 | handleValue10Change(val) { 86 | this.setState({ value10: val }); 87 | } 88 | 89 | handleValue11Change = (val) => { 90 | this.setState(_ => ({ value11: val })); 91 | }; 92 | handleFocusButtonClicked() { 93 | this.input7ref.focus(); 94 | } 95 | 96 | handleOnFocus() { 97 | this.handleValue10Change('default value'); 98 | } 99 | 100 | render() { 101 | return ( 102 |
103 |

React Touch Screen Keyboard Examples

104 | 105 |

Standard

106 | { 109 | this.handleValueChange(value); 110 | }} 111 | opacity={0.8} 112 | placeholder={'testme'} 113 | required 114 | enabled 115 | /> 116 |
117 | 118 |

Disable Dragging

119 | { 122 | this.handleValue11Change(value); 123 | }} 124 | enabled 125 | isDraggable={false} 126 | /> 127 |
128 | 129 |

Disable Submit

130 | { 133 | this.handleValue1Change(value); 134 | }} 135 | enabled 136 | showSubmit={false} 137 | /> 138 |
139 | 140 |

Disable Uppercase

141 | { 144 | this.handleValue2Change(value); 145 | }} 146 | enabled 147 | isFirstLetterUppercase={false} 148 | /> 149 |
150 | 151 |

Custom Mapping

152 | { 155 | this.handleValue3Change(value); 156 | }} 157 | defaultKeyboard={this.state.customMapping} 158 | enabled 159 | /> 160 |
161 | 162 |

Field Type (Email)

163 | { 167 | this.handleValue4Change(value); 168 | }} 169 | enabled 170 | keyboardClassName="testme" 171 | /> 172 |
173 | 174 |

Uppercase After Space (name entry)

175 | { 178 | this.handleValue5Change(value); 179 | }} 180 | enabled 181 | uppercaseAfterSpace 182 | /> 183 |
184 | 185 |

Custom container className

186 | Can create inline inputs 187 | { 190 | this.handleValue6Change(value); 191 | }} 192 | enabled 193 | uppercaseAfterSpace 194 | containerClassName={'custom-class'} 195 | /> 196 | for more flexibility building forms 197 |
198 | 199 |

Programmatically Focus Input

200 | { 202 | this.input7ref = ref; 203 | }} 204 | value={this.state.value7} 205 | onChange={(value) => { 206 | this.handleValue7Change(value); 207 | }} 208 | enabled 209 | /> 210 | 211 |
212 | 213 |

Hidden numeric row, hidden shift, hidden symbols, hidden spacebar

214 | { 217 | this.handleValue8Change(value); 218 | }} 219 | enabled 220 | showNumericRow={false} 221 | showShift={false} 222 | showSymbols={false} 223 | showSpacebar={false} 224 | /> 225 |
226 | 227 |

Callback (onBlur)

228 | { 231 | this.handleValue9Change(value); 232 | }} 233 | onBlur={(value) => { 234 | alert(`Triggered Blur: ${value}`); 235 | }} 236 | enabled 237 | /> 238 |
239 | 240 |

Callback (onFocus)

241 | { 244 | this.handleValue10Change(value); 245 | }} 246 | onFocus={this.handleOnFocus} 247 | enabled 248 | /> 249 |
250 |
251 | ); 252 | } 253 | } 254 | 255 | ReactDom.render(, document.getElementById('app')); 256 | -------------------------------------------------------------------------------- /examples/webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var webpack = require('webpack'); 3 | var ExtractTextPlugin = require('extract-text-webpack-plugin'); 4 | 5 | module.exports = { 6 | entry: path.resolve(__dirname, './index.js'), 7 | output: { 8 | path: __dirname, 9 | filename: 'bundle.js', 10 | }, 11 | devServer: { 12 | contentBase: path.resolve(__dirname), 13 | port: 8080, 14 | publicPath: '/', 15 | }, 16 | devtool: 'cheap-module-eval-source-map', 17 | module: { 18 | loaders: [ 19 | { 20 | test: /\.js?$/, 21 | loader: 'babel-loader', 22 | }, 23 | { 24 | test: /\.css/, 25 | use: ExtractTextPlugin.extract({ 26 | fallback: 'style-loader', 27 | use: 'css-loader', 28 | }), 29 | }, 30 | { 31 | test: /\.scss$/, 32 | loaders: ['style-loader', 'css-loader', 'sass-loader'] 33 | } 34 | ], 35 | }, 36 | watch: true, 37 | plugins: [ 38 | new ExtractTextPlugin('style.css'), 39 | ] 40 | }; 41 | -------------------------------------------------------------------------------- /keyboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xtrinch/react-touch-screen-keyboard/57a47c6922da4a87e64b4bac57e269ad3520e9e8/keyboard.png -------------------------------------------------------------------------------- /lib/Keyboard.css: -------------------------------------------------------------------------------- 1 | .keyboard{width:100%;max-width:1030px;margin:0 auto;background:#dadada;box-shadow:0 2px 12px 0 rgba(0,0,0,0.5)}.keyboard-wrapper{position:fixed;bottom:0;z-index:10000;width:700px;left:calc(50% - 350px)}.keyboard-wrapper .keyboard-row{display:flex}.keyboard-button{display:flex;justify-content:space-around;align-items:center;flex-basis:100px;font-size:18px;height:60px;border-radius:4px;background-color:#F5F5F5;border:1px solid #CECECE;font-family:'Roboto', sans-serif;font-weight:300}.keyboard-button:focus{outline:none}.keyboard-button:disabled{opacity:0.4;cursor:default}.keyboard-button:disabled{opacity:0.4;cursor:default}.keyboard-button:active{background-color:#cccccc}.keyboard-button.shift-symbols{flex-basis:210px}.keyboard-button.keyboard-numberButton{flex-grow:1}.keyboard-button.keyboard-halfButton{flex-basis:56px}.keyboard-button.keyboard-space{flex-grow:1}.keyboard-button.keyboard-utilButton{flex-grow:2;max-width:55px}.keyboard-button.keyboard-additionalButton{flex-basis:128px}.keyboard-button.keyboard-submitButton,.keyboard-button.keyboard-submit-button{flex-basis:95px;background-color:#63b324;border-color:#63b324;color:#ffffff}.keyboard-keysSet{display:flex;flex-grow:1} 2 | 3 | /*# sourceMappingURL=Keyboard.css.map */ -------------------------------------------------------------------------------- /lib/Keyboard.css.map: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "file": "Keyboard.css", 4 | "sources": [ 5 | "../src/Keyboard.scss" 6 | ], 7 | "names": [], 8 | "mappings": "AAAA,AAAA,SAAS,AAAC,CACR,KAAK,CAAE,IAAI,CACX,SAAS,CAAE,MAAM,CACjB,MAAM,CAAE,MAAM,CACd,UAAU,CAAE,OAAO,CACnB,UAAU,CAAE,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,eAAe,CACzC,AAED,AAAA,iBAAiB,AAAC,CAChB,QAAQ,CAAC,KAAK,CACd,MAAM,CAAE,CAAC,CACT,OAAO,CAAE,KAAK,CACd,KAAK,CAAE,KAAK,CACZ,IAAI,CAAE,iBAAiB,CAKxB,AAVD,AAOE,iBAPe,CAOf,aAAa,AAAC,CACZ,OAAO,CAAE,IAAI,CACd,AAGH,AAAA,gBAAgB,AAAC,CACf,OAAO,CAAE,IAAI,CACb,eAAe,CAAE,YAAY,CAC7B,WAAW,CAAE,MAAM,CACnB,UAAU,CAAE,KAAK,CACjB,SAAS,CAAE,IAAI,CACf,MAAM,CAAE,IAAI,CACZ,aAAa,CAAE,GAAG,CAClB,gBAAgB,CAAE,OAAO,CACzB,MAAM,CAAE,iBAAiB,CACzB,WAAW,CAAE,oBAAoB,CACjC,WAAW,CAAE,GAAG,CAqDjB,AAhED,AAaE,gBAbc,AAab,MAAM,AAAC,CACN,OAAO,CAAE,IAAI,CACd,AAfH,AAiBE,gBAjBc,AAiBb,SAAS,AAAC,CACT,OAAO,CAAE,GAAG,CACZ,MAAM,CAAE,OAAO,CAChB,AApBH,AAsBE,gBAtBc,AAsBb,SAAS,AAAC,CACT,OAAO,CAAE,GAAG,CACZ,MAAM,CAAE,OAAO,CAChB,AAzBH,AA2BE,gBA3Bc,AA2Bb,OAAO,AAAC,CACP,gBAAgB,CAAE,OAAO,CAC1B,AA7BH,AA+BE,gBA/Bc,AA+Bb,cAAc,AAAC,CACd,UAAU,CAAE,KAAK,CAClB,AAjCH,AAmCE,gBAnCc,AAmCb,sBAAsB,AAAC,CACtB,SAAS,CAAE,CAAC,CACb,AArCH,AAuCE,gBAvCc,AAuCb,oBAAoB,AAAC,CACpB,UAAU,CAAE,IAAI,CACjB,AAzCH,AA2CE,gBA3Cc,AA2Cb,eAAe,AAAC,CACf,SAAS,CAAE,CAAC,CACb,AA7CH,AAgDE,gBAhDc,AAgDb,oBAAoB,AAAC,CACpB,SAAS,CAAE,CAAC,CACZ,SAAS,CAAE,IAAI,CAChB,AAnDH,AAsDE,gBAtDc,AAsDb,0BAA0B,AAAC,CAC1B,UAAU,CAAE,KAAK,CAClB,AAxDH,AA0DE,gBA1Dc,AA0Db,sBAAsB,CA1DzB,gBAAgB,AA0DY,uBAAuB,AAAC,CAChD,UAAU,CAAE,IAAI,CAChB,gBAAgB,CAAE,OAAO,CACzB,YAAY,CAAE,OAAO,CACrB,KAAK,CAAE,OAAO,CACf,AAIH,AAAA,iBAAiB,AAAC,CAChB,OAAO,CAAE,IAAI,CACb,SAAS,CAAE,CAAC,CACb" 9 | } -------------------------------------------------------------------------------- /lib/Keyboard.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); 8 | 9 | var _react = require('react'); 10 | 11 | var _react2 = _interopRequireDefault(_react); 12 | 13 | var _propTypes = require('prop-types'); 14 | 15 | var _propTypes2 = _interopRequireDefault(_propTypes); 16 | 17 | var _reactDraggable = require('react-draggable'); 18 | 19 | var _reactDraggable2 = _interopRequireDefault(_reactDraggable); 20 | 21 | var _KeyboardButton = require('./KeyboardButton'); 22 | 23 | var _KeyboardButton2 = _interopRequireDefault(_KeyboardButton); 24 | 25 | var _LatinLayout = require('./layouts/LatinLayout'); 26 | 27 | var _LatinLayout2 = _interopRequireDefault(_LatinLayout); 28 | 29 | var _CyrillicLayout = require('./layouts/CyrillicLayout'); 30 | 31 | var _CyrillicLayout2 = _interopRequireDefault(_CyrillicLayout); 32 | 33 | var _SymbolsLayout = require('./layouts/SymbolsLayout'); 34 | 35 | var _SymbolsLayout2 = _interopRequireDefault(_SymbolsLayout); 36 | 37 | var _GermanLayout = require('./layouts/GermanLayout'); 38 | 39 | var _GermanLayout2 = _interopRequireDefault(_GermanLayout); 40 | 41 | var _FrenchLayout = require('./layouts/FrenchLayout'); 42 | 43 | var _FrenchLayout2 = _interopRequireDefault(_FrenchLayout); 44 | 45 | var _BackspaceIcon = require('./icons/BackspaceIcon'); 46 | 47 | var _BackspaceIcon2 = _interopRequireDefault(_BackspaceIcon); 48 | 49 | var _LanguageIcon = require('./icons/LanguageIcon'); 50 | 51 | var _LanguageIcon2 = _interopRequireDefault(_LanguageIcon); 52 | 53 | var _ShiftIcon = require('./icons/ShiftIcon'); 54 | 55 | var _ShiftIcon2 = _interopRequireDefault(_ShiftIcon); 56 | 57 | var _DraggableIcon = require('./icons/DraggableIcon'); 58 | 59 | var _DraggableIcon2 = _interopRequireDefault(_DraggableIcon); 60 | 61 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 62 | 63 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 64 | 65 | function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } 66 | 67 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } // The default 68 | 69 | var Keyboard = function (_PureComponent) { 70 | _inherits(Keyboard, _PureComponent); 71 | 72 | function Keyboard(props) { 73 | _classCallCheck(this, Keyboard); 74 | 75 | var _this = _possibleConstructorReturn(this, (Keyboard.__proto__ || Object.getPrototypeOf(Keyboard)).call(this, props)); 76 | 77 | _this.handleLetterButtonClick = _this.handleLetterButtonClick.bind(_this); 78 | _this.handleBackspaceClick = _this.handleBackspaceClick.bind(_this); 79 | _this.clearInput = _this.clearInput.bind(_this); 80 | _this.handleShiftClick = _this.handleShiftClick.bind(_this); 81 | _this.handleSymbolsClick = _this.handleSymbolsClick.bind(_this); 82 | _this.handleLanguageClick = _this.handleLanguageClick.bind(_this); 83 | _this.handleDragKeyClick = _this.handleDragKeyClick.bind(_this); 84 | 85 | _this.state = { 86 | currentLanguage: props.defaultKeyboard, 87 | showSymbols: false, 88 | uppercase: _this.isUppercase() 89 | }; 90 | return _this; 91 | } 92 | 93 | _createClass(Keyboard, [{ 94 | key: 'getKeys', 95 | value: function getKeys() { 96 | var keysSet = void 0; 97 | if (this.state.showSymbols) { 98 | keysSet = _SymbolsLayout2.default; 99 | } else if (this.state.currentLanguage === 'us') { 100 | keysSet = _LatinLayout2.default; 101 | } else if (this.state.currentLanguage === 'de') { 102 | keysSet = _GermanLayout2.default; 103 | } else if (this.state.currentLanguage === 'fr') { 104 | keysSet = _FrenchLayout2.default; 105 | } else if (this.state.currentLanguage === 'ru') { 106 | keysSet = _CyrillicLayout2.default; 107 | } else if (this.state.currentLanguage) { 108 | keysSet = this.state.currentLanguage; 109 | } else { 110 | keysSet = _LatinLayout2.default; 111 | } 112 | 113 | return this.state.uppercase ? keysSet.map(function (keyRow) { 114 | return keyRow.map(function (key) { 115 | return isFinite(key) ? key : key.toUpperCase(); 116 | }); 117 | }) : keysSet; 118 | } 119 | }, { 120 | key: 'getSymbolsKeyValue', 121 | value: function getSymbolsKeyValue() { 122 | var symbolsKeyValue = void 0; 123 | if (!this.state.showSymbols) { 124 | symbolsKeyValue = '.?!&'; 125 | } else if (this.state.currentLanguage === 'us' || this.state.currentLanguage === 'de') { 126 | symbolsKeyValue = 'Abc'; 127 | } else if (this.state.currentLanguage === 'ru') { 128 | symbolsKeyValue = 'Абв'; 129 | } else { 130 | symbolsKeyValue = 'Abc'; 131 | } 132 | return symbolsKeyValue; 133 | } 134 | }, { 135 | key: 'handleLanguageClick', 136 | value: function handleLanguageClick() { 137 | this.setState({ currentLanguage: this.state.currentLanguage === this.props.defaultKeyboard ? this.props.secondaryKeyboard : this.props.defaultKeyboard }); 138 | } 139 | }, { 140 | key: 'clearInput', 141 | value: function clearInput() { 142 | var inputNode = this.props.inputNode; 143 | 144 | 145 | inputNode.value = ''; 146 | if (this.props.onClick) { 147 | this.props.onClick(''); 148 | } 149 | 150 | setTimeout(function () { 151 | inputNode.focus(); 152 | }, 0); 153 | inputNode.dispatchEvent(new CustomEvent('input')); 154 | } 155 | }, { 156 | key: 'handleShiftClick', 157 | value: function handleShiftClick() { 158 | this.setState({ uppercase: !this.state.uppercase }); 159 | } 160 | }, { 161 | key: 'handleSymbolsClick', 162 | value: function handleSymbolsClick() { 163 | this.setState({ showSymbols: !this.state.showSymbols }); 164 | } 165 | }, { 166 | key: 'handleLetterButtonClick', 167 | value: function handleLetterButtonClick(key) { 168 | var inputNode = this.props.inputNode; 169 | var value = inputNode.value; 170 | 171 | var selectionStart = void 0; 172 | var selectionEnd = void 0; 173 | try { 174 | selectionStart = inputNode.selectionStart; 175 | selectionEnd = inputNode.selectionEnd; 176 | } catch (e) { 177 | selectionStart = value.length; 178 | selectionEnd = value.length; 179 | } 180 | var nextValue = value.substring(0, selectionStart) + key + value.substring(selectionEnd); 181 | 182 | inputNode.value = nextValue; 183 | if (this.props.onClick) { 184 | this.props.onClick(nextValue); 185 | } 186 | setTimeout(function () { 187 | inputNode.focus(); 188 | try { 189 | var offset = !isFinite(key) ? key.length : 1; 190 | inputNode.setSelectionRange(selectionStart + offset, selectionStart + offset); 191 | } catch (e) { 192 | console.error(e); 193 | } 194 | }); 195 | this.setState({ uppercase: this.isUppercase() }); 196 | inputNode.dispatchEvent(new CustomEvent('input')); 197 | } 198 | }, { 199 | key: 'handleDragKeyClick', 200 | value: function handleDragKeyClick() { 201 | var inputNode = this.props.inputNode; 202 | 203 | setTimeout(function () { 204 | inputNode.focus(); 205 | }, 0); 206 | } 207 | }, { 208 | key: 'isUppercase', 209 | value: function isUppercase() { 210 | var _props = this.props, 211 | inputNode = _props.inputNode, 212 | isFirstLetterUppercase = _props.isFirstLetterUppercase, 213 | uppercaseAfterSpace = _props.uppercaseAfterSpace, 214 | dataset = _props.dataset; 215 | 216 | return inputNode.type !== 'password' && dataset.type !== 'email' && (!inputNode.value.length && isFirstLetterUppercase || inputNode.value.length > 0 && inputNode.value[inputNode.value.length - 1] === ' ' && uppercaseAfterSpace); 217 | } 218 | }, { 219 | key: 'handleBackspaceClick', 220 | value: function handleBackspaceClick() { 221 | var inputNode = this.props.inputNode; 222 | var value = inputNode.value; 223 | 224 | var selectionStart = void 0; 225 | var selectionEnd = void 0; 226 | try { 227 | selectionStart = inputNode.selectionStart; 228 | selectionEnd = inputNode.selectionEnd; 229 | } catch (e) { 230 | selectionStart = 0; 231 | selectionEnd = value.length; 232 | } 233 | 234 | var nextValue = void 0; 235 | var nextSelectionPosition = void 0; 236 | if (selectionStart === selectionEnd) { 237 | nextValue = value.substring(0, selectionStart - 1) + value.substring(selectionEnd); 238 | nextSelectionPosition = selectionStart - 1; 239 | } else { 240 | nextValue = value.substring(0, selectionStart) + value.substring(selectionEnd); 241 | nextSelectionPosition = selectionStart; 242 | } 243 | nextSelectionPosition = nextSelectionPosition > 0 ? nextSelectionPosition : 0; 244 | 245 | inputNode.value = nextValue; 246 | if (this.props.onClick) { 247 | this.props.onClick(nextValue); 248 | } 249 | setTimeout(function () { 250 | inputNode.focus(); 251 | try { 252 | inputNode.setSelectionRange(nextSelectionPosition, nextSelectionPosition); 253 | } catch (e) { 254 | console.error(e); 255 | } 256 | }, 0); 257 | this.setState({ uppercase: this.isUppercase() }); 258 | inputNode.dispatchEvent(new CustomEvent('input')); 259 | } 260 | }, { 261 | key: 'render', 262 | value: function render() { 263 | var _this2 = this; 264 | 265 | var _props2 = this.props, 266 | inputNode = _props2.inputNode, 267 | secondaryKeyboard = _props2.secondaryKeyboard; 268 | 269 | var keys = this.getKeys(); 270 | var numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0]; 271 | var symbolsKeyValue = this.getSymbolsKeyValue(); 272 | 273 | return _react2.default.createElement( 274 | _reactDraggable2.default, 275 | { 276 | disabled: this.props.isDraggable === false, 277 | defaultPosition: { x: 0, y: 0 } 278 | }, 279 | _react2.default.createElement( 280 | 'div', 281 | { 282 | className: 'keyboard keyboard-wrapper ' + (typeof this.props.keyboardClassName !== 'undefined' ? this.props.keyboardClassName : ''), 283 | style: { opacity: '' + (typeof this.props.opacity !== 'undefined' ? this.props.opacity : 1) } 284 | }, 285 | this.props.showNumericRow ? _react2.default.createElement( 286 | 'div', 287 | { className: 'keyboard-row' }, 288 | numbers.map(function (button) { 289 | return _react2.default.createElement(_KeyboardButton2.default, { 290 | value: button, 291 | onClick: _this2.handleLetterButtonClick, 292 | classes: 'keyboard-numberButton', 293 | key: button 294 | }); 295 | }), 296 | _react2.default.createElement(_KeyboardButton2.default, { 297 | value: _react2.default.createElement(_BackspaceIcon2.default, null), 298 | onClick: this.handleBackspaceClick 299 | }) 300 | ) : null, 301 | keys.map(function (row, i) { 302 | return _react2.default.createElement( 303 | 'div', 304 | { key: 'r' + i, className: 'keyboard-row' }, 305 | keys.length === i + 1 && _this2.props.showShift && _react2.default.createElement(_KeyboardButton2.default, { 306 | classes: 'shift-symbols', 307 | value: _react2.default.createElement(_ShiftIcon2.default, null), 308 | onClick: _this2.handleShiftClick 309 | }), 310 | row.map(function (button, ii) { 311 | switch (button.toLowerCase()) { 312 | case '*bs': 313 | return _react2.default.createElement(_KeyboardButton2.default, { 314 | value: _react2.default.createElement(_BackspaceIcon2.default, null), 315 | onClick: _this2.handleBackspaceClick, 316 | key: 'b' + ii 317 | }); 318 | 319 | case '*sh': 320 | return _react2.default.createElement(_KeyboardButton2.default, { 321 | classes: 'shift-symbols', 322 | value: _react2.default.createElement(_ShiftIcon2.default, null), 323 | onClick: _this2.handleShiftClick, 324 | key: 'b' + ii 325 | }); 326 | 327 | default: 328 | return _react2.default.createElement(_KeyboardButton2.default, { 329 | value: button, 330 | onClick: _this2.handleLetterButtonClick, 331 | key: 'b' + ii 332 | }); 333 | } 334 | }), 335 | keys.length === i + 1 && _this2.props.showSymbols && _react2.default.createElement(_KeyboardButton2.default, { 336 | classes: 'shift-symbols', 337 | value: symbolsKeyValue, 338 | onClick: _this2.handleSymbolsClick 339 | }) 340 | ); 341 | }), 342 | _react2.default.createElement( 343 | 'div', 344 | { className: 'keyboard-row' }, 345 | typeof secondaryKeyboard !== 'undefined' ? _react2.default.createElement(_KeyboardButton2.default, { 346 | value: _react2.default.createElement(_LanguageIcon2.default, null), 347 | onClick: this.handleLanguageClick 348 | }) : null, 349 | inputNode.dataset.type === 'email' ? _react2.default.createElement(_KeyboardButton2.default, { 350 | value: '@', 351 | onClick: this.handleLetterButtonClick 352 | }) : null, 353 | this.props.isDraggable !== false ? _react2.default.createElement(_KeyboardButton2.default, { 354 | value: _react2.default.createElement(_DraggableIcon2.default, null), 355 | classes: '', 356 | onClick: this.handleDragKeyClick 357 | }) : null, 358 | this.props.showSpacebar ? _react2.default.createElement(_KeyboardButton2.default, { 359 | value: ' ', 360 | classes: 'keyboard-space', 361 | onClick: this.handleLetterButtonClick 362 | }) : null, 363 | inputNode.dataset.type === 'email' ? _react2.default.createElement(_KeyboardButton2.default, { 364 | value: '.', 365 | onClick: this.handleLetterButtonClick 366 | }) : null, 367 | _react2.default.createElement(_KeyboardButton2.default, { 368 | value: String.fromCharCode('8615'), 369 | classes: 'keyboard-submit-button', 370 | onClick: this.props.hideKeyboard 371 | }) 372 | ) 373 | ) 374 | ); 375 | } 376 | }]); 377 | 378 | return Keyboard; 379 | }(_react.PureComponent); 380 | 381 | Keyboard.propTypes = { 382 | inputNode: _propTypes2.default.any.isRequired, 383 | onClick: _propTypes2.default.func, 384 | isFirstLetterUppercase: _propTypes2.default.bool, 385 | uppercaseAfterSpace: _propTypes2.default.bool, 386 | defaultKeyboard: _propTypes2.default.any, 387 | secondaryKeyboard: _propTypes2.default.string, 388 | hideKeyboard: _propTypes2.default.func, 389 | opacity: _propTypes2.default.number, 390 | isDraggable: _propTypes2.default.bool, 391 | dataset: _propTypes2.default.any, 392 | keyboardClassName: _propTypes2.default.any, 393 | showNumericRow: _propTypes2.default.bool, 394 | showShift: _propTypes2.default.bool, 395 | showSymbols: _propTypes2.default.bool, 396 | showSpacebar: _propTypes2.default.bool 397 | }; 398 | Keyboard.defaultProps = { 399 | rightButtons: [], 400 | isFirstLetterUppercase: true, 401 | uppercaseAfterSpace: false, 402 | isDraggable: true, 403 | defaultKeyboard: 'us', 404 | dataset: { type: 'input' }, 405 | showNumericRow: true, 406 | showShift: true, 407 | showSymbols: true, 408 | showSpacebar: true 409 | }; 410 | exports.default = Keyboard; 411 | module.exports = exports['default']; -------------------------------------------------------------------------------- /lib/Keyboard.scss: -------------------------------------------------------------------------------- 1 | .keyboard { 2 | width: 100%; 3 | max-width: 1030px; 4 | margin: 0 auto; 5 | background: #dadada; 6 | box-shadow: 0 2px 12px 0 rgba(0,0,0,0.5); 7 | } 8 | 9 | .keyboard-wrapper { 10 | position:fixed; 11 | bottom: 0; 12 | z-index: 10000; 13 | width: 700px; 14 | left: calc(50% - 350px); 15 | 16 | .keyboard-row { 17 | display: flex; 18 | } 19 | } 20 | 21 | .keyboard-button { 22 | display: flex; 23 | justify-content: space-around; 24 | align-items: center; 25 | flex-basis: 100px; 26 | font-size: 18px; 27 | height: 60px; 28 | border-radius: 4px; 29 | background-color: #F5F5F5; 30 | border: 1px solid #CECECE; 31 | font-family: 'Roboto', sans-serif; 32 | font-weight: 300; 33 | 34 | &:focus { 35 | outline: none; 36 | } 37 | 38 | &:disabled { 39 | opacity: 0.4; 40 | cursor: default; 41 | } 42 | 43 | &:disabled { 44 | opacity: 0.4; 45 | cursor: default; 46 | } 47 | 48 | &:active { 49 | background-color: #cccccc; 50 | } 51 | 52 | &.shift-symbols { 53 | flex-basis: 210px; 54 | } 55 | 56 | &.keyboard-numberButton { 57 | flex-grow: 1; 58 | } 59 | 60 | &.keyboard-halfButton { 61 | flex-basis: 56px; 62 | } 63 | 64 | &.keyboard-space { 65 | flex-grow: 1; 66 | } 67 | 68 | // TODO Should be remove? 69 | &.keyboard-utilButton { 70 | flex-grow: 2; 71 | max-width: 55px; 72 | } 73 | 74 | // TODO Should be remove? 75 | &.keyboard-additionalButton { 76 | flex-basis: 128px; 77 | } 78 | 79 | &.keyboard-submitButton, &.keyboard-submit-button { 80 | flex-basis: 95px; 81 | background-color: #63b324; 82 | border-color: #63b324; 83 | color: #ffffff; 84 | } 85 | } 86 | 87 | // TODO Should be remove? 88 | .keyboard-keysSet { 89 | display: flex; 90 | flex-grow: 1; 91 | } 92 | -------------------------------------------------------------------------------- /lib/KeyboardButton.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); 8 | 9 | var _react = require('react'); 10 | 11 | var _react2 = _interopRequireDefault(_react); 12 | 13 | var _propTypes = require('prop-types'); 14 | 15 | var _propTypes2 = _interopRequireDefault(_propTypes); 16 | 17 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 18 | 19 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 20 | 21 | function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } 22 | 23 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } 24 | 25 | var KeyboardButton = function (_PureComponent) { 26 | _inherits(KeyboardButton, _PureComponent); 27 | 28 | function KeyboardButton(props) { 29 | _classCallCheck(this, KeyboardButton); 30 | 31 | var _this = _possibleConstructorReturn(this, (KeyboardButton.__proto__ || Object.getPrototypeOf(KeyboardButton)).call(this, props)); 32 | 33 | _this.handleClick = _this.handleClick.bind(_this); 34 | return _this; 35 | } 36 | 37 | _createClass(KeyboardButton, [{ 38 | key: 'handleClick', 39 | value: function handleClick() { 40 | if (typeof this.props.onClick !== 'undefined') { 41 | this.props.onClick(this.props.value); 42 | } 43 | } 44 | }, { 45 | key: 'render', 46 | value: function render() { 47 | return _react2.default.createElement( 48 | 'button', 49 | { 50 | type: 'button', 51 | className: 'keyboard-button ' + ' ' + (this.props.classes || ''), 52 | onClick: this.props.isDisabled ? null : this.handleClick, 53 | autoFocus: this.props.autofocus, 54 | disabled: this.props.isDisabled 55 | }, 56 | this.props.value 57 | ); 58 | } 59 | }]); 60 | 61 | return KeyboardButton; 62 | }(_react.PureComponent); 63 | 64 | KeyboardButton.propTypes = { 65 | value: _propTypes2.default.oneOfType([_propTypes2.default.string.isRequired, _propTypes2.default.node.isRequired]), 66 | classes: _propTypes2.default.string, 67 | onClick: _propTypes2.default.func.isRequired, 68 | autofocus: _propTypes2.default.bool, 69 | isDisabled: _propTypes2.default.bool 70 | }; 71 | KeyboardButton.defaultProps = { 72 | autofocus: false, 73 | isDisabled: false 74 | }; 75 | exports.default = KeyboardButton; 76 | module.exports = exports['default']; -------------------------------------------------------------------------------- /lib/KeyboardedInput.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; 8 | 9 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); 10 | 11 | var _react = require('react'); 12 | 13 | var _react2 = _interopRequireDefault(_react); 14 | 15 | var _propTypes = require('prop-types'); 16 | 17 | var _propTypes2 = _interopRequireDefault(_propTypes); 18 | 19 | var _Keyboard = require('./Keyboard'); 20 | 21 | var _Keyboard2 = _interopRequireDefault(_Keyboard); 22 | 23 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 24 | 25 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 26 | 27 | function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } 28 | 29 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } 30 | 31 | var KeyboardedInput = function (_React$Component) { 32 | _inherits(KeyboardedInput, _React$Component); 33 | 34 | function KeyboardedInput(props) { 35 | _classCallCheck(this, KeyboardedInput); 36 | 37 | var _this = _possibleConstructorReturn(this, (KeyboardedInput.__proto__ || Object.getPrototypeOf(KeyboardedInput)).call(this, props)); 38 | 39 | _this.focus = _this.focus.bind(_this); 40 | _this.handleFocus = _this.handleFocus.bind(_this); 41 | _this.handleFocusLost = _this.handleFocusLost.bind(_this); 42 | _this.handleChange = _this.handleChange.bind(_this); 43 | _this.hideKeyboard = _this.hideKeyboard.bind(_this); 44 | _this.handleOnBlur = _this.handleOnBlur.bind(_this); 45 | _this.handleOnFocus = _this.handleOnFocus.bind(_this); 46 | 47 | _this.state = { 48 | showKeyboard: false, 49 | input: null 50 | }; 51 | return _this; 52 | } 53 | 54 | _createClass(KeyboardedInput, [{ 55 | key: 'componentDidMount', 56 | value: function componentDidMount() { 57 | this.input.addEventListener('input', this.handleChange); 58 | } 59 | }, { 60 | key: 'componentWillUnmount', 61 | value: function componentWillUnmount() { 62 | this.input.removeEventListener('input', this.handleChange); 63 | } 64 | }, { 65 | key: 'focus', 66 | value: function focus() { 67 | this.input.focus(); 68 | } 69 | }, { 70 | key: 'handleChange', 71 | value: function handleChange(event) { 72 | this.props.onChange(event.target.value); 73 | } 74 | }, { 75 | key: 'handleOnBlur', 76 | value: function handleOnBlur(value) { 77 | this.props.onBlur(value); 78 | } 79 | }, { 80 | key: 'handleOnFocus', 81 | value: function handleOnFocus(value) { 82 | this.props.onFocus(value); 83 | } 84 | }, { 85 | key: 'handleFocus', 86 | value: function handleFocus() { 87 | var _this2 = this; 88 | 89 | var that = this; 90 | // Prevent blinking of the keyboard if opaque 91 | setTimeout(function () { 92 | if (that.input && typeof that.props.value !== 'undefined') { 93 | that.input.focus(); 94 | that.input.select(); 95 | that.input.setSelectionRange(that.props.value.length, that.props.value.length); 96 | 97 | // Only trigger on first focus 98 | if (_this2.state.showKeyboard === false && that.props.onFocus) { 99 | that.props.onFocus(that.props.value); 100 | } 101 | 102 | that.setState(_extends({}, _this2.state, { showKeyboard: true })); 103 | } 104 | }, 0); 105 | } 106 | }, { 107 | key: 'handleFocusLost', 108 | value: function handleFocusLost() { 109 | var that = this; 110 | setTimeout(function () { 111 | if (!document.activeElement.classList.contains('keyboard-button') && !document.activeElement.classList.contains('keyboard') && !document.activeElement.classList.contains('keyboard-row') && !document.activeElement.classList.contains('react-draggable-transparent-selection')) { 112 | 113 | if (that.props.onBlur) { 114 | that.props.onBlur(that.props.value); 115 | } 116 | 117 | that.setState(_extends({}, that.state, { showKeyboard: false })); 118 | } 119 | }, 0); 120 | } 121 | }, { 122 | key: 'hideKeyboard', 123 | value: function hideKeyboard() { 124 | if (this.props.onBlur) { 125 | this.props.onBlur(this.props.value); 126 | } 127 | 128 | this.setState(_extends({}, this.state, { showKeyboard: false })); 129 | } 130 | }, { 131 | key: 'render', 132 | value: function render() { 133 | var _this3 = this; 134 | 135 | return _react2.default.createElement( 136 | 'div', 137 | { className: this.props.containerClassName }, 138 | _react2.default.createElement('input', { 139 | name: this.props.name, 140 | className: this.props.inputClassName, 141 | placeholder: this.props.placeholder, 142 | required: this.props.required, 143 | value: this.props.value, 144 | type: this.props.type, 145 | onFocus: this.handleFocus, 146 | onBlur: this.handleFocusLost, 147 | min: this.props.min, 148 | max: this.props.max, 149 | step: this.props.step, 150 | pattern: this.props.pattern, 151 | onChange: this.handleChange, 152 | readOnly: this.props.readOnly === true, 153 | ref: function ref(e) { 154 | _this3.input = e; 155 | } 156 | }), 157 | this.state.showKeyboard && this.props.enabled && this.props.readOnly !== true && _react2.default.createElement(_Keyboard2.default, { 158 | hideKeyboard: this.hideKeyboard, 159 | defaultKeyboard: this.props.defaultKeyboard, 160 | secondaryKeyboard: this.props.secondaryKeyboard, 161 | inputNode: this.input, 162 | dataset: this.props.dataset, 163 | opacity: this.props.opacity, 164 | isDraggable: this.props.isDraggable, 165 | isFirstLetterUppercase: this.props.isFirstLetterUppercase, 166 | uppercaseAfterSpace: this.props.uppercaseAfterSpace, 167 | keyboardClassName: this.props.keyboardClassName, 168 | showNumericRow: this.props.showNumericRow, 169 | showShift: this.props.showShift, 170 | showSymbols: this.props.showSymbols, 171 | showSpacebar: this.props.showSpacebar 172 | }) 173 | ); 174 | } 175 | }]); 176 | 177 | return KeyboardedInput; 178 | }(_react2.default.Component); 179 | 180 | KeyboardedInput.propTypes = { 181 | name: _propTypes2.default.any, 182 | containerClassName: _propTypes2.default.any, 183 | inputClassName: _propTypes2.default.any, 184 | keyboardClassName: _propTypes2.default.any, 185 | placeholder: _propTypes2.default.any, 186 | value: _propTypes2.default.any.isRequired, 187 | type: _propTypes2.default.any, 188 | min: _propTypes2.default.any, 189 | max: _propTypes2.default.any, 190 | step: _propTypes2.default.any, 191 | pattern: _propTypes2.default.any, 192 | readOnly: _propTypes2.default.any, 193 | enabled: _propTypes2.default.any, 194 | required: _propTypes2.default.bool, 195 | defaultKeyboard: _propTypes2.default.any, 196 | secondaryKeyboard: _propTypes2.default.any, 197 | opacity: _propTypes2.default.any, 198 | isDraggable: _propTypes2.default.any, 199 | isFirstLetterUppercase: _propTypes2.default.any, 200 | uppercaseAfterSpace: _propTypes2.default.any, 201 | dataset: _propTypes2.default.any, 202 | onChange: _propTypes2.default.func, 203 | onBlur: _propTypes2.default.func, 204 | onFocus: _propTypes2.default.func, 205 | showNumericRow: _propTypes2.default.bool, 206 | showShift: _propTypes2.default.bool, 207 | showSymbols: _propTypes2.default.bool, 208 | showSpacebar: _propTypes2.default.bool 209 | }; 210 | exports.default = KeyboardedInput; 211 | module.exports = exports['default']; -------------------------------------------------------------------------------- /lib/icons/BackspaceIcon.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _react = require('react'); 8 | 9 | var _react2 = _interopRequireDefault(_react); 10 | 11 | var _propTypes = require('prop-types'); 12 | 13 | var _propTypes2 = _interopRequireDefault(_propTypes); 14 | 15 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 16 | 17 | var BackspaceIcon = function BackspaceIcon(_ref) { 18 | var _ref$viewBox = _ref.viewBox, 19 | viewBox = _ref$viewBox === undefined ? '0 0 24 24' : _ref$viewBox, 20 | _ref$width = _ref.width, 21 | width = _ref$width === undefined ? 24 : _ref$width, 22 | _ref$height = _ref.height, 23 | height = _ref$height === undefined ? 24 : _ref$height, 24 | fill = _ref.fill; 25 | return _react2.default.createElement( 26 | 'svg', 27 | { width: width, height: height, fill: fill, viewBox: viewBox }, 28 | _react2.default.createElement('path', { 29 | d: 'M22 3H7c-.69 0-1.23.35-1.59.88L0 12l5.41 8.11c.36.53.9.89 1.59.89h15c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-3 12.59L17.59 17 14 13.41 10.41 17 9 15.59 12.59 12 9 8.41 10.41 7 14 10.59 17.59 7 19 8.41 15.41 12 19 15.59z' 30 | }) 31 | ); 32 | }; 33 | 34 | BackspaceIcon.propTypes = { 35 | viewBox: _propTypes2.default.string, 36 | width: _propTypes2.default.number, 37 | height: _propTypes2.default.number, 38 | fill: _propTypes2.default.any 39 | }; 40 | 41 | exports.default = BackspaceIcon; 42 | module.exports = exports['default']; -------------------------------------------------------------------------------- /lib/icons/DraggableIcon.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _react = require('react'); 8 | 9 | var _react2 = _interopRequireDefault(_react); 10 | 11 | var _propTypes = require('prop-types'); 12 | 13 | var _propTypes2 = _interopRequireDefault(_propTypes); 14 | 15 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 16 | 17 | var DraggableIcon = function DraggableIcon(_ref) { 18 | var _ref$viewBox = _ref.viewBox, 19 | viewBox = _ref$viewBox === undefined ? '0 0 32 32' : _ref$viewBox, 20 | _ref$width = _ref.width, 21 | width = _ref$width === undefined ? 24 : _ref$width, 22 | _ref$height = _ref.height, 23 | height = _ref$height === undefined ? 24 : _ref$height, 24 | fill = _ref.fill; 25 | return _react2.default.createElement( 26 | 'svg', 27 | { width: width, height: height, fill: fill, viewBox: viewBox }, 28 | _react2.default.createElement('path', { d: 'M31.338,14.538L27.38,10.58C26.994,10.193,26.531,10,26,10c-1.188,0-2,1.016-2,2c0,0.516,0.186,0.986,0.58,1.38L25.2,14H18 V6.8l0.62,0.62C19.014,7.814,19.484,8,20,8c0.984,0,2-0.813,2-2c0-0.531-0.193-0.994-0.58-1.38l-3.973-3.974 C17.08,0.279,16.729,0,16,0s-1.135,0.334-1.463,0.662L10.58,4.62C10.193,5.006,10,5.469,10,6c0,1.188,1.016,2,2,2 c0.516,0,0.986-0.186,1.38-0.58L14,6.8V14H6.8l0.62-0.62C7.814,12.986,8,12.516,8,12c0-0.984-0.813-2-2-2 c-0.531,0-0.994,0.193-1.38,0.58l-3.958,3.958C0.334,14.866,0,15.271,0,16s0.279,1.08,0.646,1.447L4.62,21.42 C5.006,21.807,5.469,22,6,22c1.188,0,2-1.016,2-2c0-0.516-0.186-0.986-0.58-1.38L6.8,18H14v7.2l-0.62-0.62 C12.986,24.186,12.516,24,12,24c-0.984,0-2,0.813-2,2c0,0.531,0.193,0.994,0.58,1.38l3.957,3.958C14.865,31.666,15.271,32,16,32 s1.08-0.279,1.447-0.646l3.973-3.974C21.807,26.994,22,26.531,22,26c0-1.188-1.016-2-2-2c-0.516,0-0.986,0.186-1.38,0.58L18,25.2V18 h7.2l-0.62,0.62C24.186,19.014,24,19.484,24,20c0,0.984,0.813,2,2,2c0.531,0,0.994-0.193,1.38-0.58l3.974-3.973 C31.721,17.08,32,16.729,32,16S31.666,14.866,31.338,14.538z' }) 29 | ); 30 | }; 31 | 32 | DraggableIcon.propTypes = { 33 | viewBox: _propTypes2.default.string, 34 | width: _propTypes2.default.number, 35 | height: _propTypes2.default.number, 36 | fill: _propTypes2.default.any 37 | }; 38 | 39 | exports.default = DraggableIcon; 40 | module.exports = exports['default']; -------------------------------------------------------------------------------- /lib/icons/LanguageIcon.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _react = require('react'); 8 | 9 | var _react2 = _interopRequireDefault(_react); 10 | 11 | var _propTypes = require('prop-types'); 12 | 13 | var _propTypes2 = _interopRequireDefault(_propTypes); 14 | 15 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 16 | 17 | var LanguageIcon = function LanguageIcon(_ref) { 18 | var _ref$viewBox = _ref.viewBox, 19 | viewBox = _ref$viewBox === undefined ? '0 0 24 24' : _ref$viewBox, 20 | _ref$width = _ref.width, 21 | width = _ref$width === undefined ? 24 : _ref$width, 22 | _ref$height = _ref.height, 23 | height = _ref$height === undefined ? 24 : _ref$height, 24 | fill = _ref.fill; 25 | return _react2.default.createElement( 26 | 'svg', 27 | { width: width, height: height, fill: fill, viewBox: viewBox }, 28 | _react2.default.createElement('path', { d: 'M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zm6.93 6h-2.95c-.32-1.25-.78-2.45-1.38-3.56 1.84.63 3.37 1.91 4.33 3.56zM12 4.04c.83 1.2 1.48 2.53 1.91 3.96h-3.82c.43-1.43 1.08-2.76 1.91-3.96zM4.26 14C4.1 13.36 4 12.69 4 12s.1-1.36.26-2h3.38c-.08.66-.14 1.32-.14 2 0 .68.06 1.34.14 2H4.26zm.82 2h2.95c.32 1.25.78 2.45 1.38 3.56-1.84-.63-3.37-1.9-4.33-3.56zm2.95-8H5.08c.96-1.66 2.49-2.93 4.33-3.56C8.81 5.55 8.35 6.75 8.03 8zM12 19.96c-.83-1.2-1.48-2.53-1.91-3.96h3.82c-.43 1.43-1.08 2.76-1.91 3.96zM14.34 14H9.66c-.09-.66-.16-1.32-.16-2 0-.68.07-1.35.16-2h4.68c.09.65.16 1.32.16 2 0 .68-.07 1.34-.16 2zm.25 5.56c.6-1.11 1.06-2.31 1.38-3.56h2.95c-.96 1.65-2.49 2.93-4.33 3.56zM16.36 14c.08-.66.14-1.32.14-2 0-.68-.06-1.34-.14-2h3.38c.16.64.26 1.31.26 2s-.1 1.36-.26 2h-3.38z' }) 29 | ); 30 | }; 31 | 32 | LanguageIcon.propTypes = { 33 | viewBox: _propTypes2.default.string, 34 | width: _propTypes2.default.number, 35 | height: _propTypes2.default.number, 36 | fill: _propTypes2.default.any 37 | }; 38 | 39 | exports.default = LanguageIcon; 40 | module.exports = exports['default']; -------------------------------------------------------------------------------- /lib/icons/ShiftIcon.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _react = require('react'); 8 | 9 | var _react2 = _interopRequireDefault(_react); 10 | 11 | var _propTypes = require('prop-types'); 12 | 13 | var _propTypes2 = _interopRequireDefault(_propTypes); 14 | 15 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 16 | 17 | var ShiftIcon = function ShiftIcon(_ref) { 18 | var _ref$viewBox = _ref.viewBox, 19 | viewBox = _ref$viewBox === undefined ? '0 0 32 32' : _ref$viewBox, 20 | _ref$width = _ref.width, 21 | width = _ref$width === undefined ? 24 : _ref$width, 22 | _ref$height = _ref.height, 23 | height = _ref$height === undefined ? 24 : _ref$height, 24 | fill = _ref.fill; 25 | return _react2.default.createElement( 26 | 'svg', 27 | { width: width, height: height, fill: fill, viewBox: viewBox }, 28 | _react2.default.createElement('path', { d: 'M21 28h-10c-0.552 0-1-0.448-1-1v-11h-4c-0.404 0-0.769-0.244-0.924-0.617s-0.069-0.804 0.217-1.090l10-10c0.391-0.39 1.024-0.39 1.414 0l10 10c0.286 0.286 0.372 0.716 0.217 1.090s-0.519 0.617-0.924 0.617h-4v11c0 0.552-0.448 1-1 1zM12 26h8v-11c0-0.552 0.448-1 1-1h2.586l-7.586-7.586-7.586 7.586h2.586c0.552 0 1 0.448 1 1v11z' }) 29 | ); 30 | }; 31 | 32 | ShiftIcon.propTypes = { 33 | viewBox: _propTypes2.default.string, 34 | width: _propTypes2.default.number, 35 | height: _propTypes2.default.number, 36 | fill: _propTypes2.default.any 37 | }; 38 | 39 | exports.default = ShiftIcon; 40 | module.exports = exports['default']; -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | require('custom-event-polyfill'); 4 | 5 | var Keyboard = require('./Keyboard'); 6 | var KeyboardButton = require('./KeyboardButton'); 7 | var KeyboardedInput = require('./KeyboardedInput'); 8 | 9 | module.exports = KeyboardedInput.default || KeyboardedInput; 10 | module.exports.Keyboard = Keyboard.default || Keyboard; 11 | module.exports.KeyboardButton = KeyboardButton.default || KeyboardButton; -------------------------------------------------------------------------------- /lib/layouts/CyrillicLayout.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.default = [['й', 'ц', 'у', 'к', 'е', 'н', 'г', 'ш', 'щ', 'з', 'х', 'ъ'], ['ф', 'ы', 'в', 'а', 'п', 'р', 'о', 'л', 'д', 'ж', 'э'], ['я', 'ч', 'с', 'м', 'и', 'т', 'ь', 'б', 'ю']]; 7 | module.exports = exports['default']; -------------------------------------------------------------------------------- /lib/layouts/FrenchLayout.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.default = [['a', 'z', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p'], ['q', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'k', 'l', 'l', 'm'], ['w', 'x', 'c', 'v', 'b', 'n']]; 7 | module.exports = exports['default']; -------------------------------------------------------------------------------- /lib/layouts/GermanLayout.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.default = [['q', 'w', 'e', 'r', 't', 'z', 'u', 'i', 'o', 'p', 'ü'], ['a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', 'ö', 'ä'], ['y', 'x', 'c', 'v', 'b', 'n', 'm']]; 7 | module.exports = exports['default']; -------------------------------------------------------------------------------- /lib/layouts/LatinLayout.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.default = [['q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p'], ['a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l'], ['z', 'x', 'c', 'v', 'b', 'n', 'm']]; 7 | module.exports = exports['default']; -------------------------------------------------------------------------------- /lib/layouts/SymbolsLayout.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.default = [['=', '+', '%', '*', '[', ']', '{', '}', '<', '>'], ['@', ':', ';', '_', '-', '#', '(', ')', '/', '\\'], ['.', ',', '?', '!', '\'', '"', '^']]; 7 | module.exports = exports['default']; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-touch-screen-keyboard", 3 | "version": "1.0.0", 4 | "description": "React touch screen keyboard", 5 | "main": "./lib/index.js", 6 | "scripts": { 7 | "clean": "rimraf lib", 8 | "start": "cross-env NODE_ENV=development node_modules/webpack-dev-server/bin/webpack-dev-server.js --config ./examples/webpack.config.js", 9 | "lint": "eslint src || exit 0", 10 | "build": "cross-env babel src --out-dir lib", 11 | "copy": "cp src/Keyboard.scss lib/", 12 | "scss": "node-sass src/Keyboard.scss -o lib --output-style compressed --source-map true", 13 | "prepublish": "npm run clean && npm run build && npm run scss && npm run copy" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "https://github.com/xTrinch/react-touch-screen-keyboard" 18 | }, 19 | "keywords": [ 20 | "react", 21 | "react-component", 22 | "component", 23 | "touchscreen", 24 | "keyboard" 25 | ], 26 | "author": "xTrinch (https://github.com/xTrinch)", 27 | "license": "MIT", 28 | "bugs": { 29 | "url": "https://github.com/xTrinch/react-touch-screen-keyboard/issues" 30 | }, 31 | "homepage": "https://github.com/xTrinch/react-touch-screen-keyboard", 32 | "devDependencies": { 33 | "babel": "^6.5.2", 34 | "babel-cli": "^6.14.0", 35 | "babel-eslint": "^7.0.0", 36 | "babel-loader": "^7.1.1", 37 | "babel-plugin-add-module-exports": "^0.2.1", 38 | "babel-preset-es2015": "^6.14.0", 39 | "babel-preset-react": "^6.24.1", 40 | "babel-preset-stage-0": "^6.24.1", 41 | "babel-preset-stage-2": "^6.13.0", 42 | "cross-env": "^3.0.0", 43 | "css-loader": "^0.28.4", 44 | "eslint": "^3.6.0", 45 | "eslint-config-airbnb": "^13.0.0", 46 | "eslint-plugin-import": "^2.0.0", 47 | "eslint-plugin-jsx-a11y": "^2.2.2", 48 | "eslint-plugin-react": "^6.3.0", 49 | "extract-text-webpack-plugin": "^3.0.0", 50 | "node-sass": "^4.7.2", 51 | "prop-types": "^15.5.10", 52 | "react": "^16.3.2", 53 | "react-dom": "^16.3.2", 54 | "rimraf": "^2.5.4", 55 | "sass-loader": "^6.0.6", 56 | "style-loader": "^0.18.2", 57 | "webpack": "^3.4.1", 58 | "webpack-dev-server": "^2.9.3" 59 | }, 60 | "peerDependencies": { 61 | "react": "~0.14.8 || ^15.0.0 || ^16.0.0", 62 | "react-dom": "~0.14.8 || ^15.0.0 || ^16.0.0" 63 | }, 64 | "dependencies": { 65 | "custom-event-polyfill": "^0.3.0", 66 | "react-draggable": "^3.0.3" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/Keyboard.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import Draggable from 'react-draggable'; // The default 4 | 5 | import KeyboardButton from './KeyboardButton'; 6 | 7 | import LatinLayout from './layouts/LatinLayout'; 8 | import CyrillicLayout from './layouts/CyrillicLayout'; 9 | import SymbolsLayout from './layouts/SymbolsLayout'; 10 | import GermanLayout from './layouts/GermanLayout'; 11 | import FrenchLayout from './layouts/FrenchLayout'; 12 | 13 | import BackspaceIcon from './icons/BackspaceIcon'; 14 | import LanguageIcon from './icons/LanguageIcon'; 15 | import ShiftIcon from './icons/ShiftIcon'; 16 | import DraggableIcon from './icons/DraggableIcon'; 17 | 18 | export default class Keyboard extends PureComponent { 19 | static propTypes = { 20 | inputNode: PropTypes.any.isRequired, 21 | onClick: PropTypes.func, 22 | isFirstLetterUppercase: PropTypes.bool, 23 | uppercaseAfterSpace: PropTypes.bool, 24 | defaultKeyboard: PropTypes.any, 25 | secondaryKeyboard: PropTypes.string, 26 | hideKeyboard: PropTypes.func, 27 | opacity: PropTypes.number, 28 | isDraggable: PropTypes.bool, 29 | dataset: PropTypes.any, 30 | keyboardClassName: PropTypes.any, 31 | showNumericRow: PropTypes.bool, 32 | showShift: PropTypes.bool, 33 | showSymbols: PropTypes.bool, 34 | showSpacebar: PropTypes.bool, 35 | showSubmit: PropTypes.bool, 36 | }; 37 | 38 | static defaultProps = { 39 | rightButtons: [], 40 | isFirstLetterUppercase: true, 41 | uppercaseAfterSpace: false, 42 | isDraggable: true, 43 | defaultKeyboard: 'us', 44 | dataset: { type: 'input' }, 45 | showNumericRow: true, 46 | showShift: true, 47 | showSymbols: true, 48 | showSpacebar: true, 49 | showSubmit: true, 50 | }; 51 | 52 | constructor(props) { 53 | super(props); 54 | this.handleLetterButtonClick = this.handleLetterButtonClick.bind(this); 55 | this.handleBackspaceClick = this.handleBackspaceClick.bind(this); 56 | this.clearInput = this.clearInput.bind(this); 57 | this.handleShiftClick = this.handleShiftClick.bind(this); 58 | this.handleSymbolsClick = this.handleSymbolsClick.bind(this); 59 | this.handleLanguageClick = this.handleLanguageClick.bind(this); 60 | this.handleDragKeyClick = this.handleDragKeyClick.bind(this); 61 | 62 | this.state = { 63 | currentLanguage: props.defaultKeyboard, 64 | showSymbols: false, 65 | uppercase: this.isUppercase(), 66 | }; 67 | } 68 | 69 | getKeys() { 70 | let keysSet; 71 | if (this.state.showSymbols) { 72 | keysSet = SymbolsLayout; 73 | } else if (this.state.currentLanguage === 'us') { 74 | keysSet = LatinLayout; 75 | } else if (this.state.currentLanguage === 'de') { 76 | keysSet = GermanLayout; 77 | } else if (this.state.currentLanguage === 'fr') { 78 | keysSet = FrenchLayout; 79 | } else if (this.state.currentLanguage === 'ru') { 80 | keysSet = CyrillicLayout; 81 | } else if (this.state.currentLanguage) { 82 | keysSet = this.state.currentLanguage; 83 | } else { 84 | keysSet = LatinLayout; 85 | } 86 | 87 | return this.state.uppercase ? 88 | keysSet.map(keyRow => keyRow.map(key => isFinite(key) ? key : key.toUpperCase())) 89 | : keysSet; 90 | } 91 | 92 | getSymbolsKeyValue() { 93 | let symbolsKeyValue; 94 | if (!this.state.showSymbols) { 95 | symbolsKeyValue = '.?!&'; 96 | } else if (this.state.currentLanguage === 'us' || this.state.currentLanguage === 'de') { 97 | symbolsKeyValue = 'Abc'; 98 | } else if (this.state.currentLanguage === 'ru') { 99 | symbolsKeyValue = 'Абв'; 100 | } else { 101 | symbolsKeyValue = 'Abc'; 102 | } 103 | return symbolsKeyValue; 104 | } 105 | 106 | handleLanguageClick() { 107 | this.setState({ currentLanguage: this.state.currentLanguage === this.props.defaultKeyboard 108 | ? this.props.secondaryKeyboard : this.props.defaultKeyboard }); 109 | } 110 | 111 | clearInput() { 112 | const { inputNode } = this.props; 113 | 114 | inputNode.value = ''; 115 | if (this.props.onClick) { 116 | this.props.onClick(''); 117 | } 118 | 119 | setTimeout(() => { 120 | inputNode.focus(); 121 | }, 0); 122 | inputNode.dispatchEvent(new CustomEvent('input')); 123 | } 124 | 125 | handleShiftClick() { 126 | this.setState({ uppercase: !this.state.uppercase }); 127 | } 128 | 129 | handleSymbolsClick() { 130 | this.setState({ showSymbols: !this.state.showSymbols }); 131 | } 132 | 133 | handleLetterButtonClick(key) { 134 | const { inputNode } = this.props; 135 | const { value } = inputNode; 136 | let selectionStart; 137 | let selectionEnd; 138 | try { 139 | selectionStart = inputNode.selectionStart; 140 | selectionEnd = inputNode.selectionEnd; 141 | } catch (e) { 142 | selectionStart = value.length; 143 | selectionEnd = value.length; 144 | } 145 | const nextValue = value.substring(0, selectionStart) + key + value.substring(selectionEnd); 146 | 147 | inputNode.value = nextValue; 148 | if (this.props.onClick) { 149 | this.props.onClick(nextValue); 150 | } 151 | setTimeout(() => { 152 | inputNode.focus(); 153 | try { 154 | const offset = !isFinite(key) ? key.length : 1; 155 | inputNode.setSelectionRange(selectionStart + offset, selectionStart + offset); 156 | } catch (e) { 157 | console.error(e); 158 | } 159 | }); 160 | this.setState({ uppercase: this.isUppercase() }); 161 | inputNode.dispatchEvent(new CustomEvent('input')); 162 | } 163 | 164 | handleDragKeyClick() { 165 | const { inputNode } = this.props; 166 | setTimeout(() => { 167 | inputNode.focus(); 168 | }, 0); 169 | } 170 | 171 | isUppercase() { 172 | const { inputNode, isFirstLetterUppercase, uppercaseAfterSpace, dataset } = this.props; 173 | return inputNode.type !== 'password' && 174 | dataset.type !== 'email' && 175 | ((!inputNode.value.length && isFirstLetterUppercase) || (inputNode.value.length > 0 && 176 | inputNode.value[inputNode.value.length - 1] === ' ' && uppercaseAfterSpace)); 177 | } 178 | 179 | handleBackspaceClick() { 180 | const { inputNode } = this.props; 181 | const { value } = inputNode; 182 | let selectionStart; 183 | let selectionEnd; 184 | try { 185 | selectionStart = inputNode.selectionStart; 186 | selectionEnd = inputNode.selectionEnd; 187 | } catch (e) { 188 | selectionStart = 0; 189 | selectionEnd = value.length; 190 | } 191 | 192 | let nextValue; 193 | let nextSelectionPosition; 194 | if (selectionStart === selectionEnd) { 195 | nextValue = value.substring(0, selectionStart - 1) + value.substring(selectionEnd); 196 | nextSelectionPosition = selectionStart - 1; 197 | } else { 198 | nextValue = value.substring(0, selectionStart) + value.substring(selectionEnd); 199 | nextSelectionPosition = selectionStart; 200 | } 201 | nextSelectionPosition = (nextSelectionPosition > 0) ? nextSelectionPosition : 0; 202 | 203 | inputNode.value = nextValue; 204 | if (this.props.onClick) { 205 | this.props.onClick(nextValue); 206 | } 207 | setTimeout(() => { 208 | inputNode.focus(); 209 | try { 210 | inputNode.setSelectionRange(nextSelectionPosition, nextSelectionPosition); 211 | } catch (e) { 212 | console.error(e); 213 | } 214 | }, 0); 215 | this.setState({ uppercase: this.isUppercase() }); 216 | inputNode.dispatchEvent(new CustomEvent('input')); 217 | } 218 | 219 | getCharacterClassName = (letter) => { 220 | const char = `${letter}`; 221 | if (char.length > 1) { 222 | return ''; 223 | } 224 | return `keyboard-key-${char.charCodeAt(0)}`; 225 | } 226 | 227 | render() { 228 | const { inputNode, secondaryKeyboard } = this.props; 229 | const keys = this.getKeys(); 230 | const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0]; 231 | const symbolsKeyValue = this.getSymbolsKeyValue(); 232 | 233 | return ( 234 | 238 |
242 | {this.props.showNumericRow ? 243 |
244 | {numbers.map(button => 245 | , 251 | )} 252 | } 255 | onClick={this.handleBackspaceClick} 256 | /> 257 |
: null} 258 | 259 | {keys.map((row, i) => 260 |
261 | {keys.length === i + 1 && this.props.showShift && 262 | } 265 | onClick={this.handleShiftClick} 266 | /> 267 | } 268 | {row.map((button, ii) => { 269 | switch (button.toLowerCase()) { 270 | case '*bs': 271 | return ( 272 | } 275 | onClick={this.handleBackspaceClick} 276 | key={`b${ii}`} 277 | /> 278 | ); 279 | 280 | case '*sh': 281 | return ( 282 | } 285 | onClick={this.handleShiftClick} 286 | key={`b${ii}`} 287 | /> 288 | ); 289 | 290 | default: 291 | return ( 292 | 298 | ); 299 | } 300 | } 301 | )} 302 | 303 | {keys.length === i + 1 && this.props.showSymbols && 304 | 309 | } 310 |
, 311 | )} 312 | 313 |
314 | {typeof secondaryKeyboard !== 'undefined' ? 315 | } 317 | onClick={this.handleLanguageClick} 318 | /> 319 | : null} 320 | {inputNode.dataset.type === 'email' ? 321 | 325 | : null} 326 | {this.props.isDraggable !== false ? 327 | } 329 | classes="keyboard-draggable-button" 330 | onClick={this.handleDragKeyClick} 331 | /> 332 | : null} 333 | {this.props.showSpacebar ? 334 | 339 | : null} 340 | {inputNode.dataset.type === 'email' ? 341 | 345 | : null} 346 | {this.props.showSubmit ? 347 | 352 | : null} 353 |
354 |
355 |
356 | ); 357 | } 358 | } 359 | -------------------------------------------------------------------------------- /src/Keyboard.scss: -------------------------------------------------------------------------------- 1 | .keyboard { 2 | width: 100%; 3 | max-width: 1030px; 4 | margin: 0 auto; 5 | background: #dadada; 6 | box-shadow: 0 2px 12px 0 rgba(0,0,0,0.5); 7 | } 8 | 9 | .keyboard-wrapper { 10 | position:fixed; 11 | bottom: 0; 12 | z-index: 10000; 13 | width: 700px; 14 | left: calc(50% - 350px); 15 | 16 | .keyboard-row { 17 | display: flex; 18 | } 19 | } 20 | 21 | .keyboard-button { 22 | display: flex; 23 | justify-content: space-around; 24 | align-items: center; 25 | flex-basis: 100px; 26 | font-size: 18px; 27 | height: 60px; 28 | border-radius: 4px; 29 | background-color: #F5F5F5; 30 | border: 1px solid #CECECE; 31 | font-family: 'Roboto', sans-serif; 32 | font-weight: 300; 33 | 34 | &:focus { 35 | outline: none; 36 | } 37 | 38 | &:disabled { 39 | opacity: 0.4; 40 | cursor: default; 41 | } 42 | 43 | &:disabled { 44 | opacity: 0.4; 45 | cursor: default; 46 | } 47 | 48 | &:active { 49 | background-color: #cccccc; 50 | } 51 | 52 | &.shift-symbols { 53 | flex-basis: 210px; 54 | } 55 | 56 | &.keyboard-numberButton { 57 | flex-grow: 1; 58 | } 59 | 60 | &.keyboard-halfButton { 61 | flex-basis: 56px; 62 | } 63 | 64 | &.keyboard-space { 65 | flex-grow: 1; 66 | } 67 | 68 | // TODO Should be remove? 69 | &.keyboard-utilButton { 70 | flex-grow: 2; 71 | max-width: 55px; 72 | } 73 | 74 | // TODO Should be remove? 75 | &.keyboard-additionalButton { 76 | flex-basis: 128px; 77 | } 78 | 79 | &.keyboard-submitButton, &.keyboard-submit-button { 80 | flex-basis: 95px; 81 | background-color: #63b324; 82 | border-color: #63b324; 83 | color: #ffffff; 84 | } 85 | } 86 | 87 | // TODO Should be remove? 88 | .keyboard-keysSet { 89 | display: flex; 90 | flex-grow: 1; 91 | } 92 | -------------------------------------------------------------------------------- /src/KeyboardButton.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | export default class KeyboardButton extends PureComponent { 5 | static propTypes = { 6 | value: PropTypes.oneOfType([PropTypes.string.isRequired, PropTypes.node.isRequired]), 7 | classes: PropTypes.string, 8 | onClick: PropTypes.func.isRequired, 9 | autofocus: PropTypes.bool, 10 | isDisabled: PropTypes.bool, 11 | }; 12 | 13 | static defaultProps = { 14 | autofocus: false, 15 | isDisabled: false, 16 | }; 17 | 18 | constructor(props) { 19 | super(props); 20 | this.handleClick = this.handleClick.bind(this); 21 | } 22 | 23 | handleClick() { 24 | if (typeof (this.props.onClick) !== 'undefined') { 25 | this.props.onClick(this.props.value); 26 | } 27 | } 28 | 29 | render() { 30 | return ( 31 | 40 | ); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/KeyboardedInput.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | import Keyboard from './Keyboard'; 5 | 6 | class KeyboardedInput extends React.Component { 7 | static propTypes = { 8 | name: PropTypes.any, 9 | containerClassName: PropTypes.any, 10 | inputClassName: PropTypes.any, 11 | keyboardClassName: PropTypes.any, 12 | placeholder: PropTypes.any, 13 | value: PropTypes.any.isRequired, 14 | type: PropTypes.any, 15 | min: PropTypes.any, 16 | max: PropTypes.any, 17 | step: PropTypes.any, 18 | pattern: PropTypes.any, 19 | readOnly: PropTypes.any, 20 | enabled: PropTypes.any, 21 | required: PropTypes.bool, 22 | defaultKeyboard: PropTypes.any, 23 | secondaryKeyboard: PropTypes.any, 24 | opacity: PropTypes.any, 25 | isDraggable: PropTypes.any, 26 | isFirstLetterUppercase: PropTypes.any, 27 | uppercaseAfterSpace: PropTypes.any, 28 | dataset: PropTypes.any, 29 | onChange: PropTypes.func, 30 | onBlur: PropTypes.func, 31 | onFocus: PropTypes.func, 32 | showNumericRow: PropTypes.bool, 33 | showShift: PropTypes.bool, 34 | showSymbols: PropTypes.bool, 35 | showSpacebar: PropTypes.bool, 36 | showSubmit: PropTypes.bool, 37 | }; 38 | 39 | constructor(props) { 40 | super(props); 41 | this.focus = this.focus.bind(this); 42 | this.handleFocus = this.handleFocus.bind(this); 43 | this.handleFocusLost = this.handleFocusLost.bind(this); 44 | this.handleChange = this.handleChange.bind(this); 45 | this.hideKeyboard = this.hideKeyboard.bind(this); 46 | this.handleOnBlur = this.handleOnBlur.bind(this); 47 | this.handleOnFocus = this.handleOnFocus.bind(this); 48 | 49 | this.state = { 50 | showKeyboard: false, 51 | input: null, 52 | }; 53 | } 54 | 55 | componentDidMount() { 56 | this.input.addEventListener('input', this.handleChange); 57 | } 58 | 59 | componentWillUnmount() { 60 | this.input.removeEventListener('input', this.handleChange); 61 | } 62 | 63 | focus() { 64 | this.input.focus(); 65 | } 66 | 67 | handleChange(event) { 68 | this.props.onChange(event.target.value); 69 | } 70 | 71 | handleOnBlur(value) { 72 | this.props.onBlur(value); 73 | } 74 | 75 | handleOnFocus(value) { 76 | this.props.onFocus(value); 77 | } 78 | 79 | handleFocus() { 80 | const that = this; 81 | // Prevent blinking of the keyboard if opaque 82 | setTimeout(() => { 83 | if (that.input && typeof (that.props.value) !== 'undefined') { 84 | that.input.focus(); 85 | that.input.select(); 86 | that.input.setSelectionRange(that.props.value.length, that.props.value.length); 87 | 88 | // Only trigger on first focus 89 | if (this.state.showKeyboard === false && that.props.onFocus) { 90 | that.props.onFocus(that.props.value); 91 | } 92 | 93 | that.setState({ ...this.state, showKeyboard: true }); 94 | } 95 | }, 0); 96 | } 97 | 98 | handleFocusLost() { 99 | const that = this; 100 | setTimeout(() => { 101 | if (!document.activeElement.classList.contains('keyboard-button') 102 | && !document.activeElement.classList.contains('keyboard') 103 | && !document.activeElement.classList.contains('keyboard-row') 104 | && !document.activeElement.classList.contains('react-draggable-transparent-selection')) { 105 | 106 | if (that.props.onBlur) { 107 | that.props.onBlur(that.props.value); 108 | } 109 | 110 | that.setState({ ...that.state, showKeyboard: false }); 111 | } 112 | }, 0); 113 | } 114 | 115 | hideKeyboard() { 116 | if (this.props.onBlur) { 117 | this.props.onBlur(this.props.value); 118 | } 119 | 120 | this.setState({ ...this.state, showKeyboard: false }); 121 | } 122 | 123 | render() { 124 | return [ 125 | { this.input = e; }} 142 | />, 143 |
144 | {this.state.showKeyboard && this.props.enabled && this.props.readOnly !== true && 145 | 162 | } 163 |
, 164 | ]; 165 | } 166 | } 167 | 168 | export default KeyboardedInput; 169 | -------------------------------------------------------------------------------- /src/icons/BackspaceIcon.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | const BackspaceIcon = ({ viewBox = '0 0 24 24', width = 24, height = 24, fill }) => 5 | 6 | 9 | 10 | ; 11 | 12 | BackspaceIcon.propTypes = { 13 | viewBox: PropTypes.string, 14 | width: PropTypes.number, 15 | height: PropTypes.number, 16 | fill: PropTypes.any, 17 | }; 18 | 19 | export default BackspaceIcon; 20 | -------------------------------------------------------------------------------- /src/icons/DraggableIcon.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | const DraggableIcon = ({ viewBox = '0 0 32 32', width = 24, height = 24, fill }) => 5 | 6 | 7 | 8 | ; 9 | 10 | DraggableIcon.propTypes = { 11 | viewBox: PropTypes.string, 12 | width: PropTypes.number, 13 | height: PropTypes.number, 14 | fill: PropTypes.any, 15 | }; 16 | 17 | export default DraggableIcon; 18 | -------------------------------------------------------------------------------- /src/icons/LanguageIcon.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | const LanguageIcon = ({ viewBox = '0 0 24 24', width = 24, height = 24, fill }) => 5 | 6 | 7 | 8 | ; 9 | 10 | LanguageIcon.propTypes = { 11 | viewBox: PropTypes.string, 12 | width: PropTypes.number, 13 | height: PropTypes.number, 14 | fill: PropTypes.any, 15 | }; 16 | 17 | export default LanguageIcon; 18 | -------------------------------------------------------------------------------- /src/icons/ShiftIcon.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | const ShiftIcon = ({ viewBox = '0 0 32 32', width = 24, height = 24, fill }) => 5 | 6 | 7 | 8 | ; 9 | 10 | ShiftIcon.propTypes = { 11 | viewBox: PropTypes.string, 12 | width: PropTypes.number, 13 | height: PropTypes.number, 14 | fill: PropTypes.any, 15 | }; 16 | 17 | 18 | export default ShiftIcon; 19 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | require('custom-event-polyfill'); 2 | 3 | const Keyboard = require('./Keyboard'); 4 | const KeyboardButton = require('./KeyboardButton'); 5 | const KeyboardedInput = require('./KeyboardedInput'); 6 | 7 | module.exports = KeyboardedInput.default || KeyboardedInput; 8 | module.exports.Keyboard = Keyboard.default || Keyboard; 9 | module.exports.KeyboardButton = KeyboardButton.default || KeyboardButton; 10 | -------------------------------------------------------------------------------- /src/layouts/CyrillicLayout.js: -------------------------------------------------------------------------------- 1 | export default [ 2 | ['й', 'ц', 'у', 'к', 'е', 'н', 'г', 'ш', 'щ', 'з', 'х', 'ъ'], 3 | ['ф', 'ы', 'в', 'а', 'п', 'р', 'о', 'л', 'д', 'ж', 'э'], 4 | ['я', 'ч', 'с', 'м', 'и', 'т', 'ь', 'б', 'ю'], 5 | ]; 6 | -------------------------------------------------------------------------------- /src/layouts/FrenchLayout.js: -------------------------------------------------------------------------------- 1 | export default [ 2 | ['a', 'z', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p'], 3 | ['q', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', 'm'], 4 | ['w', 'x', 'c', 'v', 'b', 'n'], 5 | ]; 6 | -------------------------------------------------------------------------------- /src/layouts/GermanLayout.js: -------------------------------------------------------------------------------- 1 | export default [ 2 | ['q', 'w', 'e', 'r', 't', 'z', 'u', 'i', 'o', 'p', 'ü'], 3 | ['a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', 'ö', 'ä'], 4 | ['y', 'x', 'c', 'v', 'b', 'n', 'm'], 5 | ]; 6 | -------------------------------------------------------------------------------- /src/layouts/LatinLayout.js: -------------------------------------------------------------------------------- 1 | export default [ 2 | ['q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p'], 3 | ['a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l'], 4 | ['z', 'x', 'c', 'v', 'b', 'n', 'm'], 5 | ]; 6 | -------------------------------------------------------------------------------- /src/layouts/SymbolsLayout.js: -------------------------------------------------------------------------------- 1 | export default [ 2 | ['=', '+', '%', '*', '[', ']', '{', '}', '<', '>'], 3 | ['@', ':', ';', '_', '-', '#', '(', ')', '/', '\\'], 4 | ['.', ',', '?', '!', '\'', '"', '^'], 5 | ]; 6 | --------------------------------------------------------------------------------