├── .codeclimate.yml
├── .gitignore
├── .npmignore
├── CHANGELOG.md
├── Dev
├── demo.tsx
└── index.tsx
├── GALLERY.md
├── KEYSUPPORT.md
├── LICENSE
├── README.md
├── __tests__
├── Keyboard.spec.tsx
├── KeyboardKey.spec.tsx
├── index.spec.ts
└── layouts.spec.ts
├── example
├── README.md
├── custom_layouts
│ ├── README.md
│ ├── index.tsx
│ ├── package.json
│ ├── tsconfig.json
│ └── webpack.config.js
├── custom_textField
│ ├── README.md
│ ├── index.tsx
│ ├── package.json
│ ├── tsconfig.json
│ ├── typings.json
│ └── webpack.config.js
├── no_keyboard
│ ├── README.md
│ ├── index.tsx
│ ├── package.json
│ ├── tsconfig.json
│ ├── typings.json
│ └── webpack.config.js
└── simple_usage
│ ├── README.md
│ ├── index.tsx
│ ├── package.json
│ ├── tsconfig.json
│ ├── typings.json
│ └── webpack.config.js
├── package.json
├── screenshots
├── alphanumeric.png
├── capsed.png
├── extended.png
├── iphone6_landscape.png
├── iphone6_portrait.png
├── keyboard_icon.png
├── numeric.png
├── screen_1200x600.png
├── screen_200x100.png
├── screen_600x300.png
├── show.png
├── size.png
└── textField.png
├── server.js
├── src
├── ActiveElement.ts
├── Keyboard.tsx
├── KeyboardKey.tsx
├── index.tsx
└── layouts.ts
├── tsconfig.json
└── webpack.config.js
/.codeclimate.yml:
--------------------------------------------------------------------------------
1 | ---
2 | engines:
3 | fixme:
4 | enabled: true
5 | exclude_paths:
6 | - LICENSE
7 | - "*.md"
8 | - "*.json"
9 | - "*.js"
10 | - Dev/*
11 | - example/*
12 | - screenshots/*
13 | - __*/*
14 | ratings:
15 | paths:
16 | - src/*
--------------------------------------------------------------------------------
/.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 | # Dependency directories
30 | node_modules
31 | jspm_packages
32 |
33 | # Optional npm cache directory
34 | .npm
35 |
36 | # Optional REPL history
37 | .node_repl_history
38 |
39 | typings
40 | index.html
41 | main.js
42 | __*/*.js
43 | Dev/*.js
44 | src/*.js
45 | index.js
46 | Keyboard.js
47 | KeyboardKey.js
48 | layouts.js
49 | ActiveElement.js
50 | *.map
51 | *.d.ts
52 | coverage
53 | lcov.info
54 | .vscode
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | example
2 | node_modules
3 | src
4 | Dev
5 | screenshots
6 | typings
7 | __tests__
8 | coverage
9 | .git
10 | .vscode
11 | lcov.info
12 | index.html
13 | main.js
14 | server.js
15 | tsconfig.json
16 | typings.json
17 | webpack.config.js
18 | .npmignore
19 | .codeclimate.yml
20 | .gitignore
21 | GALLERY.md
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change log
2 |
3 | ## [v1.0.2](https://github.com/NoHomey/react-material-ui-keyboard/releases/tag/1.0.2)
4 |
5 | ### Key support
6 |
7 | - Backspace
8 | - Enter
9 | - Escape
10 | - CapsLock
11 | - Keyboard (simulates Keyboard Layout Switcher)
12 | - Any Single Char Key
13 | - Spacing (Blank spot)
14 |
15 | ### Keyboard layouts
16 |
17 | - Numeric Keyboard
18 | - AlphaNumeric Keyboard
19 |
20 | ## [v1.1.0](https://github.com/NoHomey/react-material-ui-keyboard/releases/tag/1.1.0)
21 |
22 | ### Feutures
23 |
24 | - Keyboard key's size (Can be changed by passing numbers to `keyboardKeyWidth`, `keyboardKeyHeight` & `keyboardKeySymbolSize` props)
25 |
26 | ## [v1.2.0](https://github.com/NoHomey/react-material-ui-keyboard/releases/tag/1.2.0)
27 |
28 | ### Feutures
29 |
30 | - react-material-ui-keyboard can be used in none material-ui based projects (peerDependencie of material-ui is required)
31 |
32 | ### Key support
33 |
34 | - Spacebar (Spacebar key width can be controlled by the number of spaces used for the key
35 |
36 | ### Keyboard layouts
37 |
38 | - Extended Keyboard
39 |
40 | ## [v1.2.2](https://github.com/NoHomey/react-material-ui-keyboard/releases/tag/1.2.2)
41 |
42 | ### Feutures
43 |
44 | - Can be used in TypeScript based projects: d.ts file is included (npm typings support)
45 |
46 | ## [v1.3.0](https://github.com/NoHomey/react-material-ui-keyboard/releases/tag/1.3.0)
47 |
48 | ### Feuters
49 |
50 | - Add support for regular JavaScript users by including propTypes
51 |
52 | ### Keyboard layouts
53 |
54 | - Numeric Keyboard now has `-` instead of blank space.
55 |
56 | ## [v1.4.0](https://github.com/NoHomey/react-material-ui-keyboard/releases/tag/1.4.0)
57 |
58 | ### Properties
59 |
60 | - adding new prop `type` of type `'string' | 'number'` which adds support for `textField` which uses `value` of type `number`
61 |
62 | ## [v1.5.0](https://github.com/NoHomey/react-material-ui-keyboard/releases/tag/1.5.0)
63 |
64 | ### Deprecated
65 |
66 | - prop `type` added in *v1.4.0* is deprecated due to it's probably never to be used as `'number'` `TextFieldInput` from material-ui v16.0 uses `value` of type `string` including for ``
67 |
68 | ### Bug fixes
69 |
70 | - fixing when inputs are `focus`ed and `blur`ed.
71 |
72 | ## [v2.0.0](https://github.com/NoHomey/react-material-ui-keyboard/releases/tag/2.0.0)
73 |
74 | ### Deprecated
75 |
76 | - `Keyboard.id`
77 |
78 | ### Implementaion
79 |
80 | - Switching from `id`s to `red`s
81 | - exposing public methods `getTextField` & `getKeyboardField`
82 | - using polyfilled `Object.assign` in from of `'object-assign'`
83 |
84 | ## [v2.0.1](https://github.com/NoHomey/react-material-ui-keyboard/releases/tag/2.0.1)
85 |
86 | Example changes
87 |
88 | ## [v2.0.3](https://github.com/NoHomey/react-material-ui-keyboard/releases/tag/2.0.3)
89 |
90 | ### Bug fixes
91 |
92 | - enusre Keyboard input field value is always in sync with `textField.props.value`
93 |
94 | ## [v3.0.0](https://github.com/NoHomey/react-material-ui-keyboard/releases/tag/3.0.0)
95 |
96 | ### Bug fixes
97 |
98 | - Fix bug where changing keyboard key size via passing `keyboardKey*` `prop` changed current `muiTheme`
99 |
100 | ### Implementaion
101 |
102 | - No logner using `context` to size keyboard key instead properties are passed directly to `KeyboardKey`
103 |
104 | ## [v3.0.1](https://github.com/NoHomey/react-material-ui-keyboard/releases/tag/3.0.1)
105 |
106 | ### Bug fixes
107 |
108 | - Fix bug where `KeyboardKey` would not re-render if any of `prop`s introduced in `v3.0.0` changes due to using old version of `shouldComponentUpdate`
109 |
110 | ### Implementaion
111 |
112 | - Droping inmplementation of `shouldComponentUpdate` for `KeyboardKey`
113 |
114 | ## [v3.1.0](https://github.com/NoHomey/react-material-ui-keyboard/releases/tag/3.1.0)
115 |
116 | ### Feuters
117 |
118 | - When `open` changes to `true` `Keyboard` will listen for `'keydown'` events on `window` and when `open` changes to `false` listener will be removed.
119 |
120 | ## [v3.1.1](https://github.com/NoHomey/react-material-ui-keyboard/releases/tag/3.1.1)
121 |
122 | ### Bug fixes
123 |
124 | - Fix bug introduced with `v3.1.0` which wouldd call twice `_onKeyDown` if keyboard input field is focused due to listening both on `textField` clone and `window`
125 |
126 | ### Implementaion
127 |
128 | - textField` clone no longer recives `onKeyDown` handler
129 |
130 | ## [v3.1.2](https://github.com/NoHomey/react-material-ui-keyboard/releases/tag/3.1.2)
131 |
132 | ### Bug fixes
133 |
134 | - Fixing keyboard key caps locking which got broken in `v3.0.0`
135 |
136 | ## [v3.1.3](https://github.com/NoHomey/react-material-ui-keyboard/releases/tag/3.1.3)
137 |
138 | ### Implementaion
139 |
140 | - Replacing `List`s with plain old `div`s. `List` is a wrapper container build from `div` and just adds more overheap to `render`ing. It waas initially used in conjuction with `ListItem`, which is no longer used since `v2.0.0`
141 |
142 | ## [v4.0.0](https://github.com/NoHomey/react-material-ui-keyboard/releases/tag/4.0.0)
143 |
144 | ### Properties
145 |
146 | - New prop `automatic` added. Which should remove the boilerplate of opening a keyboard when `textField.props.onFocus` is tiggered and closing it when `props.onRequestClose` is fired
147 | - Props `open` and `onRequestClose` are now optional due to adding of the new `automatic` `prop`
148 |
149 | ## [v4.0.1](https://github.com/NoHomey/react-material-ui-keyboard/releases/tag/4.0.1)
150 |
151 | Example updates
152 |
153 | ## [v4.1.0](https://github.com/NoHomey/react-material-ui-keyboard/releases/tag/4.1.0)
154 |
155 | ### Properties
156 |
157 | - New prop `nativeVirtualKeyboard` added. Which controlls when to prevent the native vertual keyboard on `textField` by setting `readOnly`.
158 |
159 | Note: `readOnly` is always `true` on the cloned `textField` used for input when keyboard is opened
160 |
161 | Note: `readOnly` is setted to `true` on `textField` when `active` is also `true`
162 |
163 | ## [v4.1.1](https://github.com/NoHomey/react-material-ui-keyboard/releases/tag/4.1.1)
164 |
165 | ### Bug fixes
166 |
167 | - Fixing a bug which falsely synced keyboard input value with `textField.props.value` each time a `componentWillReciveProps` is called. Now values are synced only when `textField.props.value` did really changed. Bug was introduced with `v2.0.3`
168 |
169 | ## [v5.0.0](https://github.com/NoHomey/react-material-ui-keyboard/releases/tag/5.0.0)
170 |
171 | ### New
172 |
173 | - `Keyboard` now resizes on when `window` `'resize'`s
174 | - `Keyboard` now fits on screen if calculated size based on `keyboardKeyWidth`, `keyboardKeyHeight` & `keyboardKeySymbolSize` `props`s is less than calculated
175 | - `Keyboard` now exposes new `public` `static` member `automaitcOpenPredicate` which is `function` with signature: `function() => boolean` that is called when `automatic` is `true` and the attached `onFocus` handler on `textField` gets fired to determinate should keyboard `open` and disable native virtual keyboard by assigning `readOnly` at `textField` in the `render`. Default `automaitcOpenPredicate` behaviour is to always return `true`. You can override it to change when to `automatic`lly open keyboard `onFocus`
176 |
177 | Chech for examples [GALLERY](https://github.com/NoHomey/react-material-ui-keyboard/blob/master/GALLERY.md)
178 |
179 | ### Properties
180 |
181 | - `onInput` is now optional
182 |
183 | ### Changes
184 |
185 | - `readOnly` is assigned to the result of invoking `Keyboard.automaitcOpenPredicate` on `textField` when `active` is`true`
186 |
187 | ## [v5.0.1](https://github.com/NoHomey/react-material-ui-keyboard/releases/tag/5.0.1)
188 |
189 | ### Bug fixes
190 |
191 | - Fixing a bug which would set `Dialog` `dialogContentStyle` prop with `height`: `NaN` for custom `textFields` which dose not support `row` `prop`
192 |
193 | ## [v5.0.2](https://github.com/NoHomey/react-material-ui-keyboard/releases/tag/5.0.2)
194 |
195 | ### Bug fixes
196 |
197 | - Fixing possible styling bugs due to wrapping `textFiled` and `Dialog` in `div`. If `style` `prop` is passed to `textFiled` it will be also passed and to the wrapping `div`
198 | - Fixing possible styling bugs when `textFiled` is passed a `style` `prop` that would set any of `['minWidth', 'width', 'maxWidth', 'minHeight', 'height', 'maxHeight']`, those styling props are `delete`d on the cloned `textField`, used for keyboard input, in favour of unified user experiance of `fullWidth` `input`
199 |
200 | ## [v5.0.3](https://github.com/NoHomey/react-material-ui-keyboard/releases/tag/5.0.3)
201 |
202 | ### Bug fixes
203 |
204 | - Fixing possible styling bugs when other than `style` `prop`s, such as `inputStyle`, `underlineStyle` or any other posible custom `prop`, is passed to `textField` would make keyboard input "style boken". `textField` `style` `prop` `properties` `['minHeight', 'height', 'maxHeight']` are no longer `delete`d
205 | - Fixing possible styling bugs when `style` `prop` contains any of `['minHeight', 'height', 'maxHeight']` would prevent `Keyboard` from calculating it's height correctly
206 |
207 | ## [v6.0.0](https://github.com/NoHomey/react-material-ui-keyboard/releases/tag/6.0.0)
208 |
209 | ### Properties
210 |
211 | #### New
212 |
213 | - `correctionName` is a `string` which is the name of the cloned `textField` `prop` to which to bind `corrector`.
214 | - `corrector` is a `function` which is bound to the the cloned `textField` at `correctorName` `prop`. `this` is bound to the `Keyboard`, `public` method `makeCorrection` can be used to apply a correction to the keyboard input.
215 |
216 | ## [v6.0.1](https://github.com/NoHomey/react-material-ui-keyboard/releases/tag/6.0.1)
217 |
218 | - Re-exporting `default` from `'./Keboard'`
219 |
220 | ## [v6.0.2](https://github.com/NoHomey/react-material-ui-keyboard/releases/tag/6.0.2)
221 |
222 | - Using npm badge for README.md#Install
223 |
224 | ## [v6.0.3](https://github.com/NoHomey/react-material-ui-keyboard/releases/tag/6.0.3)
225 |
226 | ### Bug fixes
227 |
228 | - Fixing bug: `Uncaught TypeError: Cannot read property 'minHeight' of undefined` when no `style`s are passed to `textField`
229 |
230 | ## [v6.0.4](https://github.com/NoHomey/react-material-ui-keyboard/releases/tag/6.0.4)
231 |
232 | ### Changes
233 |
234 | - keyboard layouts are now moved to ``react-material-ui-keyboard/layouts``, in adition their names are in lower camel case now
235 | - Code is completetly refactored, in addition code is now memory and performance optimizated
236 |
237 | ### TypeScript
238 |
239 | - `Keyboard` and `KeyboardKey` can be used in `call` to `React.createElement`
240 |
241 | ## [v6.0.5](https://github.com/NoHomey/react-material-ui-keyboard/releases/tag/6.0.5)
242 |
243 | ### TypeScript
244 |
245 | - Fixing `[ts] Cannot find module 'react-material-ui-keyboard/layouts'`
246 |
247 | ## [v6.0.6](https://github.com/NoHomey/react-material-ui-keyboard/releases/tag/6.0.6)
248 |
249 | ### Properties
250 |
251 | - `nativeVirtualKeyboard` is now deprecated. `readOnly` is now computed based on `automatic` and `open` for the input. For keyboard input field it's always `true`
252 |
253 | ### Bug fixes
254 |
255 | - Fixing keyboard width computaion. Bug was introduced with `v6.0.4` update
256 |
257 | ## [v6.1.0](https://github.com/NoHomey/react-material-ui-keyboard/releases/tag/6.1.0)
258 |
259 | ### Depreacted
260 |
261 | - Usage of `ref`s is deprecated: The need of `ref`s was because of controlling when `input`s gets `focus`ed and `blur`ed, which is no more required, instead `document.activeElement.tagName.toLowerCase()` is checked is it `'input'` when `open` becomes `true` and if it is the `activeElement` is `blur()`ed
262 | - `public` methods: `getTextField` and `getKeyboardField` are both removed
263 |
264 | ### Bug fixes
265 |
266 | - If any of the following `prop`s is passed to `textField` it will be overwritten for the keyboard input
267 |
268 | ### Changes
269 |
270 | - Instad of doing twice `React.cloneElement(textField, someMergedProps)` `React.cloneElement` is called for the input and `React.createElement(textField.type, keyboardFieldProps`) for the keyboard input field
271 |
272 | ## [v6.1.1](https://github.com/NoHomey/react-material-ui-keyboard/releases/tag/6.1.1)
273 |
274 | ### New
275 |
276 | - `'#'` is added to `extendedKeyboard` layout
277 | - [warning](https://design.google.com/icons/#ic_warning) icon is used when given special key is not supported
278 | - Code is now have a 100% code coverage
279 |
280 | ## [v6.1.2](https://github.com/NoHomey/react-material-ui-keyboard/releases/tag/6.1.2)
281 |
282 | ### Bug fixes
283 |
284 | - Fixing how `keyboardKeySymbolSize` is calculated when `Keyboard` resizes
285 |
286 | ### Changes
287 |
288 | - Updating project dependencies
289 | - Updating project to use `typescript` `2.0.3` and `@types` instead of `typings`
290 |
291 | ## [v6.2.0](https://github.com/NoHomey/react-material-ui-keyboard/releases/tag/6.2.0)
292 |
293 | ### New
294 |
295 | `disableEffects` is a new `prop` supported by both `Keyboard` and `KeyboardKey`. When it is set to `true` it dissables `FocusRipple`, `KeyboardFocus`, `TouchRipple` and `hover` effects on the underlinig `button`/s
296 |
297 | ## [v6.2.1](https://github.com/NoHomey/react-material-ui-keyboard/releases/tag/6.2.1)
298 |
299 | ### New
300 |
301 | `onInputValueChange` is a new optional `prop` of `Keyboard` which is a callback that is triggered once keyboard's input change it's value. (closes #45)
302 |
303 | ### Changes
304 |
305 | - Updating project dependencies
--------------------------------------------------------------------------------
/Dev/demo.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import TextField from 'material-ui/TextField';
3 | import { Keyboard, InputHandler } from './../src/index';
4 | import { extendedKeyboard } from './../src/layouts';
5 |
6 | export interface DemoState {
7 | value?: string;
8 | };
9 |
10 | export interface TextEnterTarget {
11 | value?: string;
12 | };
13 |
14 | export interface TextEnterEvent {
15 | target: TextEnterTarget;
16 | };
17 |
18 | export default class Demo extends React.Component<{}, DemoState> {
19 | private _onInput: InputHandler;
20 | private _onInputChange: InputHandler;
21 |
22 | private _handleInput(input: string): void {
23 | this.setState({ value: input });
24 | }
25 |
26 | private _handleInputChange(value: string): void {
27 | console.warn(`change: ${value}`);
28 | }
29 |
30 | public constructor() {
31 | super();
32 | this.state = { value: '12' };
33 | this._onInput = this._handleInput.bind(this);
34 | this._onInputChange = this._handleInputChange.bind(this);
35 | }
36 |
37 | public componentDidMount(): void {
38 | setTimeout(() => this.setState({ value: '123456' }), 1000);
39 | }
40 |
41 | public render(): JSX.Element {
42 | const { state, _onInput, _onInputChange } = this;
43 | const { value } = state;
44 | const textField: JSX.Element = (
45 |
50 | );
51 |
52 | return (
53 |
54 |
55 |
65 |
66 | );
67 | }
68 | };
--------------------------------------------------------------------------------
/Dev/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { render as ReactDomRender } from 'react-dom';
3 | import * as injectTapEventPlugin from 'react-tap-event-plugin';
4 | import Demo from './demo';
5 |
6 | injectTapEventPlugin();
7 | let bootstrapNode = document.createElement('div');
8 | ReactDomRender(, bootstrapNode);
9 | document.body.appendChild(bootstrapNode);
--------------------------------------------------------------------------------
/GALLERY.md:
--------------------------------------------------------------------------------
1 | # Gallery of Keyboard Screen fitting with props:
2 |
3 | - keyboardKeyHeight={50}
4 | - keyboardKeyWidth={100}
5 | - keyboardKeySymbolSize={36}
6 |
7 | ## Screen 1200x600
8 |
9 | 
10 |
11 | ## Screen 600x300
12 |
13 | 
14 |
15 | ## Screen 200x100
16 |
17 | 
18 |
19 | ## Iphone 6 Portrait
20 |
21 | 
22 |
23 | ## Iphone 6 Landscape
24 |
25 | 
26 |
--------------------------------------------------------------------------------
/KEYSUPPORT.md:
--------------------------------------------------------------------------------
1 | # Key Support
2 |
3 | ## [v1.0.2](https://github.com/NoHomey/react-material-ui-keyboard/releases/tag/1.0.2)
4 |
5 | - Backspace
6 | - Enter
7 | - Escape
8 | - CapsLock
9 | - Keyboard (simulates Keyboard Layout Switcher)
10 | - Any Single Char Key
11 | - Spacing (Blank spot)
12 |
13 | ## [v1.2.0](https://github.com/NoHomey/react-material-ui-keyboard/releases/tag/1.2.0)
14 |
15 | - Spacebar (Spacebar key width can be controlled by the number of spaces used for the key see [Extended Keyboard for an example](https://github.com/NoHomey/react-material-ui-keyboard#extended-keyboard))
16 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Ivo Stratev
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-material-ui-keyboard
2 |
3 | **The project is now archived since it requires a complete rewrite with modern day React and Material-UI!**
4 |
5 | Virtual keyboard for TextField when needed.
6 |
7 | [](https://badge.fury.io/js/react-material-ui-keyboard)
8 | [](https://github.com/NoHomey/react-material-ui-keyboard)
9 | [](https://semaphoreci.com/nohomey/react-material-ui-keyboard)
10 | [](https://codeclimate.com/github/NoHomey/react-material-ui-keyboard)
11 | [](https://codeclimate.com/github/NoHomey/react-material-ui-keyboard/coverage)
12 | [](https://codeclimate.com/github/NoHomey/react-material-ui-keyboard)
13 | 
14 | 
15 |
16 | You controll when to open it which allows cross platform App optimizations and code reusability for diferent platoforms such as Progressive Web Apps, Hybrid Apps, Electron Apps, Touch Devices, Smart TVs, Desktops, and all other Compatible JavaScript Enviroments.
17 |
18 | You have the freedom to choose on which of them to `open` the `Keyboard` and on which to just use a `textField`!
19 |
20 | 
21 |
22 | 
23 |
24 | # Install
25 |
26 | Install with npm:
27 |
28 | ```bash
29 | $ npm install react-material-ui-keyboard
30 | ```
31 |
32 | [](https://nodei.co/npm/react-material-ui-keyboard/)
33 |
34 | # Changelog
35 |
36 | **Check [Change log](https://github.com/NoHomey/react-material-ui-keyboard/blob/master/CHANGELOG.md) for changes.**
37 |
38 | # Properties
39 |
40 | | Name | Type | Default | Description |
41 | | --------------------- | ---------------| -------------------------------------------- | -------------------- |
42 | | automatic | *bool* | | If true, keyboard will automaticlly: open when textField gets focused and close instead of firing onRequestClose. |
43 | | disableEffects | *bool* | | If true, disables all effects (ripples, focus, hover) on all `keyboardKey`s |
44 | | open | *bool* | | Controls whether the Keyboard is opened or not. |
45 | | layouts* | *string[][][]* | | Keybaord layouts that can be changed when user clicks on 'Keyboard' key. |
46 | | keyboardKeyWidth | *number* | *this.context.muiThemet.button.minWidth* | Override keyboard key's max width. |
47 | | keyboardKeyHeight | *number* | *this.context.muiThemet.button.height* | Override keyboard key's max height. |
48 | | keyboardKeySymbolSize | *number* | *this.context.muiThemet.flatButton.fontSize* | Override keyboard key's max symbol size. |
49 | | textField* | *element* | | Input field used when keyboard is closed and cloned when it's opened. |
50 | | onRequestClose | *function* | | Fired when keyboard recives 'Enter' or 'Escape' eighter from onKeyDown listener or keyboard key touch/click event. |
51 | | onInput | *function* | | Fired when keyboard recives 'Enter' **Signature:** `function(input: string) => void`. |
52 | | onInputValueChange | *function* | | Fired when keyboard's input chages value **Signature:** `function(input: string) => void`. |
53 | | correctorName | *string* | | Name of the cloned textField prop to which to bind corrector. |
54 | | corrector** | *function* | | Function which is bound to the the cloned textField at correctorName prop. this is bound to the Keyboard, public method makeCorrection can be used to apply a correction to the keyboard input. |
55 |
56 | Props marked with \* are required.
57 |
58 | \*\* corrector is required when correctorName is provided.
59 |
60 | # Requirements
61 |
62 | ## `textField` must be a controlled input
63 |
64 | ## Node passed to `textField` Prop must support the following props:
65 |
66 | - `value`\* of type `string`
67 | - `readOnly`\* of type `bool`
68 |
69 | Props marked with \* must be passed down to the native `input` element.
70 |
71 | # Implementation
72 |
73 | react-material-ui-keyboard is implemented using the followong Material-Ui Elements
74 |
75 | - Dialog
76 | - FlatButtton
77 | - SVG Icons
78 |
79 | and uses `React.cloneElement` to clone `textFiled` for the kyboard input field.
80 |
81 | The used `Dialog` is `modal` which guaranties that only one keyboard can be opened which allows memory and performance optimizations.
82 |
83 | `Keyboard` Compoment uses `MuiTheme`, `props`, `window.innerWidth` and `window.innerHeight` information to calculate it's size and keyboard keys size (width x height) to ensure it always fits best on screen chech [GALLERY](https://github.com/NoHomey/react-material-ui-keyboard/blob/master/GALLERY.md).
84 |
85 | # Key Support
86 |
87 | For supported keys read [KEYSUPPORT](https://github.com/NoHomey/react-material-ui-keyboard/blob/master/KEYSUPPORT.md)
88 |
89 | # Included Layouts
90 |
91 | The following keyboard layouts are exported from `'react-material-ui-keyboard/layouts'`
92 |
93 | ## numericKeyboard
94 |
95 | ```js
96 | const numericKeyboard = [
97 | ['Escape', '-', 'Backspace'],
98 | ['7', '8', '9'],
99 | ['4', '5', '6'],
100 | ['1', '2', '3'],
101 | ['0', '.', 'Enter']
102 | ];
103 | ```
104 |
105 | 
106 |
107 | ## alphaNumericKeyboard
108 |
109 | ```js
110 | const alphaNumericKeyboard = [
111 | ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0'],
112 | ['q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p'],
113 | ['a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', 'Backspace'],
114 | ['Escape', 'CapsLock', 'z', 'x', 'c', 'v', 'b', 'n', 'm', 'Enter']
115 | ];
116 | ```
117 |
118 | 
119 |
120 | ### With CapsLock On
121 |
122 | 
123 |
124 | ## extendedKeyboard
125 |
126 | ```js
127 | const extendedKeyboard = [
128 | ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0'],
129 | ['q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p'],
130 | ['a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', 'Backspace'],
131 | ['CapsLock', 'z', 'x', 'c', 'v', 'b', 'n', 'm', '-', 'CapsLock'],
132 | ['Escape', '@', '#', ' ', '.', 'Enter']
133 | ];
134 | ```
135 | ### Demonstrating Spacebar and keyboard key size futers
136 |
137 | 
138 |
139 | # Creating Custom Keyboard Layout
140 |
141 | - All single chars suppoted as String can be used as a symbol key!
142 | - Empty strings can be used for blank spaces
143 | - Use `KeyboardEvent.key` names for all [Special keys] (https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key)
144 | - Use `'Keyboard'` for key with which user can change keyboard layout
145 |
146 | **All spacial keys (none Symbol will have an Icon and support at some point*)**
147 |
148 | **Check supported keys!**
149 |
150 | **If a key you want to use is not supported open an Issue.**
151 |
152 | # Public methods
153 |
154 | `Keyboard` exposes `public` method `makeCorrection` which can be used to apply keyboard input value corrections when keyboard is opened or within `correction` handller.
155 |
156 | # Public members
157 |
158 | `Keyboard` has one `public` `static` member which is designed to be overwritten: `automaitcOpenPredicate` it's signature is `function() => boolean`. It is called when `automatic` is `true` and the attached `onFocus` handler on `textField` gets fired to determinate should keyboard `open` and disable the native virtual keyboard by assigning `readOnly` at `textField` in the `render`. Default `automaitcOpenPredicate` behaviour is to always return `true`. You can override it to change when to `automatic`lly open keyboard `onFocus`.
159 |
160 | # Examples
161 |
162 | ```js
163 | import * as React from 'react';
164 | import TextField from 'material-ui/TextField';
165 | import Keyboard from 'react-material-ui-keyboard';
166 | import { extendedKeyboard } from 'react-material-ui-keyboard/layouts';
167 |
168 | class Demo extends React.Component {
169 | constructor(props) {
170 | super(props);
171 | this.state = {
172 | open: false,
173 | value: ''
174 | };
175 | this.onInput = this.handleInput.bind(this);
176 | }
177 |
178 | handleInput(input) {
179 | this.setState({ value: input });
180 | }
181 |
182 | render() {
183 |
189 | }
190 | automatic
191 | onInput={this.onInput}
192 | layouts={[extendedKeyboard]}
193 | />;
194 | }
195 | };
196 | ```
197 |
198 | # Example using custom textField and controlling when to open the keyboard and when to prevent the native virtual keyboard
199 |
200 | ```js
201 | import * as React from 'react';
202 | import NumberInput from 'material-ui-number-input';
203 | import Keyboard from 'react-material-ui-keyboard';
204 | import { numericKeyboard } from 'react-material-ui-keyboard/layouts';
205 |
206 | function corrector(value) {
207 | console.log(`correction ${value}`);
208 | this.makeCorrection(value);
209 | }
210 |
211 | class Demo extends React.Component {
212 | constructor(props) {
213 | super(props);
214 | this.state = { open: false, value: '2' };
215 | this.onFocus = this.handleFocus.bind(this);
216 | this.onChange = this.handleChange.bind(this);
217 | this.onRequestClose = this.handleRequestClose.bind(this);
218 | this.onInput = this.handleInput.bind(this);
219 | this.onError = this.handleError.bind(this);
220 | this.onValid = this.handleValid.bind(this);
221 | }
222 |
223 | canOpenKeyboard() {
224 | return (this.state.value.length % 2) === 0;
225 | }
226 |
227 | handleFocus(event) {
228 | if(this.canOpenKeyboard()) {
229 | this.setState({ open: true });
230 | }
231 | }
232 |
233 | handleChange(event, value) {
234 | console.log(value);
235 | this.setState({ value: value });
236 | }
237 |
238 | handleRequestClose() {
239 | this.setState({ open: false });
240 | }
241 |
242 | handleInput(input) {
243 | console.log(input);
244 | this.setState({ value: input });
245 | }
246 |
247 | handleError(error) {
248 | let errorText;
249 | switch (error) {
250 | case 'required':
251 | errorText = 'This field is required';
252 | break;
253 | case 'invalidSymbol':
254 | errorText = 'You are tring to enter none number symbol';
255 | break;
256 | case 'incompleteNumber':
257 | errorText = 'Number is incomplete';
258 | break;
259 | case 'singleMinus':
260 | errorText = 'Minus can be use only for negativity';
261 | break;
262 | case 'singleFloatingPoint':
263 | errorText = 'There is already a floating point';
264 | break;
265 | case 'singleZero':
266 | errorText = 'Floating point is expected';
267 | break;
268 | case 'min':
269 | errorText = 'You are tring to enter number less than -10';
270 | break;
271 | case 'max':
272 | errorText = 'You are tring to enter number greater than 12';
273 | break;
274 | }
275 | this.setState({ errorText: errorText });
276 | }
277 |
278 | handleValid(value) {
279 | console.debug(`valid ${value}`);
280 | }
281 |
282 | componentDidMount() {
283 | setTimeout(() => this.setState({ value: '89' }), 1000);
284 | }
285 |
286 | render() {
287 | const { state, onFocus, onChange, onError, onValid, onInput } = this;
288 | const { value, errorText } = state;
289 | const textField = (
290 |
303 | );
304 |
305 | return (
306 |
318 | );
319 | }
320 | }
321 | ```
322 |
323 | # Written in Typescript and Typescript Ready! ([check examples](https://github.com/NoHomey/react-material-ui-keyboard/blob/master/example))
324 |
325 | # Supports propTypes for regular JavaScript users
326 |
327 | # It is possible to use react-material-ui-keyboard in none material-ui project.
328 |
329 | ## Limitations
330 |
331 | If you need to change theme eg. gutter, spacing, colors or any other option you need to wrapp `````` in ```MuiThemeProvider``` or to manually provide a ```muiTheme``` to parent's ```context```.
332 |
333 | # Testing
334 |
335 | 1. `npm install`
336 |
337 | 2. `npm test`
338 |
339 | # Contributing
340 |
341 | 1. `npm install`
342 |
343 | 2. Make changes
344 |
345 | 3. If necessary add some tests to `__tests__`
346 |
347 | 4. `npm test`
348 |
349 | 5. Make a Pull Request
350 |
--------------------------------------------------------------------------------
/__tests__/Keyboard.spec.tsx:
--------------------------------------------------------------------------------
1 | import { Keyboard, KeyboardProps, KeyboardState, RequestCloseHandler, InputHandler, KeyboardLayout } from './../src/Keyboard';
2 | import * as React from 'react';
3 | import { shallow, ShallowWrapper } from 'enzyme';
4 | import TextField from 'material-ui/TextField';
5 | import Dialog from 'material-ui/Dialog';
6 | import { KeyboardKey, KeyboardKeyProps } from './../src/KeyboardKey';
7 | import { extendedKeyboard, numericKeyboard } from './../src/layouts';
8 | import EventListenerService from 'event-listener-service';
9 | import ActiveElement from './../src/ActiveElement';
10 | import * as injectTapEventPlugin from 'react-tap-event-plugin';
11 |
12 | type KeyboardShallowWrapper = ShallowWrapper;
13 | type Listener = (eventName: string, listener: (event: any) => void, capture: boolean) => void;
14 |
15 | injectTapEventPlugin();
16 |
17 | describe('Keyboard', () => {
18 | let addListener: jest.Mock;
19 | let removeListener: jest.Mock;
20 | let isInput: jest.Mock<() => boolean>;
21 | let blur: jest.Mock<() => void>;
22 |
23 | beforeEach(() => {
24 | addListener = jest.fn();
25 | removeListener = jest.fn();
26 | isInput = jest.fn<() => boolean>(() => false);
27 | blur = jest.fn<() => void>();
28 | EventListenerService.setImplementation({ addListener, removeListener });
29 | ActiveElement.isInput = isInput;
30 | ActiveElement.blur = blur;
31 | });
32 |
33 | describe('when rendering', () => {
34 | it('undefines onChange prop for keyboard input field', () => {
35 | const onChange: () => void = (): void => { };
36 | const wrapper: KeyboardShallowWrapper = shallow(
37 | }
40 | layouts={[extendedKeyboard]} />
41 | , { lifecycleExperimental: true }
42 | );
43 | expect(wrapper.find(TextField).first().prop('onChange')).toBe(onChange);
44 | expect(wrapper.find(TextField).last().prop('onChange')).toBeUndefined();
45 | });
46 |
47 | it('undefines onFocus prop for keyboard input field and overwrites it for input when automatic', () => {
48 | const onFocus: () => void = (): void => { };
49 | const wrapper: KeyboardShallowWrapper = shallow(
50 | }
53 | layouts={[extendedKeyboard]} />
54 | , { lifecycleExperimental: true }
55 | );
56 | expect(wrapper.find(TextField).first().prop('onFocus')).not.toBe(onFocus);
57 | expect(typeof wrapper.find(TextField).first().prop('onFocus')).toBe('function');
58 | expect(wrapper.find(TextField).last().prop('onFocus')).toBeUndefined();
59 | });
60 |
61 | it('undefines onFocus prop for keyboard input field and nulls it for input when not automatic and opened', () => {
62 | const onFocus: () => void = (): void => { };
63 | const wrapper: KeyboardShallowWrapper = shallow(
64 | }
67 | layouts={[extendedKeyboard]} />
68 | , { lifecycleExperimental: true }
69 | );
70 | expect(wrapper.find(TextField).first().prop('onFocus')).not.toBe(onFocus);
71 | expect(wrapper.find(TextField).first().prop('onFocus')).toBeUndefined();
72 | expect(wrapper.find(TextField).last().prop('onFocus')).toBeUndefined();
73 | });
74 |
75 | it('undefines onFocus prop for keyboard input field', () => {
76 | const onFocus: () => void = (): void => { };
77 | const wrapper: KeyboardShallowWrapper = shallow(
78 | }
80 | layouts={[extendedKeyboard]} />
81 | , { lifecycleExperimental: true }
82 | );
83 | expect(wrapper.find(TextField).first().prop('onFocus')).toBe(onFocus);
84 | expect(wrapper.find(TextField).last().prop('onFocus')).toBeUndefined();
85 | });
86 |
87 | it('undefines onBlur prop for keyboard input field', () => {
88 | const onBlur: () => void = (): void => { };
89 | const wrapper: KeyboardShallowWrapper = shallow(
90 | }
93 | layouts={[extendedKeyboard]} />
94 | , { lifecycleExperimental: true }
95 | );
96 | expect(wrapper.find(TextField).first().prop('onBlur')).toBe(onBlur);
97 | expect(wrapper.find(TextField).last().prop('onBlur')).toBeUndefined();
98 | });
99 |
100 | it('undefines onKeyUp prop for keyboard input field', () => {
101 | const onKeyUp: () => void = (): void => { };
102 | const wrapper: KeyboardShallowWrapper = shallow(
103 | }
106 | layouts={[extendedKeyboard]} />
107 | , { lifecycleExperimental: true }
108 | );
109 | expect(wrapper.find('input').first().prop('onKeyUp')).toBe(onKeyUp);
110 | expect(wrapper.find('input').last().prop('onKeyUp')).toBeUndefined();
111 | });
112 |
113 | it('undefines onKeyDown prop for keyboard input field', () => {
114 | const onKeyDown: () => void = (): void => { };
115 | const wrapper: KeyboardShallowWrapper = shallow(
116 | }
119 | layouts={[extendedKeyboard]} />
120 | , { lifecycleExperimental: true }
121 | );
122 | expect(wrapper.find(TextField).first().prop('onKeyDown')).toBe(onKeyDown);
123 | expect(wrapper.find(TextField).last().prop('onKeyDown')).toBeUndefined();
124 | });
125 |
126 | it('undefines onKeyPress prop for keyboard input field', () => {
127 | const onKeyPress: () => void = (): void => { };
128 | const wrapper: KeyboardShallowWrapper = shallow(
129 | }
132 | layouts={[extendedKeyboard]} />
133 | , { lifecycleExperimental: true }
134 | );
135 | expect(wrapper.find('input').first().prop('onKeyPress')).toBe(onKeyPress);
136 | expect(wrapper.find('input').last().prop('onKeyPress')).toBeUndefined();
137 | });
138 |
139 | it('generates keyboard key for each key in the current layout', () => {
140 | const wrapper: KeyboardShallowWrapper = shallow(
141 | }
144 | layouts={[extendedKeyboard]} />
145 | , { lifecycleExperimental: true }
146 | );
147 | expect(extendedKeyboard.every((row: Array): boolean => row.every((key: string): boolean => wrapper.find({ keyboardKey: key.match(/\ +/) === null ? key : ' ' }).length >= 1))).toBe(true);
148 | });
149 |
150 | it('transfers keyboardKey props to every keyboard key from current layout', () => {
151 | const wrapper: KeyboardShallowWrapper = shallow(
152 | }
155 | layouts={[extendedKeyboard]}
156 | keyboardKeyHeight={30}
157 | keyboardKeyWidth={60}
158 | keyboardKeySymbolSize={20} />
159 | , { lifecycleExperimental: true }
160 | );
161 | extendedKeyboard.forEach((row: Array): void => row.forEach((key: string): void => {
162 | const isNotSpacebar: boolean = key.match(/\ +/) === null;
163 | const wrappedKey: ShallowWrapper = wrapper.find({ keyboardKey: isNotSpacebar ? key : ' ' }).first();
164 | expect(wrappedKey.prop('keyboardKeyHeight')).toBe(30);
165 | isNotSpacebar ? expect(wrappedKey.prop('keyboardKeyWidth')).toBe(60) : expect(wrappedKey.prop('keyboardKeyWidth')).toBeGreaterThan(60);
166 | expect(wrappedKey.prop('keyboardKeySymbolSize')).toBe(20);
167 | }));
168 | });
169 | });
170 |
171 | describe('once mounted', () => {
172 | const textField: JSX.Element = ;
173 | const layouts: KeyboardLayout[] = [extendedKeyboard];
174 | let wrapper: KeyboardShallowWrapper;
175 |
176 | beforeEach(() => {
177 | wrapper = shallow(
178 |
182 | , { lifecycleExperimental: true }
183 | );
184 | });
185 |
186 | describe('componentDidMount', () => {
187 | it('adds event listener for resize', () => {
188 | expect(addListener).toBeCalled();
189 | const args: any[] = addListener.mock.calls[0];
190 | expect(args[0]).toBe('resize');
191 | expect(typeof args[1]).toBe('function');
192 | expect(args[2]).toBe(false);
193 | });
194 |
195 | it('syncs values', () => {
196 | expect(wrapper.state('value')).toBe('new');
197 | });
198 | });
199 |
200 | describe('shouldComponentUpdate', () => {
201 | it('should re-render when state.value changes', () => {
202 | expect(wrapper.state('value')).toBe('new');
203 | wrapper.setState({ value: 'old' });
204 | expect(wrapper.state('value')).toBe('old');
205 | });
206 |
207 | it('should re-render when state.open changes', () => {
208 | expect(wrapper.state('open')).toBe(false);
209 | wrapper.setState({ open: true });
210 | expect(wrapper.state('open')).toBe(true);
211 | });
212 |
213 | it('should re-render when state.capsLock changes', () => {
214 | expect(wrapper.state('capsLock')).toBe(false);
215 | wrapper.setState({ capsLock: true });
216 | expect(wrapper.state('capsLock')).toBe(true);
217 | });
218 |
219 | it('should re-render when state.layout changes', () => {
220 | wrapper.setProps({ textField: textField, layouts: [extendedKeyboard, numericKeyboard] });
221 | expect(wrapper.state('layout')).toBe(0);
222 | wrapper.setState({ layout: 1 });
223 | expect(wrapper.state('layout')).toBe(1);
224 | });
225 |
226 | it('should re-render when props.open changes', () => {
227 | wrapper.setProps({ textField: textField, layouts: layouts, automatic: false });
228 | expect(wrapper.find(Dialog).prop('open')).toBe(false);
229 | wrapper.setProps({ textField: textField, layouts: layouts, open: true });
230 | expect(wrapper.find(Dialog).prop('open')).toBe(true);
231 | });
232 |
233 | it('should re-render when props.keyboardKeyHeight changes', () => {
234 | wrapper.setProps({ textField: textField, layouts: layouts, keyboardKeyHeight: 24 });
235 | expect(wrapper.find(KeyboardKey).first().prop('keyboardKeyHeight')).toBe(24);
236 | wrapper.setProps({ textField: textField, layouts: layouts, keyboardKeyHeight: 32 });
237 | expect(wrapper.find(KeyboardKey).first().prop('keyboardKeyHeight')).toBe(32);
238 | });
239 |
240 | it('should re-render when props.keyboardKeySymbolSize changes', () => {
241 | wrapper.setProps({ textField: textField, layouts: layouts, keyboardKeySymbolSize: 24 });
242 | expect(wrapper.find(KeyboardKey).first().prop('keyboardKeySymbolSize')).toBe(24);
243 | wrapper.setProps({ textField: textField, layouts: layouts, keyboardKeySymbolSize: 32 });
244 | expect(wrapper.find(KeyboardKey).first().prop('keyboardKeySymbolSize')).toBe(32);
245 | });
246 |
247 | it('should re-render when props.keyboardKeyWidth changes', () => {
248 | wrapper.setProps({ textField: textField, layouts: layouts, keyboardKeyWidth: 24 });
249 | expect(wrapper.find(KeyboardKey).first().prop('keyboardKeyWidth')).toBe(24);
250 | wrapper.setProps({ textField: textField, layouts: layouts, keyboardKeyWidth: 32 });
251 | expect(wrapper.find(KeyboardKey).first().prop('keyboardKeyWidth')).toBe(32);
252 | });
253 |
254 | it('should re-render when props.automatic changes', () => {
255 | expect(wrapper.state('open')).toBe(false);
256 | expect(wrapper.find(Dialog).prop('open')).toBe(false);
257 | wrapper.setState({ open: true });
258 | expect(wrapper.state('open')).toBe(true);
259 | expect(wrapper.find(Dialog).prop('open')).toBe(true);
260 | wrapper.setProps({ textField: textField, layouts: layouts, open: false });
261 | expect(wrapper.state('open')).toBe(true);
262 | expect(wrapper.find(Dialog).prop('open')).not.toBe(false);
263 | wrapper.setProps({ textField: textField, layouts: layouts, automatic: false });
264 | wrapper.setProps({ textField: textField, layouts: layouts, open: false });
265 | expect(wrapper.state('open')).toBe(true);
266 | expect(wrapper.find(Dialog).prop('open')).toBe(false);
267 | });
268 |
269 | it('should re-render when props.disableEffects changes', () => {
270 | wrapper.setProps({ textField: textField, layouts: layouts, disableEffects: false });
271 | expect(wrapper.find(KeyboardKey).first().prop('disableEffects')).toBe(false);
272 | wrapper.setProps({ textField: textField, layouts: layouts, disableEffects: true });
273 | expect(wrapper.find(KeyboardKey).first().prop('disableEffects')).toBe(true);
274 | });
275 |
276 | it('should re-render when props.correctorName changes', () => {
277 | const corrector: jest.Mock<(value: string) => void> = jest.fn<(value: string) => void>();
278 | wrapper.setProps({
279 | automatic: true,
280 | textField: textField,
281 | layouts: layouts,
282 | correctorName: 'onRequestChange',
283 | corrector: corrector
284 | });
285 | wrapper.find(TextField).last().simulate('RequestChange', 'old');
286 | expect(corrector).toBeCalledWith('old');
287 | corrector.mockClear();
288 | wrapper.setProps({
289 | automatic: true,
290 | textField: textField,
291 | layouts: layouts,
292 | correctorName: 'onPleaseChange',
293 | corrector: corrector
294 | });
295 | wrapper.find(TextField).last().simulate('PleaseChange', 'please');
296 | expect(corrector).toBeCalledWith('please');
297 | });
298 |
299 | it('should re-render when props.corrector changes', () => {
300 | const corrector1: jest.Mock<(value: string) => void> = jest.fn<(value: string) => void>();
301 | const corrector2: jest.Mock<(value: string) => void> = jest.fn<(value: string) => void>();
302 | wrapper = shallow(
303 |
309 | , { lifecycleExperimental: true }
310 | );
311 | wrapper.find(TextField).last().simulate('RequestChange', 'correct1');
312 | expect(corrector1).toBeCalledWith('correct1');
313 | wrapper.setProps({
314 | automatic: true,
315 | textField: textField,
316 | layouts: layouts,
317 | correctorName: 'onRequestChange',
318 | corrector: corrector2
319 | });
320 | wrapper.find(TextField).last().simulate('RequestChange', 'correct2');
321 | expect(corrector1).not.toBeCalledWith('correct2');
322 | expect(corrector2).toBeCalledWith('correct2');
323 | });
324 |
325 | it('should re-render when props.onInput', () => {
326 | const onInput1:jest.Mock = jest.fn();
327 | const onInput2:jest.Mock = jest.fn();
328 | const keydownEvent: any = {
329 | key: 'Enter',
330 | stopImmediatePropagation: jest.fn(),
331 | stopPropagation: jest.fn(),
332 | preventDefault: jest.fn()
333 | };
334 | wrapper.setProps({ automatic: true, textField: textField, layouts: layouts, onInput: onInput1 });
335 | wrapper.setState({ open: true });
336 | EventListenerService.emit('keydown', keydownEvent);
337 | expect(onInput1).toBeCalledWith('new');
338 | wrapper.setProps({ automatic: true, textField: textField, layouts: layouts, onInput: onInput2 });
339 | wrapper.setState({ open: true, value: 'old' });
340 | EventListenerService.emit('keydown', keydownEvent);
341 | expect(onInput1).not.toBeCalledWith('old');
342 | expect(onInput2).toBeCalledWith('old');
343 | });
344 |
345 | it('should re-render when props.onRequestClose', () => {
346 | const onRequestClose1:jest.Mock = jest.fn();
347 | const keydownEvent: any = {
348 | key: 'Escape',
349 | stopImmediatePropagation: jest.fn(),
350 | stopPropagation: jest.fn(),
351 | preventDefault: jest.fn()
352 | };
353 | wrapper.setProps({ textField: textField, layouts: layouts, automatic: false, open: true });
354 | EventListenerService.emit('keydown', keydownEvent);
355 | expect(onRequestClose1).not.toBeCalled();
356 | onRequestClose1.mockClear();
357 | wrapper.setProps({ textField: textField, layouts: layouts, onRequestClose: onRequestClose1, automatic: false, open: true });
358 | EventListenerService.emit('keydown', keydownEvent);
359 | expect(onRequestClose1).toBeCalled();
360 | });
361 |
362 | it('should re-render when textField.type changes', () => {
363 | expect(wrapper.find(TextField).length >= 2).toBe(true);
364 | wrapper.setProps({ automatic: true, layouts: layouts, textField: });
365 | expect(wrapper.find(TextField).length).toBe(0);
366 | expect(wrapper.find('input').length >= 2).toBe(true);
367 | });
368 |
369 | it('should re-render when props.layouts changes', () => {
370 | expect(wrapper.find({ keyboardKey: 'a' }).length).toBe(1);
371 | wrapper.setProps({ automatic: true, textField: textField, layouts: [numericKeyboard] });
372 | expect(wrapper.find({ keyboardKey: 'a' }).length).toBe(0);
373 | });
374 |
375 | it('should re-render when textField.props changes', () => {
376 | expect(wrapper.find(TextField).first().prop('value')).toBe('new');
377 | wrapper.setProps({ automatic: true, layouts: layouts, textField: });
378 | expect(wrapper.find(TextField).first().prop('value')).toBe('old');
379 | });
380 | });
381 |
382 | describe('componentWillReciveProps', () => {
383 | it('syncs values if they differ', () => {
384 | expect(wrapper.state('value')).toBe('new');
385 | wrapper.setProps({ textField: textField, layouts: layouts });
386 | expect(wrapper.state('value')).toBe('new');
387 | wrapper.setProps({ textField: , layouts: layouts });
388 | expect(wrapper.state('value')).toBe('old');
389 | wrapper.setProps({ textField: textField, layouts: layouts });
390 | expect(wrapper.state('value')).toBe('new');
391 | });
392 | });
393 |
394 | describe('componentDidUpdate', () => {
395 | describe('when automatic', () => {
396 | describe('when state.open changes to true', () => {
397 | beforeEach(() => {
398 | wrapper.setState({ open: true });
399 | });
400 |
401 | describe('when ActiveElement.isInput', () => {
402 | it('ActiveElement.blur()s', () => {
403 | wrapper.setState({ open: false });
404 | isInput.mockReturnValueOnce(true);
405 | wrapper.setState({ open: true });
406 | expect(blur).toBeCalled();
407 | });
408 | });
409 |
410 | describe('when ActiveElement is not input', () => {
411 | it('dose not call ActiveElement.blur', () => {
412 | expect(blur).not.toBeCalled();
413 | });
414 | });
415 |
416 | it('tests if ActiveElement.isInput', () => {
417 | expect(isInput).toBeCalled();
418 | });
419 |
420 | it('adds listener for global keydown event', () => {
421 | expect(addListener).toBeCalled();
422 | const { calls } = addListener.mock;
423 | const args: any[] = calls[calls.length - 1];
424 | expect(args[0]).toBe('keydown');
425 | expect(typeof args[1]).toBe('function');
426 | expect(args[2]).toBe(true);
427 | });
428 | });
429 |
430 | describe('when state.open changes from true to false', () => {
431 | beforeEach(() => {
432 | wrapper.setState({ open: true });
433 | wrapper.setState({ open: false });
434 | });
435 |
436 | it('removes listener for global keydown event', () => {
437 | wrapper.setState({ open: false });
438 | expect(removeListener).toBeCalled();
439 | const { calls } = removeListener.mock;
440 | const args: any[] = calls[calls.length - 1];
441 | expect(args[0]).toBe('keydown');
442 | expect(typeof args[1]).toBe('function');
443 | expect(args[2]).toBe(true);
444 | });
445 | });
446 | });
447 |
448 | describe('when not automatic', () => {
449 | beforeEach(() => {
450 | wrapper = shallow(
451 |
455 | , { lifecycleExperimental: true }
456 | );
457 | });
458 |
459 | describe('when props.open changes to true', () => {
460 | beforeEach(() => {
461 | wrapper.setProps({ textField: textField, layouts: layouts, open: true });
462 | });
463 |
464 | describe('when ActiveElement.isInput', () => {
465 | it('ActiveElement.blur()s', () => {
466 | wrapper.setProps({ textField: textField, layouts: layouts, open: false });
467 | isInput.mockReturnValueOnce(true);
468 | wrapper.setProps({ textField: textField, layouts: layouts, open: true });
469 | expect(blur).toBeCalled();
470 | });
471 | });
472 |
473 | describe('when ActiveElement is not input', () => {
474 | it('dose not call ActiveElement.blur', () => {
475 | expect(blur).not.toBeCalled();
476 | });
477 | });
478 |
479 | it('tests if ActiveElement.isInput', () => {
480 | expect(isInput).toBeCalled();
481 | });
482 |
483 | it('adds listener for global keydown event', () => {
484 | expect(addListener).toBeCalled();
485 | const { calls } = addListener.mock;
486 | const args: any[] = calls[calls.length - 1];
487 | expect(args[0]).toBe('keydown');
488 | expect(typeof args[1]).toBe('function');
489 | expect(args[2]).toBe(true);
490 | });
491 | });
492 |
493 | describe('when props.open changes from true to false', () => {
494 | it('removes listener for global keydown event', () => {
495 | wrapper.setProps({ textField: textField, layouts: layouts, open: true });
496 | wrapper.setProps({ textField: textField, layouts: layouts, open: false });
497 | expect(removeListener).toBeCalled();
498 | const { calls } = removeListener.mock;
499 | const args: any[] = calls[calls.length - 1];
500 | expect(args[0]).toBe('keydown');
501 | expect(typeof args[1]).toBe('function');
502 | expect(args[2]).toBe(true);
503 | });
504 | });
505 | });
506 | });
507 |
508 | describe('componentWillUnmount', () => {
509 | it('removes resize event listner', () => {
510 | wrapper.unmount();
511 | expect(removeListener).toBeCalled();
512 | const args: any[] = removeListener.mock.calls[0];
513 | expect(args[0]).toBe('resize');
514 | expect(typeof args[1]).toBe('function');
515 | expect(args[2]).toBe(false);
516 | });
517 | });
518 |
519 | describe('onKeyboard', () => {
520 | beforeEach(() => wrapper.find(TextField).first().simulate('focus'));
521 |
522 | describe('when Enter is recived', () => {
523 | let onInput: jest.Mock;
524 |
525 | beforeEach(() => {
526 | onInput = jest.fn();
527 | wrapper.setProps({ textField: textField, layouts: layouts, automatic: true, onInput: onInput });
528 | });
529 |
530 | it('emits onInput and closes the Keyboard', () => {
531 | expect(wrapper.find(Dialog).prop('open')).toBe(true);
532 | expect(onInput).not.toBeCalled();
533 | wrapper.find({ keyboardKey: 'Enter' }).shallow().simulate('touchTap');
534 | wrapper.update();
535 | expect(onInput).toBeCalled();
536 | expect(onInput).toBeCalledWith('new');
537 | expect(wrapper.find(Dialog).prop('open')).toBe(false);
538 | });
539 | });
540 |
541 | describe('when Backspace key is recived', () => {
542 | it('deletes last character as long as there are characters to be deleted', () => {
543 | expect(wrapper.state('value')).toBe('new');
544 | wrapper.find({ keyboardKey: 'Backspace' }).shallow().simulate('touchTap');
545 | wrapper.update();
546 | expect(wrapper.state('value')).toBe('ne');
547 | wrapper.find({ keyboardKey: 'Backspace' }).shallow().simulate('touchTap');
548 | wrapper.update();
549 | expect(wrapper.state('value')).toBe('n');
550 | wrapper.find({ keyboardKey: 'Backspace' }).shallow().simulate('touchTap');
551 | wrapper.update();
552 | expect(wrapper.state('value')).toBe('');
553 | wrapper.find({ keyboardKey: 'Backspace' }).shallow().simulate('touchTap');
554 | wrapper.update();
555 | expect(wrapper.state('value')).toBe('');
556 | });
557 | });
558 |
559 | describe('when Escape key is recived', () => {
560 | it('closes the Keyboard', () => {
561 | expect(wrapper.find(Dialog).prop('open')).toBe(true);
562 | wrapper.find({ keyboardKey: 'Escape' }).shallow().simulate('touchTap');
563 | wrapper.update();
564 | expect(wrapper.find(Dialog).prop('open')).toBe(false);
565 | });
566 | });
567 |
568 | describe('when CapsLock key is recived', () => {
569 | it('CapsLocks Keyboard', () => {
570 | expect(wrapper.find({ keyboardKey: 'a' }).length).toBe(1);
571 | expect(wrapper.find({ keyboardKey: 'A' }).length).toBe(0);
572 | wrapper.find({ keyboardKey: 'CapsLock' }).first().shallow().simulate('touchTap');
573 | wrapper.update();
574 | expect(wrapper.find({ keyboardKey: 'a' }).length).toBe(0);
575 | expect(wrapper.find({ keyboardKey: 'A' }).length).toBe(1);
576 | });
577 | });
578 |
579 | describe('when Keyboard key is recived', () => {
580 | beforeEach(() => wrapper.setProps({ textField: textField, layouts: [[['a', 'b'], ['Keyboard', 'Enter']], [['a', 'b', 'Keyboard', 'Enter']]] }))
581 |
582 | it('Changes Keyboard\'s layout', () => {
583 | expect(wrapper.state('layout')).toBe(0);
584 | wrapper.find({ keyboardKey: 'Keyboard' }).first().shallow().simulate('touchTap');
585 | wrapper.update();
586 | expect(wrapper.state('layout')).toBe(1);
587 | });
588 |
589 | it('should make circular layout changes', () => {
590 | expect(wrapper.state('layout')).toBe(0);
591 | wrapper.find({ keyboardKey: 'Keyboard' }).first().shallow().simulate('touchTap');
592 | wrapper.update();
593 | expect(wrapper.state('layout')).toBe(1);
594 | wrapper.find({ keyboardKey: 'Keyboard' }).first().shallow().simulate('touchTap');
595 | wrapper.update();
596 | expect(wrapper.state('layout')).toBe(0);
597 | });
598 | });
599 |
600 | describe('when symbol key is pressed', () => {
601 | it('should add character to value', () => {
602 | expect(wrapper.state('value')).toBe('new');
603 | wrapper.find({ keyboardKey: '@' }).first().shallow().simulate('touchTap');
604 | expect(wrapper.state('value')).toBe('new@');
605 | wrapper.find({ keyboardKey: 't' }).first().shallow().simulate('touchTap');
606 | expect(wrapper.state('value')).toBe('new@t');
607 | wrapper.find({ keyboardKey: 's' }).first().shallow().simulate('touchTap');
608 | expect(wrapper.state('value')).toBe('new@ts');
609 | wrapper.find({ keyboardKey: '#' }).first().shallow().simulate('touchTap');
610 | expect(wrapper.state('value')).toBe('new@ts#');
611 | wrapper.find({ keyboardKey: '9' }).first().shallow().simulate('touchTap');
612 | expect(wrapper.state('value')).toBe('new@ts#9');
613 | });
614 | });
615 |
616 | describe('when not supported Special Key is pressed', () => {
617 | it('dose not do anything', () => {
618 | wrapper.setProps({ textField: textField, layouts: [[['TS']]] });
619 | const props: KeyboardProps = wrapper.props();
620 | const state: KeyboardState = wrapper.state();
621 | wrapper.find({ keyboardKey: 'TS' }).first().shallow().simulate('touchTap');
622 | wrapper.update();
623 | expect(wrapper.props()).toBe(props);
624 | expect(wrapper.state()).toBe(state);
625 | });
626 | });
627 | });
628 |
629 | describe('onKeyDown', () => {
630 | beforeEach(() => wrapper.find(TextField).first().simulate('focus'));
631 |
632 | describe('when Enter is recived', () => {
633 | let onInput: jest.Mock;
634 |
635 | beforeEach(() => {
636 | onInput = jest.fn();
637 | wrapper.setProps({ textField: textField, layouts: layouts, automatic: true, onInput: onInput });
638 | });
639 |
640 | it('emits onInput and closes the Keyboard when is pressed', () => {
641 | const keydownEvent: any = {
642 | key: 'Enter',
643 | stopImmediatePropagation: jest.fn(),
644 | stopPropagation: jest.fn(),
645 | preventDefault: jest.fn()
646 | };
647 | expect(wrapper.find(Dialog).prop('open')).toBe(true);
648 | expect(onInput).not.toBeCalled();
649 | EventListenerService.emit('keydown', keydownEvent);
650 | wrapper.update();
651 | expect(onInput).toBeCalled();
652 | expect(onInput).toBeCalledWith('new');
653 | expect(wrapper.find(Dialog).prop('open')).toBe(false);
654 | });
655 | });
656 |
657 | describe('when Backspace key is recived', () => {
658 | it('deletes last character as long as there are characters to be deleted when is pressed', () => {
659 | const keydownEvent: any = {
660 | key: 'Backspace',
661 | stopImmediatePropagation: jest.fn(),
662 | stopPropagation: jest.fn(),
663 | preventDefault: jest.fn()
664 | };
665 | expect(wrapper.state('value')).toBe('new');
666 | EventListenerService.emit('keydown', keydownEvent);
667 | wrapper.update();
668 | expect(wrapper.state('value')).toBe('ne');
669 | EventListenerService.emit('keydown', keydownEvent);
670 | wrapper.update();
671 | expect(wrapper.state('value')).toBe('n');
672 | EventListenerService.emit('keydown', keydownEvent);
673 | wrapper.update();
674 | expect(wrapper.state('value')).toBe('');
675 | EventListenerService.emit('keydown', keydownEvent);
676 | wrapper.update();
677 | expect(wrapper.state('value')).toBe('');
678 | });
679 | });
680 |
681 | describe('when Escape key is recived', () => {
682 | it('closes the Keyboard when is pressed', () => {
683 | const keydownEvent: any = {
684 | key: 'Escape',
685 | stopImmediatePropagation: jest.fn(),
686 | stopPropagation: jest.fn(),
687 | preventDefault: jest.fn()
688 | };
689 | expect(wrapper.find(Dialog).prop('open')).toBe(true);
690 | EventListenerService.emit('keydown', keydownEvent);
691 | wrapper.update();
692 | expect(wrapper.find(Dialog).prop('open')).toBe(false);
693 | });
694 | });
695 |
696 | describe('when CapsLock key is recived', () => {
697 | it('CapsLocks Keyboard when is pressed', () => {
698 | const keydownEvent: any = {
699 | key: 'CapsLock',
700 | stopImmediatePropagation: jest.fn(),
701 | stopPropagation: jest.fn(),
702 | preventDefault: jest.fn()
703 | };
704 | expect(wrapper.find({ keyboardKey: 'a' }).length).toBe(1);
705 | expect(wrapper.find({ keyboardKey: 'A' }).length).toBe(0);
706 | EventListenerService.emit('keydown', keydownEvent);
707 | wrapper.update();
708 | expect(wrapper.find({ keyboardKey: 'a' }).length).toBe(0);
709 | expect(wrapper.find({ keyboardKey: 'A' }).length).toBe(1);
710 | });
711 | });
712 |
713 | describe('when symbol key is pressed', () => {
714 | it('should add character to value', () => {
715 | let keydownEvent: any = {
716 | key: '@',
717 | stopImmediatePropagation: jest.fn(),
718 | stopPropagation: jest.fn(),
719 | preventDefault: jest.fn()
720 | };
721 | expect(wrapper.state('value')).toBe('new');
722 | EventListenerService.emit('keydown', keydownEvent);
723 | expect(wrapper.state('value')).toBe('new@');
724 | keydownEvent.key = 't';
725 | EventListenerService.emit('keydown', keydownEvent);
726 | expect(wrapper.state('value')).toBe('new@t');
727 | keydownEvent.key = 's';
728 | EventListenerService.emit('keydown', keydownEvent);
729 | expect(wrapper.state('value')).toBe('new@ts');
730 | keydownEvent.key = '#';
731 | EventListenerService.emit('keydown', keydownEvent);
732 | expect(wrapper.state('value')).toBe('new@ts#');
733 | keydownEvent.key = '9';
734 | EventListenerService.emit('keydown', keydownEvent);
735 | expect(wrapper.state('value')).toBe('new@ts#9');
736 | });
737 | });
738 |
739 | describe('when not supported Special Key is pressed', () => {
740 | it('just stops event propagation', () => {
741 | const keydownEvent: any = {
742 | key: 'TS',
743 | stopImmediatePropagation: jest.fn(),
744 | stopPropagation: jest.fn(),
745 | preventDefault: jest.fn()
746 | };
747 | expect(wrapper.state('value')).toBe('new');
748 | EventListenerService.emit('keydown', keydownEvent);
749 | expect(wrapper.state('value')).toBe('new');
750 | expect(keydownEvent.stopImmediatePropagation).toBeCalled();
751 | expect(keydownEvent.stopPropagation).toBeCalled();
752 | expect(keydownEvent.preventDefault).not.toBeCalled();
753 | });
754 | });
755 | });
756 |
757 | describe('onResize', () => {
758 | function extractTransformTopHelper(transform: string): number {
759 | return Number(transform.match(/translate\(0, (\d+)px\)/)![1]);
760 | }
761 |
762 | beforeEach(() => wrapper.setProps({
763 | textField: textField,
764 | layouts: layouts,
765 | keyboardKeyHeight: 60,
766 | keyboardKeySymbolSize: 32
767 | }));
768 |
769 | it('decreses Keyboard with if window.innerWidth is less than needed', () => {
770 | const { width, maxWidth } = wrapper.find(Dialog).prop('contentStyle') as React.CSSProperties;
771 | const { innerWidth: oldWidth } = window;
772 | (window as any).innerWidth = oldWidth / 10;
773 | EventListenerService.emit('resize');
774 | wrapper.update();
775 | const { width: newWidth, maxWidth: newMaxWidth } = wrapper.find(Dialog).prop('contentStyle') as React.CSSProperties;
776 | expect(newWidth).toBeLessThan(width);
777 | expect(newMaxWidth).toBeLessThan(maxWidth);
778 | const { keyboardKeySymbolSize } = wrapper.find(KeyboardKey as any).first().props();
779 | expect(keyboardKeySymbolSize).toBeLessThan(32);
780 | expect(keyboardKeySymbolSize).toBeGreaterThan(0);
781 | (window as any).innerWidth = oldWidth;
782 | });
783 |
784 | it('decreses Keyboard translate(0, top) if window.innerHeight is less than needed but greater than the need for height resize', () => {
785 | const { height, maxHeight, transform } = wrapper.find(Dialog).prop('contentStyle') as React.CSSProperties;
786 | const { innerHeight: oldHeight } = window;
787 | (window as any).innerHeight = (oldHeight / 2) + 50;
788 | EventListenerService.emit('resize');
789 | wrapper.update();
790 | const { height: newHeight, maxHeight: newMaxHeight, transform: newTransform } = wrapper.find(Dialog).prop('contentStyle') as React.CSSProperties;
791 | expect(newHeight).toEqual(height);
792 | expect(newMaxHeight).toBeLessThan(maxHeight);
793 | expect(extractTransformTopHelper(newTransform)).toBeLessThan(extractTransformTopHelper(transform));
794 | (window as any).innerHeight = oldHeight;
795 | });
796 |
797 | it('decreses Keyboard height if window.innerHeight is less than needed and sets transform to translate(0, 0)', () => {
798 | const { height, maxHeight } = wrapper.find(Dialog).prop('contentStyle') as React.CSSProperties;
799 | const { innerHeight: oldHeight } = window;
800 | (window as any).innerHeight = oldHeight / 2;
801 | EventListenerService.emit('resize');
802 | wrapper.update();
803 | const { height: newHeight, maxHeight: newMaxHeight, transform: newTransform } = wrapper.find(Dialog).prop('contentStyle') as React.CSSProperties;
804 | expect(newHeight).toBeLessThan(height);
805 | expect(newMaxHeight).toBeLessThan(maxHeight);
806 | expect(extractTransformTopHelper(newTransform)).toEqual(0);
807 | (window as any).innerHeight = oldHeight;
808 | });
809 |
810 | it('decreses Keyboard size if window inners* are less than requested with keyboardKey* props', () => {
811 | const { width, maxWidth, height, maxHeight } = wrapper.find(Dialog).prop('contentStyle') as React.CSSProperties;
812 | const { innerHeight: oldHeight, innerWidth: oldWidth } = window;
813 | (window as any).innerWidth = oldWidth / 10;
814 | (window as any).innerHeight = oldHeight / 10;
815 | EventListenerService.emit('resize');
816 | wrapper.update();
817 | const { width: newWidth, maxWidth: newMaxWidth, height: newHeight, maxHeight: newMaxHeight, transform: newTransform } = wrapper.find(Dialog).prop('contentStyle') as React.CSSProperties;
818 | expect(newWidth).toBeLessThan(width);
819 | expect(newMaxWidth).toBeLessThan(maxWidth);
820 | expect(newHeight).toBeLessThan(height);
821 | expect(newMaxHeight).toBeLessThan(maxHeight);
822 | expect(extractTransformTopHelper(newTransform)).toEqual(0);
823 | (window as any).innerWidth = oldWidth;
824 | (window as any).innerHeight = oldHeight;
825 | });
826 |
827 | it('it dose not change Keyboard width if window.innerWidth is greater than needed', () => {
828 | const { width, maxWidth } = wrapper.find(Dialog).prop('contentStyle') as React.CSSProperties;
829 | const { innerWidth: oldWidth } = window;
830 | (window as any).innerWidth = oldWidth * 10;
831 | EventListenerService.emit('resize');
832 | wrapper.update();
833 | const { width: newWidth, maxWidth: newMaxWidth } = wrapper.find(Dialog).prop('contentStyle') as React.CSSProperties;
834 | expect(newWidth).toEqual(width);
835 | expect(newMaxWidth).toBeGreaterThan(maxWidth);
836 | (window as any).innerWidth = oldWidth;
837 | });
838 |
839 | it('it dose not change Keyboard height if window.innerHeight is greater than needed', () => {
840 | const { height, maxHeight, transform } = wrapper.find(Dialog).prop('contentStyle') as React.CSSProperties;
841 | const { innerHeight: oldHeight } = window;
842 | (window as any).innerHeight = oldHeight * 10;
843 | EventListenerService.emit('resize');
844 | wrapper.update();
845 | const { height: newHeight, maxHeight: newMaxHeight, transform: newTransform } = wrapper.find(Dialog).prop('contentStyle') as React.CSSProperties;
846 | expect(newHeight).toEqual(height);
847 | expect(newMaxHeight).toBeGreaterThan(maxHeight);
848 | expect(extractTransformTopHelper(newTransform)).toEqual(extractTransformTopHelper(transform));
849 | (window as any).innerHeight = oldHeight;
850 | });
851 |
852 | it('it dose not change Keyboard size if window inners* are greater than requested with keyboardKey* props', () => {
853 | const { width, maxWidth, height, maxHeight, transform } = wrapper.find(Dialog).prop('contentStyle') as React.CSSProperties;
854 | const { innerHeight: oldHeight, innerWidth: oldWidth } = window;
855 | (window as any).innerWidth = oldWidth * 10;
856 | (window as any).innerHeight = oldHeight * 10;
857 | EventListenerService.emit('resize');
858 | wrapper.update();
859 | const { width: newWidth, maxWidth: newMaxWidth, height: newHeight, maxHeight: newMaxHeight, transform: newTransform } = wrapper.find(Dialog).prop('contentStyle') as React.CSSProperties;
860 | expect(newWidth).toEqual(width);
861 | expect(newMaxWidth).toBeGreaterThan(maxWidth);
862 | expect(newHeight).toEqual(height);
863 | expect(newMaxHeight).toBeGreaterThan(maxHeight);
864 | expect(extractTransformTopHelper(newTransform)).toEqual(extractTransformTopHelper(transform));
865 | const { keyboardKeySymbolSize } = wrapper.find(KeyboardKey as any).first().props();
866 | expect(keyboardKeySymbolSize).toBe(32);
867 | (window as any).innerWidth = oldWidth;
868 | (window as any).innerHeight = oldHeight;
869 | });
870 | });
871 |
872 | describe('makeCorrection', () => {
873 | it('corrects value', () => {
874 | expect(wrapper.state('value')).toBe('new');
875 | (wrapper.instance() as Keyboard).makeCorrection('old');
876 | expect(wrapper.state('value')).toBe('old');
877 | });
878 | });
879 |
880 | describe('when automatic', () => {
881 | describe('onFocus', () => {
882 | describe('when Keyboard.automaitcOpenPredicate return true', () => {
883 | it('sets state.open to true', () => {
884 | expect(wrapper.state('open')).toBe(false);
885 | wrapper.find(TextField).first().simulate('focus');
886 | expect(wrapper.state('open')).toBe(true);
887 | });
888 | });
889 |
890 | describe('when Keyboard.automaitcOpenPredicate return false', () => {
891 | it('sets state.open to true', () => {
892 | Keyboard.automaitcOpenPredicate = () => false;
893 | expect(wrapper.state('open')).toBe(false);
894 | wrapper.find(TextField).first().simulate('focus');
895 | expect(wrapper.state('open')).toBe(false);
896 | });
897 | });
898 | });
899 | });
900 | });
901 | });
--------------------------------------------------------------------------------
/__tests__/KeyboardKey.spec.tsx:
--------------------------------------------------------------------------------
1 | import { KeyboardKey, KeyboardKeyProps, KeyboardKeyPressHandler } from './../src/KeyboardKey';
2 | import * as React from 'react';
3 | import FlatButton from 'material-ui/FlatButton';
4 | import Backspace from 'material-ui/svg-icons/content/backspace';
5 | import Enter from 'material-ui/svg-icons/hardware/keyboard-return';
6 | import Escape from 'material-ui/svg-icons/action/exit-to-app';
7 | import Keyboard from 'material-ui/svg-icons/hardware/keyboard';
8 | import CapsLock from 'material-ui/svg-icons/hardware/keyboard-capslock';
9 | import Spacebar from 'material-ui/svg-icons/editor/space-bar';
10 | import Warning from 'material-ui/svg-icons/alert/warning';
11 | import { shallow, ShallowWrapper } from 'enzyme';
12 | import * as injectTapEventPlugin from 'react-tap-event-plugin';
13 | import getMuiTheme from 'material-ui/styles/getMuiTheme';
14 |
15 | type KeyboardKeyWrapper = ShallowWrapper;
16 |
17 | type IconType = {type: typeof React.Component};
18 |
19 | injectTapEventPlugin();
20 |
21 | const options: any = {
22 | context: {
23 | muiTheme: getMuiTheme()
24 | }
25 | };
26 |
27 | describe('KeyboardKey', () => {
28 | describe('when rendering', () => {
29 | describe('common for all KeyboardKeys', () => {
30 | let wrapper: KeyboardKeyWrapper;
31 |
32 | beforeEach(() => {
33 | const onKeyPress: jest.Mock = jest.fn();
34 | wrapper = shallow(
35 | ,
42 | options
43 | );
44 | });
45 |
46 | it('renders primary FlatButton', () => {
47 | expect(wrapper.is(FlatButton)).toBeTruthy();
48 | expect(wrapper.prop('primary')).toBeTruthy();
49 | });
50 |
51 | it('has onTouchTap handler', () => {
52 | expect(typeof wrapper.prop('onTouchTap')).toBe('function');
53 | });
54 |
55 | it('has keyboardKeyHeight for style.height', () => {
56 | expect((wrapper.prop('style') as React.CSSProperties).height).toEqual(40);
57 | });
58 |
59 | it('has keyboardKeyWidth for style.width', () => {
60 | expect((wrapper.prop('style') as React.CSSProperties).width).toEqual(70);
61 | });
62 |
63 | it('has keyboardKeyWidth for style.minWidth', () => {
64 | expect((wrapper.prop('style') as React.CSSProperties).width).toEqual(70);
65 | });
66 |
67 | it('has enabled disableFocusRipple, disableKeyboardFocus, disableTouchRipple', () => {
68 | expect(wrapper.prop('disableFocusRipple')).toBe(false);
69 | expect(wrapper.prop('disableKeyboardFocus')).toBe(false);
70 | expect(wrapper.prop('disableTouchRipple')).toBe(false);
71 | });
72 | });
73 | describe('disableEffects', () => {
74 | it('has special effects when disableEffects is false', () => {
75 | const onKeyPress: jest.Mock = jest.fn();
76 | const wrapper: KeyboardKeyWrapper = shallow(
77 | ,
84 | options
85 | );
86 |
87 | expect(wrapper.prop('disableFocusRipple')).toBe(false);
88 | expect(wrapper.prop('disableKeyboardFocus')).toBe(false);
89 | expect(wrapper.prop('disableTouchRipple')).toBe(false);
90 | expect(wrapper.prop('hoverColor')).toBeUndefined();
91 | });
92 |
93 | it('has no special effects when disableEffects is true', () => {
94 | const onKeyPress: jest.Mock = jest.fn();
95 | const wrapper: KeyboardKeyWrapper = shallow(
96 | ,
103 | options
104 | );
105 |
106 | expect(wrapper.prop('disableFocusRipple')).toBe(true);
107 | expect(wrapper.prop('disableKeyboardFocus')).toBe(true);
108 | expect(wrapper.prop('disableTouchRipple')).toBe(true);
109 | expect(wrapper.prop('hoverColor')).toBe(options.context.muiTheme.flatButton.color);
110 | });
111 | });
112 | describe('when keyboardKey.length less than or equal to 1 and is not \' \'', () => {
113 | describe('common for all when keyboardKey.length less than or equal to 1 and is not \' \'', () => {
114 | let wrapper: KeyboardKeyWrapper;
115 |
116 | beforeEach(() => {
117 | const onKeyPress: jest.Mock = jest.fn();
118 | wrapper = shallow(
119 | ,
126 | options
127 | );
128 | });
129 |
130 | it('has keyboardKeySymbolSize for labelStyle.fontSize', () => {
131 | expect((wrapper.prop('labelStyle') as React.CSSProperties).fontSize).toBe(20);
132 | });
133 |
134 | it('has \'none\' for labelStyle.textTransform', () => {
135 | expect((wrapper.prop('labelStyle') as React.CSSProperties).textTransform).toBe('none');
136 | });
137 | });
138 |
139 | describe('when keyboardKey.length is 1', () => {
140 | it('has keyboardKey for label', () => {
141 | const onKeyPress: jest.Mock = jest.fn();
142 | let wrapper: KeyboardKeyWrapper = shallow(
143 | ,
150 | options
151 | );
152 | expect(wrapper.prop('label')).toBe('f');
153 | });
154 | });
155 |
156 | describe('when keyboardKey.length is less tnan 1', () => {
157 | let wrapper: KeyboardKeyWrapper;
158 |
159 | beforeEach(() => {
160 | const onKeyPress: jest.Mock = jest.fn();
161 | wrapper = shallow(
162 | ,
169 | options
170 | );
171 | });
172 |
173 | it('is disabled', () => {
174 | expect(wrapper.prop('disabled')).toBeTruthy();
175 | });
176 |
177 | it('has \' \' for a label', () => {
178 | expect(wrapper.prop('label')).toBe(' ');
179 | });
180 | });
181 | });
182 |
183 | describe('when keyboardKey.length is greater than 1 or keyboardKey is \' \'', () => {
184 | describe('common for all when keyboardKey.length is greater than 1 or keyboardKey is \' \'', () => {
185 | let wrapper: KeyboardKeyWrapper;
186 |
187 | beforeEach(() => {
188 | const onKeyPress: jest.Mock = jest.fn();
189 | wrapper = shallow(
190 | ,
197 | options
198 | );
199 | });
200 |
201 | type IconProps = {props: {style: React.CSSProperties}};
202 |
203 | it('has keyboardKeySymbolSize for icon.props.style.width', () => {
204 | expect((wrapper.prop('icon') as IconProps).props.style.width).toBe(20);
205 | });
206 |
207 | it('has keyboardKeySymbolSize for icon.props.style.height', () => {
208 | expect((wrapper.prop('icon') as IconProps).props.style.height).toBe(20);
209 | });
210 | });
211 |
212 | describe('when keyboardKey is \'Enter\'', () => {
213 | it('has Enter for icon', () => {
214 | const onKeyPress: jest.Mock = jest.fn();
215 | let wrapper: KeyboardKeyWrapper = shallow(
216 | ,
223 | options
224 | );
225 | expect((wrapper.prop('icon') as IconType).type).toBe(Enter);
226 | });
227 | });
228 |
229 | describe('when keyboardKey is \'Backspace\'', () => {
230 | it('has Backspace for icon', () => {
231 | const onKeyPress: jest.Mock = jest.fn();
232 | let wrapper: KeyboardKeyWrapper = shallow(
233 | ,
240 | options
241 | );
242 | expect((wrapper.prop('icon') as IconType).type).toBe(Backspace);
243 | });
244 | });
245 |
246 | describe('when keyboardKey is \'Escape\'', () => {
247 | it('has Escape for icon', () => {
248 | const onKeyPress: jest.Mock = jest.fn();
249 | let wrapper: KeyboardKeyWrapper = shallow(
250 | ,
257 | options
258 | );
259 | expect((wrapper.prop('icon') as IconType).type).toBe(Escape);
260 | });
261 | });
262 |
263 | describe('when keyboardKey is \'CapsLock\'', () => {
264 | it('has CapsLock for icon', () => {
265 | const onKeyPress: jest.Mock = jest.fn();
266 | let wrapper: KeyboardKeyWrapper = shallow(
267 | ,
274 | options
275 | );
276 | expect((wrapper.prop('icon') as IconType).type).toBe(CapsLock);
277 | });
278 | });
279 |
280 | describe('when keyboardKey is \'Keyboard\'', () => {
281 | it('has Keyboard for icon', () => {
282 | const onKeyPress: jest.Mock = jest.fn();
283 | let wrapper: KeyboardKeyWrapper = shallow(
284 | ,
291 | options
292 | );
293 | expect((wrapper.prop('icon') as IconType).type).toBe(Keyboard);
294 | });
295 | });
296 |
297 | describe('when keyboardKey is \' \'', () => {
298 | it('has Spacebar for icon', () => {
299 | const onKeyPress: jest.Mock = jest.fn();
300 | let wrapper: KeyboardKeyWrapper = shallow(
301 | ,
308 | options
309 | );
310 | expect((wrapper.prop('icon') as IconType).type).toBe(Spacebar);
311 | });
312 | });
313 |
314 | describe('when keyboardKey is not recognized', () => {
315 | it('has Warning for icon', () => {
316 | const onKeyPress: jest.Mock = jest.fn();
317 | let wrapper: KeyboardKeyWrapper = shallow(
318 | ,
325 | options
326 | );
327 | expect((wrapper.prop('icon') as IconType).type).toBe(Warning);
328 | });
329 | });
330 | });
331 | });
332 |
333 | describe('when shouldComponentUpdate and once touchTap-ed', () => {
334 | let onKeyPress: jest.Mock;
335 | let wrapper: KeyboardKeyWrapper;
336 |
337 | beforeEach(() => {
338 | onKeyPress = jest.fn();
339 | wrapper = shallow(
340 | ,
347 | options
348 | );
349 | });
350 |
351 | describe('when touchTaped', () => {
352 | describe('when keyboardKey length is 1 or it is a supported special key', () => {
353 | it('calls onKeyPress with keyboardKey', () => {
354 | wrapper.simulate('touchTap');
355 | expect(onKeyPress).lastCalledWith('i');
356 | wrapper.setProps({
357 | keyboardKey: 'Enter',
358 | onKeyPress: onKeyPress,
359 | keyboardKeySymbolSize: 16,
360 | keyboardKeyHeight :40,
361 | keyboardKeyWidth: 70,
362 | disableEffects: false
363 | });
364 | wrapper.simulate('touchTap');
365 | expect(onKeyPress).lastCalledWith('Enter');
366 | });
367 | });
368 |
369 | describe('when keyboardKey is not recognized and supported', () => {
370 | it('dose not call onKeyPress', () => {
371 | wrapper.setProps({
372 | keyboardKey: 'TS',
373 | onKeyPress: onKeyPress,
374 | keyboardKeySymbolSize: 16,
375 | keyboardKeyHeight :40,
376 | keyboardKeyWidth: 70,
377 | disableEffects: false
378 | });
379 | wrapper.simulate('touchTap');
380 | expect(onKeyPress).not.toBeCalled();
381 | });
382 | });
383 | });
384 |
385 | describe('when shouldComponentUpdate', () => {
386 | it('re-renders when keyboardKey changes', () => {
387 | expect(wrapper.prop('label')).toEqual('i');
388 | wrapper.setProps({
389 | keyboardKey: 's',
390 | onKeyPress: onKeyPress,
391 | keyboardKeySymbolSize: 16,
392 | keyboardKeyHeight: 40,
393 | keyboardKeyWidth: 70,
394 | disableEffects: false
395 | });
396 | expect(wrapper.prop('label')).toEqual('s');
397 | });
398 |
399 | it('re-renders when onKeyPress changes', () => {
400 | const changedHandler: jest.Mock = jest.fn();
401 | wrapper.simulate('touchTap');
402 | expect(onKeyPress).toBeCalledWith('i');
403 | wrapper.setProps({
404 | keyboardKey: 'i',
405 | onKeyPress: changedHandler,
406 | keyboardKeySymbolSize: 16,
407 | keyboardKeyHeight: 40,
408 | keyboardKeyWidth: 70,
409 | disableEffects: false
410 | });
411 | wrapper.simulate('touchTap');
412 | expect(changedHandler).toBeCalledWith('i');
413 | expect(onKeyPress.call.length).toEqual(1);
414 | });
415 |
416 | it('re-renders when keyboardKeySymbolSize changes', () => {
417 | expect((wrapper.prop('labelStyle') as React.CSSProperties).fontSize).toEqual(16);
418 | wrapper.setProps({
419 | keyboardKey: 'i',
420 | onKeyPress: onKeyPress,
421 | keyboardKeySymbolSize: 20,
422 | keyboardKeyHeight: 40,
423 | keyboardKeyWidth: 70,
424 | disableEffects: false
425 | });
426 | expect((wrapper.prop('labelStyle') as React.CSSProperties).fontSize).toEqual(20);
427 | });
428 |
429 | it('re-renders when keyboardKeyHeight changes', () => {
430 | expect((wrapper.prop('style') as React.CSSProperties).height).toEqual(40);
431 | wrapper.setProps({
432 | keyboardKey: 'i',
433 | onKeyPress: onKeyPress,
434 | keyboardKeySymbolSize: 16,
435 | keyboardKeyHeight: 50,
436 | keyboardKeyWidth: 70,
437 | disableEffects: false
438 | });
439 | expect((wrapper.prop('style') as React.CSSProperties).height).toEqual(50);
440 | });
441 |
442 | it('re-renders when keyboardKeyWidth changes', () => {
443 | expect((wrapper.prop('style') as React.CSSProperties).width).toEqual(70);
444 | wrapper.setProps({
445 | keyboardKey: 'i',
446 | onKeyPress: onKeyPress,
447 | keyboardKeySymbolSize: 16,
448 | keyboardKeyHeight: 40,
449 | keyboardKeyWidth: 60,
450 | disableEffects: false
451 | });
452 | expect((wrapper.prop('style') as React.CSSProperties).width).toEqual(60);
453 | });
454 | it('re-renders when disableEffects changes', () => {
455 | expect(wrapper.prop('disableFocusRipple')).toBe(true);
456 | wrapper.setProps({
457 | keyboardKey: 'i',
458 | onKeyPress: onKeyPress,
459 | keyboardKeySymbolSize: 16,
460 | keyboardKeyHeight: 40,
461 | keyboardKeyWidth: 60,
462 | disableEffects: false
463 | });
464 | expect(wrapper.prop('disableFocusRipple')).toBe(false);
465 | });
466 |
467 | it('dose not re-renders when no prop has changed', () => {
468 | const props: any = wrapper.props();
469 | wrapper.setProps({
470 | keyboardKey: 'i',
471 | onKeyPress: onKeyPress,
472 | keyboardKeySymbolSize: 16,
473 | keyboardKeyHeight: 40,
474 | keyboardKeyWidth: 70,
475 | disableEffects: true
476 | });
477 | expect(wrapper.props()).toBe(props);
478 | });
479 | });
480 | });
481 | });
--------------------------------------------------------------------------------
/__tests__/index.spec.ts:
--------------------------------------------------------------------------------
1 | import ReExportedKeyboard from './../src';
2 | import Keyboard from './../src/Keyboard';
3 | import * as ReExportedEverythingFromKeyboard from './../src';
4 | import * as EverythingFromKeyboard from './../src/Keyboard';
5 |
6 | describe('index', function () {
7 | it('should re-export everything from Keyboard.tsx', function () {
8 | expect(ReExportedEverythingFromKeyboard).toEqual(EverythingFromKeyboard);
9 | });
10 |
11 | it('should re-export default from Keyboard.tsx', function () {
12 | expect(ReExportedKeyboard).toBe(Keyboard);
13 | });
14 | });
--------------------------------------------------------------------------------
/__tests__/layouts.spec.ts:
--------------------------------------------------------------------------------
1 | import {
2 | alphaNumericKeyboard,
3 | extendedKeyboard,
4 | numericKeyboard,
5 | kyeboardCapsLockLayout
6 | } from './../src/layouts';
7 |
8 | describe('layots', function () {
9 | describe('alphaNumericKeyboard', function () {
10 | it('is a keyboard layout containg only numbers and letters symbols, Excape, CapsLock, Backspace and Enter', function () {
11 | expect(alphaNumericKeyboard).toEqual([
12 | ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0'],
13 | ['q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p'],
14 | ['a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', 'Backspace'],
15 | ['Escape', 'CapsLock', 'z', 'x', 'c', 'v', 'b', 'n', 'm', 'Enter']
16 | ]);
17 | });
18 | });
19 |
20 | describe('extendedKeyboard', function () {
21 | it('is a keyboard layout extending alphaNumericKeyboard by adding second CapsLock, -, @, , #, Spacebar and . keys', function () {
22 | expect(extendedKeyboard).toEqual([
23 | ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0'],
24 | ['q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p'],
25 | ['a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', 'Backspace'],
26 | ['CapsLock', 'z', 'x', 'c', 'v', 'b', 'n', 'm', '-', 'CapsLock'],
27 | ['Escape', '@', '#', ' ', '.', 'Enter']
28 | ]);
29 | });
30 | });
31 |
32 | describe('numericKeyboard', function () {
33 | it('is a keyboard layout for inputing numbers only', function () {
34 | expect(numericKeyboard).toEqual([
35 | ['Escape', '-', 'Backspace'],
36 | ['7', '8', '9'],
37 | ['4', '5', '6'],
38 | ['1', '2', '3'],
39 | ['0', '.', 'Enter']
40 | ]);
41 | });
42 | });
43 |
44 | describe('kyeboardCapsLockLayout', function () {
45 | describe('when second argument is true', function () {
46 | it('will upper case all keyboard layout keys which are not special keys (strings with lengh equal to 1)', function () {
47 | expect(kyeboardCapsLockLayout([
48 | ['0', '9'],
49 | ['a', 'z'],
50 | ['а', 'я'],
51 | ['Ю', 'F'],
52 | ['@', '#'],
53 | ['-', '+'],
54 | ['S7s', 't3M'],
55 | ['', '']
56 | ], true)).toEqual([
57 | ['0', '9'],
58 | ['A', 'Z'],
59 | ['А', 'Я'],
60 | ['Ю', 'F'],
61 | ['@', '#'],
62 | ['-', '+'],
63 | ['S7s', 't3M'],
64 | ['', '']
65 | ]);
66 | });
67 |
68 | it('will not upper case keyboard layout keys which are special keys (strings with lengh different from 1)', function () {
69 | expect(kyeboardCapsLockLayout([
70 | ['Escape', 'Backspace'],
71 | ['Enter', 'Alt'],
72 | ['Shift', 'CapsLock'],
73 | ['', 'iv'],
74 | ['S7s', 't3M'],
75 | ], true)).toEqual([
76 | ['Escape', 'Backspace'],
77 | ['Enter', 'Alt'],
78 | ['Shift', 'CapsLock'],
79 | ['', 'iv'],
80 | ['S7s', 't3M'],
81 | ]);
82 | });
83 | });
84 |
85 | describe('when second argument is false', function () {
86 | it('will lower case all keyboard layout keys which are not special keys (strings with lengh equal to 1)', function () {
87 | expect(kyeboardCapsLockLayout([
88 | ['0', '9'],
89 | ['A', 'Z'],
90 | ['А', 'Я'],
91 | ['ю', 'f'],
92 | ['@', '#'],
93 | ['-', '+'],
94 | ['S7s', 't3M'],
95 | ['', '']
96 | ], false)).toEqual([
97 | ['0', '9'],
98 | ['a', 'z'],
99 | ['а', 'я'],
100 | ['ю', 'f'],
101 | ['@', '#'],
102 | ['-', '+'],
103 | ['S7s', 't3M'],
104 | ['', '']
105 | ]);
106 | });
107 |
108 | it('will not lower case keyboard layout keys which are special keys (strings with lengh different from 1)', function () {
109 | expect(kyeboardCapsLockLayout([
110 | ['Escape', 'Backspace'],
111 | ['Enter', 'Alt'],
112 | ['Shift', 'CapsLock'],
113 | ['', 'iv']
114 | ], false)).toEqual([
115 | ['Escape', 'Backspace'],
116 | ['Enter', 'Alt'],
117 | ['Shift', 'CapsLock'],
118 | ['', 'iv']
119 | ]);
120 | });
121 | });
122 | });
123 | });
--------------------------------------------------------------------------------
/example/README.md:
--------------------------------------------------------------------------------
1 | # custom_layouts
2 |
3 | Demonstrates a Keyboard with all layouts beeing custom and how the `'Keyboard'` key can be used to switch between layouts
4 |
5 | # custom_textField
6 |
7 | Demonstrates a custom textField element ('material-ui-number-input') a TextField wrapper
8 |
9 | # no_keyboard
10 |
11 | Demonstrates a Keyboard that never opens, showing that when user don't needs on screen keyboard the input field is just a TextField
12 |
13 | # simple_example
14 |
15 | Demonstrates a Keyboard that opens every time an input field is focused
--------------------------------------------------------------------------------
/example/custom_layouts/README.md:
--------------------------------------------------------------------------------
1 | ```
2 | npm install
3 | npm start
4 | // Open ./demo/index.html in your favorite browser
5 | ```
6 |
--------------------------------------------------------------------------------
/example/custom_layouts/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import TextField from 'material-ui/TextField';
3 | import { Keyboard, InputHandler, KeyboardLayout } from 'react-material-ui-keyboard';
4 | import { render as ReactDomRender } from 'react-dom';
5 | import * as injectTapEventPlugin from 'react-tap-event-plugin';
6 |
7 | interface DemoState {
8 | value?: string;
9 | };
10 |
11 | const layout1: KeyboardLayout = [
12 | ['Escape', 'Keyboard', 'CapsLock', 'Backspace', 'Enter'],
13 | ['a', 'b', 'c', 'd', 'e']
14 | ];
15 |
16 | const layout2: KeyboardLayout = [
17 | ['z', 'x', 'y', 'u', 'q'],
18 | ['Escape', 'Keyboard', 'CapsLock', 'Backspace', 'Enter']
19 | ];
20 |
21 | const layouts: Array = [layout1, layout2];
22 |
23 | class Demo extends React.Component {
24 | private _onInput: InputHandler;
25 |
26 | private _handleInput(input: string): void {
27 | this.setState({ value: input });
28 | }
29 |
30 | public constructor(props: void) {
31 | super(props);
32 | this.state = { value: '' };
33 | this._onInput = this._handleInput.bind(this);
34 | }
35 |
36 | public render(): JSX.Element {
37 | const { state, _onInput } = this;
38 | const { value } = state;
39 | const textField: JSX.Element = (
40 |
44 | );
45 |
46 | return (
47 |
48 |
49 |
58 |
59 | );
60 | }
61 | };
62 |
63 | injectTapEventPlugin();
64 | let bootstrapNode = document.createElement('div');
65 | ReactDomRender(, bootstrapNode);
66 | document.body.appendChild(bootstrapNode);
67 |
--------------------------------------------------------------------------------
/example/custom_layouts/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "main": "./demo/index.html",
3 | "scripts": {
4 | "test": "echo \"Error: no test specified\" && exit 1",
5 | "start": "webpack"
6 | },
7 | "author": "Ivo Stratev",
8 | "license": "MIT",
9 | "dependencies": {
10 | "material-ui": "^0.16.1",
11 | "react": "^15.3.2",
12 | "react-dom": "^15.3.2",
13 | "react-tap-event-plugin": "^1.0.0",
14 | "react-material-ui-keyboard": "^6.1.2",
15 | "@types/node": "^6.0.46",
16 | "@types/react": "^0.14.43",
17 | "@types/react-dom": "^0.14.18",
18 | "@types/react-tap-event-plugin": "^0.0.30",
19 | "@types/material-ui": "^0.16.38"
20 | },
21 | "devDependencies": {
22 | "html-webpack-plugin": "^2.24.0",
23 | "ts-loader": "^0.9.5",
24 | "typescript": "^2.0.6",
25 | "webpack": "^1.13.3"
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/example/custom_layouts/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "commonjs",
4 | "target": "es5",
5 | "noImplicitAny": true,
6 | "strictNullChecks": true,
7 | "noImplicitThis": true,
8 | "noUnusedParameters": true,
9 | "noUnusedLocals": true,
10 | "experimentalDecorators": true,
11 | "jsx": "react"
12 | },
13 | "include": [
14 | "index.tsx"
15 | ],
16 | "exclude": [
17 | "node_modules"
18 | ]
19 | }
20 |
21 |
--------------------------------------------------------------------------------
/example/custom_layouts/webpack.config.js:
--------------------------------------------------------------------------------
1 | const HtmlWebpackPlugin = require('html-webpack-plugin');
2 | const webpack = require('webpack');
3 | const path = require('path');
4 |
5 | module.exports = {
6 | entry: [
7 | "./index.tsx"
8 | ],
9 |
10 | output: {
11 | path: path.join(__dirname, "demo"),
12 | filename: "[name].js",
13 | publicPath: ''
14 | },
15 |
16 | plugins: [
17 | new HtmlWebpackPlugin({ title: "React Material-Ui Keyboard" })
18 | ],
19 |
20 | resolve: {
21 | extensions: ["", ".webpack.js", ".web.js", ".ts", ".tsx", ".js"]
22 | },
23 |
24 | module: {
25 | loaders: [
26 | {
27 | test: /\.tsx?$/,
28 | loaders: ["ts-loader"]
29 | }
30 | ],
31 | }
32 | };
33 |
--------------------------------------------------------------------------------
/example/custom_textField/README.md:
--------------------------------------------------------------------------------
1 | ```
2 | npm install
3 | npm start
4 | // Open ./demo/index.html in your favorite browser
5 | ```
6 |
--------------------------------------------------------------------------------
/example/custom_textField/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { NumberInput, NumberInputChangeHandler, NumberInputError, EventValue, NumberInputErrorHandler, NumberInputValidHandler } from 'material-ui-number-input';
3 | import { Keyboard, RequestCloseHandler, InputHandler } from 'react-material-ui-keyboard';
4 | import { numericKeyboard } from 'react-material-ui-keyboard/layouts';
5 | import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
6 | import getMuiTheme from 'material-ui/styles/getMuiTheme';
7 | import { MuiTheme } from 'material-ui/styles';
8 | import { render as ReactDomRender } from 'react-dom';
9 | import * as injectTapEventPlugin from 'react-tap-event-plugin';
10 |
11 | const { div, link } = React.DOM;
12 |
13 | interface DemoState {
14 | open?: boolean;
15 | value?: string;
16 | errorText?: string;
17 | };
18 |
19 | function corrector(value: string): void {
20 | (this as Keyboard).makeCorrection(value);
21 | }
22 |
23 | class Demo extends React.Component {
24 | private onFocus: React.FocusEventHandler;
25 | private onChange: React.FormEventHandler;
26 | private onRequestClose: RequestCloseHandler;
27 | private onInput: InputHandler;
28 | private onError: NumberInputErrorHandler;
29 | private onValid: NumberInputValidHandler;
30 |
31 | private canOpenKeyboard(): boolean {
32 | return (this.state.value.length % 2) === 0;
33 | }
34 |
35 | private handleFocus(event: React.FocusEvent): void {
36 | if(this.canOpenKeyboard()) {
37 | this.setState({ open: true });
38 | }
39 | }
40 |
41 | private handleChange(event: React.FormEvent, value: string): void {
42 | console.log(value);
43 | this.setState({ value: value });
44 | }
45 |
46 | private handleRequestClose(): void {
47 | this.setState({ open: false });
48 | }
49 |
50 | private handleInput(input: string): void {
51 | console.log(input);
52 | this.setState({ value: input });
53 | }
54 |
55 | private handleError(error: NumberInputError): void {
56 | let errorText: string;
57 | switch(error) {
58 | case 'required':
59 | errorText = 'This field is required';
60 | break;
61 | case 'invalidSymbol':
62 | errorText = 'You are tring to enter none number symbol';
63 | break;
64 | case 'incompleteNumber':
65 | errorText = 'Number is incomplete';
66 | break;
67 | case 'singleMinus':
68 | errorText = 'Minus can be use only for negativity';
69 | break;
70 | case 'singleFloatingPoint':
71 | errorText = 'There is already a floating point';
72 | break;
73 | case 'singleZero':
74 | errorText = 'Floating point is expected';
75 | break;
76 | case 'min':
77 | errorText = 'You are tring to enter number less than -10';
78 | break;
79 | case 'max':
80 | errorText = 'You are tring to enter number greater than 120';
81 | break;
82 | }
83 | this.setState({ errorText: errorText });
84 | }
85 |
86 | private handleValid(value: number): void {
87 | console.debug(`valid ${value}`);
88 | }
89 |
90 | public constructor(props: void) {
91 | super(props);
92 | this.state = { open: false, value: '' };
93 | this.onFocus = this.handleFocus.bind(this);
94 | this.onChange = this.handleChange.bind(this);
95 | this.onRequestClose = this.handleRequestClose.bind(this);
96 | this.onInput = this.handleInput.bind(this);
97 | this.onError = this.handleError.bind(this);
98 | this.onValid = this.handleValid.bind(this);
99 | }
100 |
101 | public componentDidMount(): void {
102 | setTimeout(() => this.setState({ value: '89' }), 1000);
103 | }
104 |
105 | public render(): JSX.Element {
106 | const { state, onFocus, onChange, onError, onValid } = this;
107 | const { value, errorText } = state;
108 | const textField: JSX.Element = (
109 |
122 | );
123 |
124 | return (
125 |
126 |
127 |
139 |
140 | );
141 | }
142 | };
143 |
144 | injectTapEventPlugin();
145 | let bootstrapNode = document.createElement('div');
146 | ReactDomRender(, bootstrapNode);
147 | document.body.appendChild(bootstrapNode);
148 |
--------------------------------------------------------------------------------
/example/custom_textField/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "main": "./demo/index.html",
3 | "scripts": {
4 | "test": "echo \"Error: no test specified\" && exit 1",
5 | "postinstall": "typings install",
6 | "start": "webpack"
7 | },
8 | "author": "Ivo Stratev",
9 | "license": "MIT",
10 | "dependencies": {
11 | "material-ui": "^0.15.4",
12 | "react": "^15.3.1",
13 | "react-dom": "^15.3.1",
14 | "react-tap-event-plugin": "^1.0.0",
15 | "react-material-ui-keyboard": "^6.1.0",
16 | "material-ui-number-input": "^5.0.8"
17 | },
18 | "devDependencies": {
19 | "html-webpack-plugin": "^2.22.0",
20 | "ts-loader": "^0.8.2",
21 | "typescript": "^1.8.10",
22 | "typings": "^1.3.3",
23 | "webpack": "^1.13.2"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/example/custom_textField/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "commonjs",
4 | "moduleResolution": "node",
5 | "target": "es5",
6 | "jsx": "react",
7 | "noImplicitAny": true
8 | },
9 | "files": [
10 | "typings/index.d.ts",
11 | "index.tsx"
12 | ],
13 | "exclude": [
14 | "node_modules"
15 | ]
16 | }
17 |
18 |
--------------------------------------------------------------------------------
/example/custom_textField/typings.json:
--------------------------------------------------------------------------------
1 | {
2 | "globalDependencies": {
3 | "node": "registry:dt/node",
4 | "react": "registry:dt/react",
5 | "react-dom": "registry:dt/react-dom",
6 | "react-tap-event-plugin": "registry:dt/react-tap-event-plugin",
7 | "material-ui": "registry:dt/material-ui"
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/example/custom_textField/webpack.config.js:
--------------------------------------------------------------------------------
1 | const HtmlWebpackPlugin = require('html-webpack-plugin');
2 | const webpack = require('webpack');
3 | const path = require('path');
4 |
5 | module.exports = {
6 | entry: [
7 | "./index.tsx"
8 | ],
9 |
10 | output: {
11 | path: path.join(__dirname, "demo"),
12 | filename: "[name].js",
13 | publicPath: ''
14 | },
15 |
16 | plugins: [
17 | new HtmlWebpackPlugin({ title: "React Material-Ui Keyboard" })
18 | ],
19 |
20 | resolve: {
21 | extensions: ["", ".webpack.js", ".web.js", ".ts", ".tsx", ".js"]
22 | },
23 |
24 | module: {
25 | loaders: [
26 | {
27 | test: /\.tsx?$/,
28 | loaders: ["ts-loader"]
29 | }
30 | ],
31 | }
32 | };
33 |
--------------------------------------------------------------------------------
/example/no_keyboard/README.md:
--------------------------------------------------------------------------------
1 | ```
2 | npm install
3 | npm start
4 | // Open ./demo/index.html in your favorite browser
5 | ```
6 |
--------------------------------------------------------------------------------
/example/no_keyboard/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import TextField from 'material-ui/TextField';
3 | import { Keyboard, RequestCloseHandler, InputHandler } from 'react-material-ui-keyboard';
4 | import { extendedKeyboard } from 'react-material-ui-keyboard/layouts';
5 | import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
6 | import getMuiTheme from 'material-ui/styles/getMuiTheme';
7 | import { MuiTheme } from 'material-ui/styles';
8 | import { render as ReactDomRender } from 'react-dom';
9 | import * as injectTapEventPlugin from 'react-tap-event-plugin';
10 |
11 | const { div, link } = React.DOM;
12 |
13 | interface DemoState {
14 | value?: string;
15 | }
16 |
17 | interface TextEnterTarget {
18 | value?: string;
19 | }
20 |
21 | interface TextEnterEvent {
22 | target: TextEnterTarget;
23 | }
24 |
25 | class Demo extends React.Component {
26 | private _onChange: React.FormEventHandler;
27 |
28 | private _handleChange(event: React.FormEvent): void {
29 | const textEnterEvent: TextEnterEvent = event as TextEnterEvent;
30 | const value: string = textEnterEvent.target.value;
31 | this.setState({ value: value });
32 | }
33 |
34 | public constructor(props: void) {
35 | super(props);
36 | this.state = { value: '' };
37 | this._onChange = this._handleChange.bind(this);
38 | }
39 |
40 | public render(): JSX.Element {
41 | const { state, _onChange } = this;
42 | const { value } = state;
43 | const textField: JSX.Element = (
44 |
49 | );
50 |
51 | return (
52 |
53 |
54 |
59 |
60 | );
61 | }
62 | };
63 |
64 | injectTapEventPlugin();
65 | let bootstrapNode = document.createElement('div');
66 | ReactDomRender(, bootstrapNode);
67 | document.body.appendChild(bootstrapNode);
68 |
--------------------------------------------------------------------------------
/example/no_keyboard/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "main": "./demo/index.html",
3 | "scripts": {
4 | "test": "echo \"Error: no test specified\" && exit 1",
5 | "postinstall": "typings install",
6 | "start": "webpack"
7 | },
8 | "author": "Ivo Stratev",
9 | "license": "MIT",
10 | "dependencies": {
11 | "material-ui": "^0.15.4",
12 | "react": "^15.3.1",
13 | "react-dom": "^15.3.1",
14 | "react-tap-event-plugin": "^1.0.0",
15 | "react-material-ui-keyboard": "^6.1.0"
16 | },
17 | "devDependencies": {
18 | "html-webpack-plugin": "^2.22.0",
19 | "ts-loader": "^0.8.2",
20 | "typescript": "^1.8.10",
21 | "typings": "^1.3.3",
22 | "webpack": "^1.13.2"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/example/no_keyboard/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "commonjs",
4 | "moduleResolution": "node",
5 | "target": "es5",
6 | "jsx": "react",
7 | "noImplicitAny": true
8 | },
9 | "files": [
10 | "typings/index.d.ts",
11 | "index.tsx"
12 | ],
13 | "exclude": [
14 | "node_modules"
15 | ]
16 | }
17 |
18 |
--------------------------------------------------------------------------------
/example/no_keyboard/typings.json:
--------------------------------------------------------------------------------
1 | {
2 | "globalDependencies": {
3 | "node": "registry:dt/node",
4 | "react": "registry:dt/react",
5 | "react-dom": "registry:dt/react-dom",
6 | "react-tap-event-plugin": "registry:dt/react-tap-event-plugin",
7 | "material-ui": "registry:dt/material-ui"
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/example/no_keyboard/webpack.config.js:
--------------------------------------------------------------------------------
1 | const HtmlWebpackPlugin = require('html-webpack-plugin');
2 | const webpack = require('webpack');
3 | const path = require('path');
4 |
5 | module.exports = {
6 | entry: [
7 | "./index.tsx"
8 | ],
9 |
10 | output: {
11 | path: path.join(__dirname, "demo"),
12 | filename: "[name].js",
13 | publicPath: ''
14 | },
15 |
16 | plugins: [
17 | new HtmlWebpackPlugin({ title: "React Material-Ui Keyboard" })
18 | ],
19 |
20 | resolve: {
21 | extensions: ["", ".webpack.js", ".web.js", ".ts", ".tsx", ".js"]
22 | },
23 |
24 | module: {
25 | loaders: [
26 | {
27 | test: /\.tsx?$/,
28 | loaders: ["ts-loader"]
29 | }
30 | ],
31 | }
32 | };
33 |
--------------------------------------------------------------------------------
/example/simple_usage/README.md:
--------------------------------------------------------------------------------
1 | ```
2 | npm install
3 | npm start
4 | // Open ./demo/index.html in your favorite browser
5 | ```
6 |
--------------------------------------------------------------------------------
/example/simple_usage/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import TextField from 'material-ui/TextField';
3 | import { Keyboard, RequestCloseHandler, InputHandler } from 'react-material-ui-keyboard';
4 | import { extendedKeyboard } from 'react-material-ui-keyboard/layouts';
5 | import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
6 | import getMuiTheme from 'material-ui/styles/getMuiTheme';
7 | import { MuiTheme } from 'material-ui/styles';
8 | import { render as ReactDomRender } from 'react-dom';
9 | import * as injectTapEventPlugin from 'react-tap-event-plugin';
10 |
11 | const { div, link } = React.DOM;
12 |
13 | interface DemoState {
14 | value?: string;
15 | };
16 |
17 | interface TextEnterTarget {
18 | value?: string;
19 | };
20 |
21 | interface TextEnterEvent {
22 | target: TextEnterTarget;
23 | };
24 |
25 | class Demo extends React.Component {
26 | private _onInput: InputHandler;
27 |
28 | private _handleInput(input: string): void {
29 | this.setState({ value: input });
30 | }
31 |
32 | public constructor(props: void) {
33 | super(props);
34 | this.state = { value: '' };
35 | this._onInput = this._handleInput.bind(this);
36 | }
37 |
38 | public render(): JSX.Element {
39 | const { state, _onInput } = this;
40 | const { value } = state;
41 | const textField: JSX.Element = (
42 |
46 | );
47 |
48 | return (
49 |
50 |
51 |
57 |
58 | );
59 | }
60 | };
61 |
62 | injectTapEventPlugin();
63 | let bootstrapNode = document.createElement('div');
64 | ReactDomRender(, bootstrapNode);
65 | document.body.appendChild(bootstrapNode);
66 |
--------------------------------------------------------------------------------
/example/simple_usage/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "main": "./demo/index.html",
3 | "scripts": {
4 | "test": "echo \"Error: no test specified\" && exit 1",
5 | "postinstall": "typings install",
6 | "start": "webpack"
7 | },
8 | "author": "Ivo Stratev",
9 | "license": "MIT",
10 | "dependencies": {
11 | "material-ui": "^0.15.4",
12 | "react": "^15.3.1",
13 | "react-dom": "^15.3.1",
14 | "react-tap-event-plugin": "^1.0.0",
15 | "react-material-ui-keyboard": "^6.1.0"
16 | },
17 | "devDependencies": {
18 | "html-webpack-plugin": "^2.22.0",
19 | "ts-loader": "^0.8.2",
20 | "typescript": "^1.8.10",
21 | "typings": "^1.3.3",
22 | "webpack": "^1.13.2"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/example/simple_usage/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "commonjs",
4 | "moduleResolution": "node",
5 | "target": "es5",
6 | "jsx": "react",
7 | "noImplicitAny": true
8 | },
9 | "files": [
10 | "typings/index.d.ts",
11 | "index.tsx"
12 | ],
13 | "exclude": [
14 | "node_modules"
15 | ]
16 | }
17 |
18 |
--------------------------------------------------------------------------------
/example/simple_usage/typings.json:
--------------------------------------------------------------------------------
1 | {
2 | "globalDependencies": {
3 | "node": "registry:dt/node",
4 | "react": "registry:dt/react",
5 | "react-dom": "registry:dt/react-dom",
6 | "react-tap-event-plugin": "registry:dt/react-tap-event-plugin",
7 | "material-ui": "registry:dt/material-ui"
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/example/simple_usage/webpack.config.js:
--------------------------------------------------------------------------------
1 | const HtmlWebpackPlugin = require('html-webpack-plugin');
2 | const webpack = require('webpack');
3 | const path = require('path');
4 |
5 | module.exports = {
6 | entry: [
7 | "./index.tsx"
8 | ],
9 |
10 | output: {
11 | path: path.join(__dirname, "demo"),
12 | filename: "[name].js",
13 | publicPath: ''
14 | },
15 |
16 | plugins: [
17 | new HtmlWebpackPlugin({ title: "React Material-Ui Keyboard" })
18 | ],
19 |
20 | resolve: {
21 | extensions: ["", ".webpack.js", ".web.js", ".ts", ".tsx", ".js"]
22 | },
23 |
24 | module: {
25 | loaders: [
26 | {
27 | test: /\.tsx?$/,
28 | loaders: ["ts-loader"]
29 | }
30 | ],
31 | }
32 | };
33 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-material-ui-keyboard",
3 | "version": "6.2.0",
4 | "description": "Virtual keyboard for TextField when needed.",
5 | "main": "index.js",
6 | "typings": "index.d.ts",
7 | "scripts": {
8 | "test": "tsc && jest --verbose",
9 | "jest-cov": "tsc --sourceMap -p . && jest --verbose --coverage --testPathPattern __tests__/.+.spec.js",
10 | "map-cov": "remap-istanbul -i ./coverage/coverage-final.json -t lcovonly -o ./lcov.info",
11 | "report-cov": "codeclimate-test-reporter < ./lcov.info",
12 | "coverage": "npm run jest-cov && npm run map-cov && npm run report-cov",
13 | "start": "webpack && node ./server.js",
14 | "npm": "tsc -d -p . && mv ./src/*.js . && mv ./src/*.d.ts .",
15 | "clean": "npm run clean:some && npm run clean:rest",
16 | "clean:some": "rm -f coverage ./src/*.map ./src/*.d.ts ./Dev/*.d.ts ./__tests__/*.d.ts",
17 | "clean:rest": "rm -f ./index* ./layouts* ./Keyboard* ./__tests__/*.js ./src/*.js ./Dev/*.js main.js lcov.info"
18 | },
19 | "keywords": [
20 | "react",
21 | "material-ui",
22 | "keyboard",
23 | "on-screen-keyboard",
24 | "virtual-keyboard"
25 | ],
26 | "author": "Ivo Stratev",
27 | "license": "MIT",
28 | "repository": {
29 | "type": "git",
30 | "url": "git+https://github.com/NoHomey/react-material-ui-keyboard.git"
31 | },
32 | "peerDependencies": {
33 | "material-ui": "^0.18.6",
34 | "react": "15.6.1",
35 | "react-dom": "15.6.1",
36 | "react-tap-event-plugin": "^2.0.1"
37 | },
38 | "devDependencies": {
39 | "@types/deep-equal": "^1.0.0",
40 | "@types/enzyme": "^2.8.3",
41 | "@types/jest": "^20.0.4",
42 | "@types/material-ui": "^0.17.17",
43 | "@types/node": "^8.0.14",
44 | "@types/object-assign": "^4.0.30",
45 | "@types/prop-types": "^15.5.1",
46 | "@types/react": "^15.0.38",
47 | "@types/react-dom": "^15.5.1",
48 | "@types/react-tap-event-plugin": "^0.0.30",
49 | "codeclimate-test-reporter": "^0.5.0",
50 | "enzyme": "^2.9.1",
51 | "express": "^4.15.3",
52 | "html-webpack-plugin": "^2.29.0",
53 | "jest": "^20.0.4",
54 | "material-ui": "^0.18.6",
55 | "react": "15.6.1",
56 | "react-addons-test-utils": "15.6.0",
57 | "react-dom": "15.6.1",
58 | "react-hot-loader": "^1.3.1",
59 | "react-tap-event-plugin": "^2.0.1",
60 | "remap-istanbul": "^0.9.5",
61 | "ts-loader": "^2.3.1",
62 | "typescript": "^2.4.2",
63 | "webpack": "^2.3.3",
64 | "webpack-dev-middleware": "^1.11.0",
65 | "webpack-hot-middleware": "^2.18.2"
66 | },
67 | "dependencies": {
68 | "bind-decorator": "^1.0.11",
69 | "deep-equal": "^1.0.1",
70 | "event-listener-service": "^1.0.1",
71 | "object-assign": "^4.1.1",
72 | "prop-types": "^15.5.10"
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/screenshots/alphanumeric.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NoHomey/react-material-ui-keyboard/d4f1eb31bb5a6ff264055c4d9effcc88a1a30c6c/screenshots/alphanumeric.png
--------------------------------------------------------------------------------
/screenshots/capsed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NoHomey/react-material-ui-keyboard/d4f1eb31bb5a6ff264055c4d9effcc88a1a30c6c/screenshots/capsed.png
--------------------------------------------------------------------------------
/screenshots/extended.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NoHomey/react-material-ui-keyboard/d4f1eb31bb5a6ff264055c4d9effcc88a1a30c6c/screenshots/extended.png
--------------------------------------------------------------------------------
/screenshots/iphone6_landscape.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NoHomey/react-material-ui-keyboard/d4f1eb31bb5a6ff264055c4d9effcc88a1a30c6c/screenshots/iphone6_landscape.png
--------------------------------------------------------------------------------
/screenshots/iphone6_portrait.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NoHomey/react-material-ui-keyboard/d4f1eb31bb5a6ff264055c4d9effcc88a1a30c6c/screenshots/iphone6_portrait.png
--------------------------------------------------------------------------------
/screenshots/keyboard_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NoHomey/react-material-ui-keyboard/d4f1eb31bb5a6ff264055c4d9effcc88a1a30c6c/screenshots/keyboard_icon.png
--------------------------------------------------------------------------------
/screenshots/numeric.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NoHomey/react-material-ui-keyboard/d4f1eb31bb5a6ff264055c4d9effcc88a1a30c6c/screenshots/numeric.png
--------------------------------------------------------------------------------
/screenshots/screen_1200x600.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NoHomey/react-material-ui-keyboard/d4f1eb31bb5a6ff264055c4d9effcc88a1a30c6c/screenshots/screen_1200x600.png
--------------------------------------------------------------------------------
/screenshots/screen_200x100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NoHomey/react-material-ui-keyboard/d4f1eb31bb5a6ff264055c4d9effcc88a1a30c6c/screenshots/screen_200x100.png
--------------------------------------------------------------------------------
/screenshots/screen_600x300.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NoHomey/react-material-ui-keyboard/d4f1eb31bb5a6ff264055c4d9effcc88a1a30c6c/screenshots/screen_600x300.png
--------------------------------------------------------------------------------
/screenshots/show.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NoHomey/react-material-ui-keyboard/d4f1eb31bb5a6ff264055c4d9effcc88a1a30c6c/screenshots/show.png
--------------------------------------------------------------------------------
/screenshots/size.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NoHomey/react-material-ui-keyboard/d4f1eb31bb5a6ff264055c4d9effcc88a1a30c6c/screenshots/size.png
--------------------------------------------------------------------------------
/screenshots/textField.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NoHomey/react-material-ui-keyboard/d4f1eb31bb5a6ff264055c4d9effcc88a1a30c6c/screenshots/textField.png
--------------------------------------------------------------------------------
/server.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 | var express = require('express');
3 | var webpack = require('webpack');
4 | var config = require('./webpack.config');
5 |
6 | config.plugins.pop();
7 |
8 | var app = express();
9 | var compiler = webpack(config);
10 |
11 | app.use(require('webpack-dev-middleware')(compiler, {
12 | noInfo: true,
13 | publicPath: config.output.publicPath
14 | }));
15 |
16 | app.use(require('webpack-hot-middleware')(compiler));
17 |
18 | app.get('*', function (req, res) {
19 | res.sendFile(path.join(__dirname, 'index.html'));
20 | });
21 |
22 | app.listen(3000, function (err) {
23 | if (err) {
24 | console.log(err);
25 | return;
26 | }
27 |
28 | console.log('Listening at http://localhost:3000');
29 | });
--------------------------------------------------------------------------------
/src/ActiveElement.ts:
--------------------------------------------------------------------------------
1 | export class ActiveElement {
2 | public static isInput: () => boolean;
3 | public static blur: () => void;
4 | }
5 |
6 | export default ActiveElement;
--------------------------------------------------------------------------------
/src/Keyboard.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import * as PropTypes from 'prop-types';
3 | import bind from 'bind-decorator';
4 | import Dialog from 'material-ui/Dialog';
5 | import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
6 | import { KeyboardKey, KeyboardKeyProps } from './KeyboardKey';
7 | import { KeyboardLayout, kyeboardCapsLockLayout } from './layouts';
8 | import { MuiTheme } from 'material-ui/styles';
9 | import getMuiTheme from 'material-ui/styles/getMuiTheme';
10 | import EventListenerService from 'event-listener-service';
11 | import ActiveElement from './ActiveElement';
12 | import objectAssign = require('object-assign');
13 | import deepEqual = require('deep-equal');
14 |
15 | export { KeyboardLayout };
16 |
17 | export type RequestCloseHandler = () => void;
18 |
19 | export type InputHandler = (input: string) => void;
20 |
21 | export interface TextFieldRequiredProps {
22 | style?: React.CSSProperties;
23 | readOnly: boolean;
24 | value: string;
25 | onFocus?: React.FocusEventHandler;
26 | }
27 |
28 | export interface TextFieldAccessedProps extends TextFieldRequiredProps {
29 | rows?: number;
30 | floatingLabelText?: string;
31 | }
32 |
33 | export type TextFieldElement = React.ReactElement;
34 |
35 | export type CreatableTextField = React.ComponentClass;
36 |
37 | export type KeyboardRow = React.ReactElement;
38 |
39 | export type KeyboardRowKey = React.ReactElement;
40 |
41 | export interface KeyboardProps {
42 | open?: boolean;
43 | automatic?: boolean;
44 | layouts: Array;
45 | keyboardKeyWidth?: number;
46 | keyboardKeyHeight?: number;
47 | keyboardKeySymbolSize?: number;
48 | textField: TextFieldElement;
49 | onRequestClose?: RequestCloseHandler;
50 | onInput?: InputHandler;
51 | onInputValueChange?: InputHandler;
52 | correctorName?: string;
53 | corrector?: Function;
54 | disableEffects?: boolean;
55 | }
56 |
57 | export interface KeyboardState {
58 | value?: string;
59 | layout?: number;
60 | capsLock?: boolean;
61 | open?: boolean;
62 | };
63 |
64 | export interface KeyboardContext {
65 | muiTheme?: MuiTheme;
66 | }
67 |
68 | export type AutomaitcOpenPredicate = () => boolean;
69 |
70 | namespace constants {
71 | export const minusOne: number = -1;
72 | export const zero: number = 0;
73 | export const one: number = 1;
74 | export const two: number = 2;
75 | export const three: number = 3;
76 | export const four: number = 4;
77 | export const sixteen: number = 16;
78 | export const twentyFour: number = 24;
79 | export const fourtyEight: number = 48;
80 | export const seventyTwo: number = 72;
81 | export const emptyString: string = '';
82 | export const space: string = ' ';
83 | export const keydown: string = 'keydown';
84 | export const resize: string = 'resize';
85 | export const input: string = 'input';
86 | export const fullWidth: string = '100%';
87 | export const typeofString: string = 'string';
88 | export const typeofNumber: string = 'number';
89 | export const typeofFunction: string = 'function';
90 | export const boolTrue: boolean = true;
91 | export const boolFalse: boolean = false;
92 | export const isSpaceBar: RegExp = /^\ +$/;
93 | export const strictCompare: { strict: boolean } = { strict: boolTrue };
94 | }
95 |
96 | function allwaysTruePredicate(): boolean {
97 | return constants.boolTrue;
98 | }
99 |
100 | EventListenerService.setImplementation({
101 | addListener: window.addEventListener.bind(window),
102 | removeListener: window.removeEventListener.bind(window)
103 | });
104 |
105 | ActiveElement.isInput = () => document.activeElement.tagName.toLowerCase() === constants.input;
106 | ActiveElement.blur = () => (document.activeElement as HTMLElement).blur();
107 |
108 | export class Keyboard extends React.Component {
109 | private static calculatedTextFieldHeight(props: TextFieldAccessedProps): number {
110 | const { rows, floatingLabelText } = props;
111 | const normalHeight: number = floatingLabelText ? constants.seventyTwo : constants.fourtyEight;
112 | return (rows ? ((rows - constants.one) * constants.twentyFour) : constants.zero) + normalHeight;
113 | }
114 |
115 | public static getSupportedSpecialKeys(): Array {
116 | return Keyboard.supportedSpecialKeys;
117 | }
118 |
119 | private static supportedSpecialKeys: Array = ['Enter', 'Backspace', 'Escape', 'CapsLock', 'Keyboard'];
120 | private static overwrittenProps: Array = ['onChange', 'onFocus', 'onBlur', 'onKeyUp', 'onKeyDown', 'onKeyPress'];
121 | private static noStyleHeight: React.CSSProperties = {
122 | minHeight: constants.zero,
123 | height: constants.zero,
124 | maxHeight: constants.zero
125 | };
126 | public static propTypes: React.ValidationMap = {
127 | open: PropTypes.bool,
128 | automatic: PropTypes.bool,
129 | layouts: PropTypes.arrayOf(PropTypes.arrayOf(PropTypes.arrayOf(PropTypes.string))).isRequired,
130 | keyboardKeyWidth: PropTypes.number,
131 | keyboardKeyHeight: PropTypes.number,
132 | keyboardKeySymbolSize: PropTypes.number,
133 | textField: PropTypes.element.isRequired,
134 | onRequestClose: PropTypes.func,
135 | onInput: PropTypes.func,
136 | onInputValueChange: PropTypes.func,
137 | correctorName: PropTypes.string,
138 | corrector: PropTypes.func,
139 | disableEffects: PropTypes.bool
140 | };
141 | public static contextTypes: any = { muiTheme: PropTypes.object };
142 | public static automaitcOpenPredicate: AutomaitcOpenPredicate = allwaysTruePredicate;
143 | public context: KeyboardContext;
144 | private corrector: Function;
145 |
146 | @bind
147 | private onInputValueChange(): void {
148 | if(typeof this.props.onInputValueChange === constants.typeofFunction) {
149 | this.props.onInputValueChange!(this.state.value!);
150 | }
151 | }
152 |
153 | private setValue(value: string): void {
154 | if(this.state.value !== value) {
155 | this.setState({ value: value }, this.onInputValueChange);
156 | }
157 | }
158 |
159 | private syncValue(value: string): void {
160 | if(value !== this.state.value) {
161 | this.setValue(value);
162 | }
163 | }
164 |
165 | private setAutomaitcOpen(open: boolean): void {
166 | this.setState({ open: open });
167 | }
168 |
169 | private requestClose(): void {
170 | const { automatic, onRequestClose } = this.props;
171 | if(automatic) {
172 | this.setAutomaitcOpen(constants.boolFalse);
173 | } else if(onRequestClose) {
174 | onRequestClose();
175 | }
176 | }
177 |
178 | @bind
179 | private onFocus(): void {
180 | if(Keyboard.automaitcOpenPredicate()) {
181 | this.setAutomaitcOpen(constants.boolTrue);
182 | }
183 | }
184 |
185 | @bind
186 | private onKeyboard(key: string): void {
187 | const { supportedSpecialKeys } = Keyboard;
188 | const { props, state } = this;
189 | const { onInput, layouts: propsLayout } = props;
190 | const { capsLock, layout } = state;
191 | const value: string = state.value!;
192 | switch(key) {
193 | case supportedSpecialKeys[constants.zero]:
194 | if(onInput) {
195 | onInput(value);
196 | }
197 | return this.requestClose();
198 | case supportedSpecialKeys[constants.one]:
199 | return this.setValue(value.substring(constants.zero, value.length - constants.one));
200 | case supportedSpecialKeys[constants.two]:
201 | return this.requestClose();
202 | case supportedSpecialKeys[constants.three]: return this.setState({ capsLock: !capsLock });
203 | case supportedSpecialKeys[constants.four]:
204 | return this.setState({
205 | layout:
206 | (layout === (propsLayout.length - constants.one))
207 | ? constants.zero : layout! + constants.one
208 | });
209 | default: return this.setValue(value + key);
210 | }
211 | }
212 |
213 | @bind
214 | private onKeyDown(event: KeyboardEvent): void {
215 | const { key } = event;
216 | event.stopImmediatePropagation();
217 | event.stopPropagation();
218 | if((key.length === constants.one) || (Keyboard.getSupportedSpecialKeys().indexOf(key) !== constants.minusOne)) {
219 | event.preventDefault();
220 | this.onKeyboard(key);
221 | }
222 | }
223 |
224 | @bind
225 | private onResize(): void {
226 | this.forceUpdate();
227 | }
228 |
229 | public makeCorrection(value: string): void {
230 | this.setValue(value);
231 | }
232 |
233 | public constructor(props: KeyboardProps, context: KeyboardContext) {
234 | super(props, context);
235 | this.state = {
236 | value: constants.emptyString,
237 | layout: constants.zero,
238 | capsLock: constants.boolFalse,
239 | open: constants.boolFalse
240 | };
241 | this.context = context;
242 | }
243 |
244 | public componentWillMount(): void {
245 | const { corrector } = this.props;
246 | if(typeof corrector === constants.typeofFunction) {
247 | this.corrector = corrector!.bind(this);
248 | }
249 | }
250 |
251 | public componentDidMount(): void {
252 | EventListenerService.addListener(constants.resize, this.onResize, constants.boolFalse);
253 | this.syncValue(this.props.textField.props.value);
254 | }
255 |
256 | public shouldComponentUpdate(props: KeyboardProps, state: KeyboardState): boolean {
257 | const { textField } = props;
258 | const { textField: thisTextField } = this.props;
259 | if(this.state.value !== state.value) {
260 | return constants.boolTrue;
261 | }
262 | if(this.state.open !== state.open) {
263 | return constants.boolTrue;
264 | }
265 | if(this.state.capsLock !== state.capsLock) {
266 | return constants.boolTrue;
267 | }
268 | if(this.state.layout !== state.layout) {
269 | return constants.boolTrue;
270 | }
271 | if(this.props.open !== props.open) {
272 | return constants.boolTrue;
273 | }
274 | if(this.props.keyboardKeyHeight !== props.keyboardKeyHeight) {
275 | return constants.boolTrue;
276 | }
277 | if(this.props.keyboardKeySymbolSize !== props.keyboardKeySymbolSize) {
278 | return constants.boolTrue;
279 | }
280 | if(this.props.keyboardKeyWidth !== props.keyboardKeyWidth) {
281 | return constants.boolTrue;
282 | }
283 | if(this.props.automatic !== props.automatic) {
284 | return constants.boolTrue;
285 | }
286 | if(this.props.disableEffects !== props.disableEffects) {
287 | return constants.boolTrue;
288 | }
289 | if(this.props.correctorName !== props.correctorName) {
290 | return constants.boolTrue;
291 | }
292 | if(this.props.corrector !== props.corrector) {
293 | return constants.boolTrue;
294 | }
295 | if(this.props.onInput !== props.onInput) {
296 | return constants.boolTrue;
297 | }
298 | if(this.props.onRequestClose !== props.onRequestClose) {
299 | return constants.boolTrue;
300 | }
301 | if(thisTextField.type !== textField.type) {
302 | return constants.boolTrue;
303 | }
304 | if(!deepEqual(this.props.layouts, props.layouts, constants.strictCompare)) {
305 | return constants.boolTrue;
306 | }
307 | if(!deepEqual(thisTextField.props, textField.props, constants.strictCompare)) {
308 | return constants.boolTrue;
309 | }
310 |
311 | return constants.boolFalse;
312 | }
313 |
314 | public componentWillReceiveProps(props: KeyboardProps): void {
315 | this.syncValue(props.textField.props.value);
316 | if(this.props.corrector !== props.corrector) {
317 | this.corrector = props.corrector!.bind(this);
318 | }
319 | }
320 |
321 | public componentDidUpdate(props: KeyboardProps, state: KeyboardState): void {
322 | const { automatic } = this.props;
323 | const open: boolean = automatic ? this.state.open! : this.props.open!;
324 | const prev: boolean = automatic ? state.open! : props.open!;
325 | if(open !== prev) {
326 | if(open) {
327 | if(ActiveElement.isInput()) {
328 | ActiveElement.blur();
329 | }
330 | EventListenerService.addListener(constants.keydown, this.onKeyDown, constants.boolTrue);
331 | } else {
332 | EventListenerService.removeListener(constants.keydown, this.onKeyDown, constants.boolTrue);
333 | }
334 | }
335 | }
336 |
337 | public componentWillUnmount(): void {
338 | EventListenerService.removeListener(constants.resize, this.onResize, constants.boolFalse);
339 | }
340 |
341 | public render(): JSX.Element {
342 | const { props, state, context } = this;
343 | const { textField, layouts, keyboardKeyHeight, keyboardKeyWidth, keyboardKeySymbolSize, automatic, correctorName, disableEffects } = props;
344 | const { value, layout: stateLayout, capsLock } = state;
345 | const { muiTheme } = context;
346 | const open: boolean = automatic ? state.open! : (props.open ? constants.boolTrue : constants.boolFalse);
347 | const theme: MuiTheme = muiTheme ? muiTheme : getMuiTheme();
348 | const styles: React.CSSProperties = textField.props.style!;
349 | let keyboardFieldProps: any = objectAssign({}, textField.props);
350 | let inputTextFieldProps: TextFieldAccessedProps = objectAssign({}, textField.props, { readOnly: open });
351 | if(automatic || open) {
352 | inputTextFieldProps.onFocus = automatic ? this.onFocus : undefined;
353 | }
354 | keyboardFieldProps.style = objectAssign({}, styles);
355 | keyboardFieldProps.style.minWidth = constants.fullWidth;
356 | keyboardFieldProps.style.width = constants.fullWidth;
357 | keyboardFieldProps.style.maxWidth = constants.fullWidth;
358 | keyboardFieldProps.readOnly = constants.boolTrue;
359 | keyboardFieldProps.value = value;
360 | if(typeof correctorName === constants.typeofString) {
361 | keyboardFieldProps[correctorName!] = this.corrector;
362 | }
363 | const overwrittenProps: Array = Keyboard.overwrittenProps;
364 | const { length: overwrittenPropsLength } = overwrittenProps;
365 | let propIndex: number;
366 | let prop: string;
367 | for(propIndex = constants.zero; propIndex < overwrittenPropsLength; ++propIndex) {
368 | prop = overwrittenProps[propIndex];
369 | if(keyboardFieldProps.hasOwnProperty(prop)) {
370 | keyboardFieldProps[prop] = undefined;
371 | }
372 | }
373 | const inputTextField: TextFieldElement = React.cloneElement(textField, inputTextFieldProps);
374 | const keyboardTextField: TextFieldElement = React.createElement(textField.type as CreatableTextField, keyboardFieldProps);
375 | const keyboardLayout: KeyboardLayout = kyeboardCapsLockLayout(layouts[stateLayout!], capsLock!);
376 | const keyboardRowLength: number = keyboardLayout.length;
377 | const keyboardRowLengths: Array = [];
378 | let rowIndex: number;
379 | let keyIndex: number;
380 | let spacebar: number;
381 | let rowLength: number;
382 | let row: Array;
383 | let key: string;
384 | for(rowIndex = constants.zero; rowIndex < keyboardRowLength; ++rowIndex) {
385 | spacebar = constants.one;
386 | row = keyboardLayout[rowIndex];
387 | rowLength = row.length;
388 | for(keyIndex = constants.zero; keyIndex < rowLength; ++keyIndex) {
389 | key = row[keyIndex];
390 | if(key.match(constants.isSpaceBar)) {
391 | spacebar = key.length;
392 | }
393 | }
394 | keyboardRowLengths.push(rowLength + spacebar - constants.one);
395 | }
396 | const maxKeyboardRowLength: number = Math.max(...keyboardRowLengths);
397 | let keyHeight: number = typeof keyboardKeyHeight === constants.typeofNumber ? keyboardKeyHeight! : theme.button!.height!;
398 | let keyWidth: number = typeof keyboardKeyWidth === constants.typeofNumber ? keyboardKeyWidth! : theme.button!.minWidth!;
399 | let keySymbolSize: number = typeof keyboardKeySymbolSize === constants.typeofNumber ? keyboardKeySymbolSize! : theme.flatButton!.fontSize!;
400 | const { desktopGutter, desktopKeylineIncrement } = theme.baseTheme!.spacing!;
401 | const { innerHeight, innerWidth } = window;
402 | const { minHeight, height, maxHeight } = (styles ? styles : Keyboard.noStyleHeight);
403 | const dialogGutter: number = constants.two * desktopGutter!;
404 | const styleHeight: number = minHeight ? minHeight : (height ? height : (maxHeight ? maxHeight : constants.zero));
405 | const textFieldHeight: number = styleHeight > constants.zero ? styleHeight : Keyboard.calculatedTextFieldHeight(inputTextFieldProps);
406 | let transformTop: number = desktopKeylineIncrement!;
407 | let dialogWidth: number = (maxKeyboardRowLength * keyWidth) + dialogGutter;
408 | let dialogHeight: number = (keyboardRowLength * keyHeight) + textFieldHeight + dialogGutter;
409 | const maxDialogHeight: number = innerHeight - constants.sixteen;
410 | const dialogSpacingTop: number = maxDialogHeight - dialogHeight;
411 | const overwriteWidth: boolean = dialogWidth > innerWidth;
412 | const overwriteHeight: boolean = dialogSpacingTop < transformTop;
413 | if(overwriteWidth || overwriteHeight) {
414 | if(overwriteWidth) {
415 | dialogWidth = innerWidth;
416 | keyWidth = (innerWidth - dialogGutter) / maxKeyboardRowLength;
417 | }
418 | if(overwriteHeight) {
419 | if(dialogSpacingTop >= constants.zero) {
420 | transformTop = dialogSpacingTop;
421 | } else {
422 | transformTop = constants.zero;
423 | dialogHeight = maxDialogHeight;
424 | keyHeight = (dialogHeight - textFieldHeight - dialogGutter) / keyboardRowLength;
425 | }
426 | }
427 | const smallerSymbolSize: number = (keyHeight < keyWidth ? keyHeight : keyWidth) - constants.four;
428 | if(smallerSymbolSize < keySymbolSize) {
429 | keySymbolSize = smallerSymbolSize;
430 | }
431 | }
432 | const dialogContentStyle: React.CSSProperties = {
433 | width: dialogWidth,
434 | maxWidth: innerWidth,
435 | height: dialogHeight,
436 | maxHeight: maxDialogHeight,
437 | transform: `translate(0, ${transformTop}px)`
438 | };
439 | let keyboardRows: Array = [];
440 | let keyboardRowKeys: Array;
441 | let notSpacebar: boolean;
442 | for(let rowIndex: number = constants.zero; rowIndex < keyboardRowLength; ++rowIndex) {
443 | keyboardRowKeys = [];
444 | row = keyboardLayout[rowIndex];
445 | rowLength = row.length;
446 | for(keyIndex = constants.zero; keyIndex < rowLength; ++keyIndex) {
447 | key = row[keyIndex];
448 | notSpacebar = key.match(constants.isSpaceBar) === null;
449 | keyboardRowKeys.push(
450 |
459 | );
460 | }
461 | keyboardRows.push({keyboardRowKeys}
);
462 | }
463 | const keyboard: JSX.Element = (
464 |
465 | {inputTextField}
466 |
476 |
477 | );
478 | return muiTheme ? keyboard : {keyboard};
479 | }
480 | };
481 |
482 | export default Keyboard;
--------------------------------------------------------------------------------
/src/KeyboardKey.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import * as PropTypes from 'prop-types';
3 | import bind from 'bind-decorator';
4 | import FlatButton from 'material-ui/FlatButton';
5 | import Backspace from 'material-ui/svg-icons/content/backspace';
6 | import Enter from 'material-ui/svg-icons/hardware/keyboard-return';
7 | import Escape from 'material-ui/svg-icons/action/exit-to-app';
8 | import Keyboard from 'material-ui/svg-icons/hardware/keyboard';
9 | import CapsLock from 'material-ui/svg-icons/hardware/keyboard-capslock';
10 | import Spacebar from 'material-ui/svg-icons/editor/space-bar';
11 | import Warning from 'material-ui/svg-icons/alert/warning';
12 | import { MuiTheme } from 'material-ui/styles';
13 |
14 | export type KeyboardKeyPressHandler = (key: string) => void;
15 |
16 | export interface KeyboardKeyProps {
17 | keyboardKey: string;
18 | onKeyPress: KeyboardKeyPressHandler;
19 | keyboardKeyWidth: number;
20 | keyboardKeyHeight: number;
21 | keyboardKeySymbolSize: number;
22 | disableEffects: boolean;
23 | }
24 |
25 | export interface KeyboardKeyContext {
26 | muiTheme: MuiTheme;
27 | }
28 |
29 | interface SpecialIcons {
30 | [index: string]: React.ComponentClass;
31 | }
32 |
33 | namespace constants {
34 | export const one: number = 1;
35 | export const spacebar: string = ' ';
36 | export const none: string = 'none';
37 | export const notFound: string = 'notFound';
38 | export const boolTrue: boolean = true;
39 | export const boolFalse: boolean = false;
40 | }
41 |
42 | export class KeyboardKey extends React.Component {
43 | public context: KeyboardKeyContext;
44 |
45 | private static specialIcons: SpecialIcons = {
46 | 'Enter': Enter,
47 | 'Backspace': Backspace,
48 | 'Escape': Escape,
49 | 'CapsLock': CapsLock,
50 | 'Keyboard': Keyboard,
51 | ' ' : Spacebar,
52 | 'notFound': Warning
53 | };
54 |
55 | public static propTypes: React.ValidationMap = {
56 | keyboardKey: PropTypes.string.isRequired,
57 | onKeyPress: PropTypes.func.isRequired,
58 | keyboardKeyWidth: PropTypes.number.isRequired,
59 | keyboardKeyHeight: PropTypes.number.isRequired,
60 | keyboardKeySymbolSize: PropTypes.number.isRequired,
61 | };
62 | public static contextTypes: any = { muiTheme: PropTypes.object.isRequired };
63 |
64 | @bind
65 | private onTouchTap(): void {
66 | const { onKeyPress, keyboardKey } = this.props;
67 | if((keyboardKey.length === 1) || KeyboardKey.specialIcons.hasOwnProperty(keyboardKey)) {
68 | onKeyPress(keyboardKey);
69 | }
70 | }
71 |
72 | public constructor(props: KeyboardKeyProps, context: KeyboardKeyContext) {
73 | super(props);
74 | this.context = context;
75 | }
76 |
77 | public shouldComponentUpdate(props: KeyboardKeyProps): boolean {
78 | if(this.props.keyboardKey !== props.keyboardKey) {
79 | return constants.boolTrue;
80 | }
81 | if(this.props.keyboardKeyHeight !== props.keyboardKeyHeight) {
82 | return constants.boolTrue;
83 | }
84 | if(this.props.keyboardKeySymbolSize !== props.keyboardKeySymbolSize) {
85 | return constants.boolTrue;
86 | }
87 | if(this.props.keyboardKeyWidth !== props.keyboardKeyWidth) {
88 | return constants.boolTrue;
89 | }
90 | if(this.props.onKeyPress !== props.onKeyPress) {
91 | return constants.boolTrue;
92 | }
93 | if(this.props.disableEffects !== props.disableEffects) {
94 | return constants.boolTrue;
95 | }
96 |
97 | return constants.boolFalse;
98 | }
99 |
100 | public render(): JSX.Element {
101 | const { keyboardKey: key, keyboardKeyHeight: height, keyboardKeyWidth: width, keyboardKeySymbolSize: size, disableEffects } = this.props;
102 | let flatButtonProps: any = {
103 | style: {
104 | height: height,
105 | width: width,
106 | minWidth: width
107 | },
108 | primary: constants.boolTrue,
109 | onTouchTap: this.onTouchTap,
110 | disableFocusRipple: disableEffects,
111 | disableKeyboardFocus: disableEffects,
112 | disableTouchRipple: disableEffects
113 | };
114 | if(disableEffects) {
115 | flatButtonProps.hoverColor = this.context.muiTheme.flatButton!.color;
116 | }
117 | if((key.length <= constants.one) && (key !== constants.spacebar)) {
118 | if(key.length) {
119 | flatButtonProps.label = key;
120 | } else {
121 | flatButtonProps.disabled = constants.boolTrue;
122 | flatButtonProps.label = constants.spacebar;
123 | }
124 | flatButtonProps.labelStyle = { fontSize: size, textTransform: constants.none };
125 | } else {
126 | const { specialIcons } = KeyboardKey;
127 | const icon: React.ComponentClass = specialIcons[specialIcons.hasOwnProperty(key) ? key : constants.notFound];
128 | flatButtonProps.icon = React.createElement(icon, { style: { width: size, height: size } });
129 | }
130 | return React.createElement(FlatButton, flatButtonProps);
131 | }
132 | };
133 |
134 | export default KeyboardKey;
--------------------------------------------------------------------------------
/src/index.tsx:
--------------------------------------------------------------------------------
1 | import Keyboard from './Keyboard';
2 | export * from './Keyboard';
3 | export default Keyboard;
--------------------------------------------------------------------------------
/src/layouts.ts:
--------------------------------------------------------------------------------
1 | export type KeyboardLayout = Array>;
2 |
3 | export const alphaNumericKeyboard: KeyboardLayout = [
4 | ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0'],
5 | ['q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p'],
6 | ['a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', 'Backspace'],
7 | ['Escape', 'CapsLock', 'z', 'x', 'c', 'v', 'b', 'n', 'm', 'Enter']
8 | ];
9 |
10 | export const extendedKeyboard: KeyboardLayout = [
11 | ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0'],
12 | ['q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p'],
13 | ['a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', 'Backspace'],
14 | ['CapsLock', 'z', 'x', 'c', 'v', 'b', 'n', 'm', '-', 'CapsLock'],
15 | ['Escape', '@', '#', ' ', '.', 'Enter']
16 | ];
17 |
18 | export const numericKeyboard: KeyboardLayout = [
19 | ['Escape', '-', 'Backspace'],
20 | ['7', '8', '9'],
21 | ['4', '5', '6'],
22 | ['1', '2', '3'],
23 | ['0', '.', 'Enter']
24 | ];
25 |
26 | export function kyeboardCapsLockLayout(layout: KeyboardLayout, caps: boolean): KeyboardLayout {
27 | return layout.map((row: Array): Array => {
28 | return row.map((key: string): string => {
29 | return (key.length === 1) ? (caps ? key.toUpperCase() : key.toLowerCase()) : key;
30 | });
31 | });
32 | }
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "commonjs",
4 | "target": "es5",
5 | "noImplicitAny": true,
6 | "strictNullChecks": true,
7 | "noImplicitThis": true,
8 | "noUnusedParameters": true,
9 | "noUnusedLocals": true,
10 | "experimentalDecorators": true,
11 | "lib": ["es5", "es6", "dom"],
12 | "jsx": "react"
13 | },
14 | "include": [
15 | "src/**/*",
16 | "Dev/**/*",
17 | "__tests__/**/*"
18 | ],
19 | "exclude": [
20 | "node_modules"
21 | ]
22 | }
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const HtmlWebpackPlugin = require('html-webpack-plugin');
2 | const webpack = require('webpack');
3 | const path = require('path');
4 |
5 | module.exports = {
6 | entry: [
7 | 'webpack-hot-middleware/client',
8 | "./Dev/index.tsx"
9 | ],
10 |
11 | output: {
12 | path: __dirname,
13 | filename: "[name].js",
14 | publicPath: ''
15 | },
16 |
17 | plugins: [
18 | new webpack.HotModuleReplacementPlugin(),
19 | new HtmlWebpackPlugin({ title: "React Material-Ui Keyboard" })
20 | ],
21 |
22 | resolve: {
23 | extensions: [".ts", ".tsx", ".js"]
24 | },
25 |
26 | module: {
27 | loaders: [
28 | {
29 | test: /\.tsx?$/,
30 | loaders: ["ts-loader"]
31 | }
32 | ],
33 | }
34 | };
35 |
--------------------------------------------------------------------------------