├── .angular-cli.json ├── .editorconfig ├── .gitignore ├── .idea └── codeStyleSettings.xml ├── .nvmrc ├── .sass-lint.yml ├── .travis.yml ├── README.md ├── karma.conf.js ├── ng-package.json ├── package-lock.json ├── package.json ├── protractor.conf.js ├── screenshots ├── ngxmk-2.gif └── ngxmk-2.stg ├── src ├── core │ ├── package.json │ ├── src │ │ ├── classes │ │ │ └── keyboard-ref.class.ts │ │ ├── components │ │ │ ├── keyboard-container │ │ │ │ ├── keyboard-container.component.html │ │ │ │ ├── keyboard-container.component.scss │ │ │ │ └── keyboard-container.component.ts │ │ │ ├── keyboard-key │ │ │ │ ├── keyboard-key.component.html │ │ │ │ ├── keyboard-key.component.scss │ │ │ │ └── keyboard-key.component.ts │ │ │ └── keyboard │ │ │ │ ├── keyboard.component.html │ │ │ │ ├── keyboard.component.scss │ │ │ │ └── keyboard.component.ts │ │ ├── configs │ │ │ ├── keyboard-deadkey.config.ts │ │ │ ├── keyboard-icons.config.ts │ │ │ ├── keyboard-layouts.config.ts │ │ │ ├── keyboard-numpad.config.js.ref │ │ │ ├── keyboard-symbols.config.js.ref │ │ │ └── keyboard.config.ts │ │ ├── directives │ │ │ └── keyboard.directive.ts │ │ ├── enums │ │ │ ├── keyboard-animation-state.enum.ts │ │ │ ├── keyboard-animation-transition.enum.ts │ │ │ ├── keyboard-class-key.enum.ts │ │ │ └── keyboard-modifier.enum.ts │ │ ├── interfaces │ │ │ ├── keyboard-deadkeys.interface.ts │ │ │ ├── keyboard-icons.interface.ts │ │ │ ├── keyboard-layout.interface.ts │ │ │ ├── keyboard-layouts.interface.ts │ │ │ └── locale-map.interface.ts │ │ ├── keyboard.module.ts │ │ ├── pipes │ │ │ └── kebab-case.pipe.ts │ │ ├── public_api.ts │ │ ├── services │ │ │ └── keyboard.service.ts │ │ └── utils │ │ │ └── keyboard.utils.ts │ └── tslint.yml └── demo │ ├── app │ ├── app.component.html │ ├── app.component.scss │ ├── app.component.ts │ └── app.module.ts │ ├── assets │ └── .gitkeep │ ├── environments │ ├── environment.prod.ts │ └── environment.ts │ ├── favicon.ico │ ├── index.html │ ├── main.ts │ ├── polyfills.ts │ ├── styles.scss │ ├── test.ts │ ├── tsconfig.app.json │ ├── tsconfig.spec.json │ ├── tslint.yml │ └── typings.d.ts ├── tsconfig.json └── tslint.yml /.angular-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "project": { 4 | "name": "ngx-material-keyboard" 5 | }, 6 | "apps": [ 7 | { 8 | "name": "demo", 9 | "root": "src/demo", 10 | "outDir": "dist/demo", 11 | "assets": [ 12 | "assets", 13 | "favicon.ico" 14 | ], 15 | "index": "index.html", 16 | "main": "main.ts", 17 | "polyfills": "polyfills.ts", 18 | "test": "test.ts", 19 | "tsconfig": "tsconfig.app.json", 20 | "testTsconfig": "tsconfig.spec.json", 21 | "prefix": "mat-keyboard-demo", 22 | "styles": [ 23 | "../../node_modules/reset-css/_reset.scss", 24 | "../../node_modules/@angular/material/prebuilt-themes/indigo-pink.css", 25 | "styles.scss" 26 | ], 27 | "scripts": [], 28 | "environmentSource": "environments/environment.ts", 29 | "environments": { 30 | "dev": "environments/environment.ts", 31 | "prod": "environments/environment.prod.ts" 32 | } 33 | } 34 | ], 35 | "lint": [ 36 | { 37 | "project": "src/tsconfig.app.json" 38 | }, 39 | { 40 | "project": "src/tsconfig.spec.json" 41 | } 42 | ], 43 | "test": { 44 | "karma": { 45 | "config": "./karma.conf.js" 46 | } 47 | }, 48 | "defaults": { 49 | "styleExt": "scss", 50 | "build": { 51 | "showCircularDependencies": false 52 | }, 53 | "component": { 54 | } 55 | }, 56 | "warnings": { 57 | "typescriptMismatch": false 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Output Directories # 2 | build/ 3 | dist/ 4 | docs/ 5 | src/core/.ng_pkg_build 6 | target/ 7 | 8 | .classpath 9 | .settings/ 10 | 11 | /.ng_build 12 | /.temp 13 | /tmp 14 | 15 | .vscode 16 | 17 | # Dependency directory # 18 | # https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git 19 | node_modules 20 | 21 | # Optional npm cache directory # 22 | .npm 23 | npm 24 | npm-debug.log 25 | 26 | # IDE Directories # 27 | .idea 28 | !.idea/codeStyleSettings.xml 29 | !.idea/ngx-material-keyboard.iml 30 | 31 | # External tool builders # 32 | .externalToolBuilders/ 33 | 34 | # Locally stored "Eclipse launch configurations" # 35 | *.launch 36 | 37 | # OS Files # 38 | .DS_Store 39 | 40 | # Misc # 41 | *~ 42 | -------------------------------------------------------------------------------- /.idea/codeStyleSettings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | true 88 | true 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | true 98 | true 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | true 108 | true 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | true 117 | 118 | 119 | 120 | 121 | 122 | 123 | true 124 | 125 | 126 | 127 | 128 | 129 | 130 | true 131 | 132 | 133 | 134 | 135 | 136 | 137 | true 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | true 197 | true 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | true 207 | true 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | true 217 | true 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | true 227 | false 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | true 237 | false 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | true 247 | false 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | false 257 | true 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | true 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | true 274 | false 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | true 284 | false 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 8.9.4 -------------------------------------------------------------------------------- /.sass-lint.yml: -------------------------------------------------------------------------------- 1 | # sass-lint config generated by make-sass-lint-config v0.1.2 2 | # 3 | # The following scss-lint Linters are not yet supported by sass-lint: 4 | # ChainedClasses, DisableLinterReason, ElsePlacement, PrivateNamingConvention 5 | # PropertyCount, PseudoElement, SelectorDepth, SpaceAfterComment 6 | # SpaceAfterVariableColon, SpaceAroundOperator, TrailingWhitespace, UnnecessaryParentReference 7 | # Compass::* 8 | # 9 | # The following settings/values are unsupported by sass-lint: 10 | # Linter Comment, option "style" 11 | # Linter Indentation, option "allow_non_nested_indentation" 12 | # Linter Indentation, option "character" 13 | # Linter NestingDepth, option "ignore_parent_selectors" 14 | # Linter PropertySortOrder, option "min_properties" 15 | # Linter PropertySortOrder, option "separate_groups" 16 | # Linter PropertySpelling, option "disabled_properties" 17 | # Linter SpaceBeforeBrace, option "allow_single_line_padding" 18 | # Linter VendorPrefix, option "identifier_list" 19 | 20 | # Linter Options 21 | options: 22 | # Don't merge default rules 23 | merge-default-rules: false 24 | # Set the formatter to 'html' 25 | formatter: stylish 26 | # Output file instead of logging results 27 | # output-file: 'sass-lint.html' 28 | # Raise an error if more than 50 warnings are generated 29 | max-warnings: 50 30 | # File Options 31 | files: 32 | include: 'src/**/*.s+(a|c)ss' 33 | ignore: 34 | - 'vendor/**/*.*' 35 | # Rule Configuration 36 | rules: 37 | bem-depth: 38 | - 0 39 | - max-depth: 1 40 | border-zero: 41 | - 1 42 | - convention: zero 43 | brace-style: 44 | - 1 45 | - allow-single-line: true 46 | class-name-format: 47 | - 1 48 | - convention: hyphenatedlowercase 49 | clean-import-paths: 50 | - 1 51 | - filename-extension: false 52 | leading-underscore: false 53 | empty-line-between-blocks: 54 | - 1 55 | - ignore-single-line-rulesets: true 56 | extends-before-declarations: 1 57 | extends-before-mixins: 1 58 | final-newline: 59 | - 1 60 | - include: true 61 | force-attribute-nesting: 0 62 | force-element-nesting: 0 63 | force-pseudo-nesting: 0 64 | function-name-format: 65 | - 1 66 | - allow-leading-underscore: true 67 | convention: hyphenatedlowercase 68 | hex-length: 69 | - 1 70 | - style: short 71 | hex-notation: 72 | - 1 73 | - style: lowercase 74 | id-name-format: 75 | - 1 76 | - convention: hyphenatedlowercase 77 | indentation: 78 | - 1 79 | - size: 2 80 | leading-zero: 81 | - 1 82 | - include: false 83 | mixin-name-format: 84 | - 1 85 | - allow-leading-underscore: true 86 | convention: hyphenatedlowercase 87 | mixins-before-declarations: 0 88 | nesting-depth: 89 | - 0 90 | - max-depth: 6 91 | no-color-keywords: 1 92 | no-color-literals: 1 93 | no-css-comments: 1 94 | no-debug: 1 95 | no-duplicate-properties: 1 96 | no-empty-rulesets: 1 97 | no-extends: 0 98 | no-ids: 1 99 | no-important: 1 100 | no-invalid-hex: 1 101 | no-mergeable-selectors: 1 102 | no-misspelled-properties: 103 | - 1 104 | - extra-properties: [] 105 | no-qualifying-elements: 106 | - 1 107 | - allow-element-with-attribute: false 108 | allow-element-with-class: false 109 | allow-element-with-id: false 110 | no-trailing-zero: 1 111 | no-transition-all: 0 112 | no-url-protocols: 1 113 | no-vendor-prefixes: 114 | - 1 115 | - additional-identifiers: [] 116 | excluded-identifiers: [] 117 | placeholder-in-extend: 1 118 | placeholder-name-format: 119 | - 1 120 | - convention: hyphenatedlowercase 121 | property-sort-order: 122 | - 0 123 | - ignore-custom-properties: false 124 | property-units: 125 | - 1 126 | - global: 127 | - ch 128 | - em 129 | - ex 130 | - rem 131 | - cm 132 | - in 133 | - mm 134 | - pc 135 | - pt 136 | - px 137 | - q 138 | - vh 139 | - vw 140 | - vmin 141 | - vmax 142 | - deg 143 | - grad 144 | - rad 145 | - turn 146 | - ms 147 | - s 148 | - Hz 149 | - kHz 150 | - dpi 151 | - dpcm 152 | - dppx 153 | - '%' 154 | per-property: {} 155 | quotes: 156 | - 1 157 | - style: single 158 | shorthand-values: 159 | - 1 160 | - allowed-shorthands: 161 | - 1 162 | - 2 163 | - 3 164 | - 4 165 | single-line-per-selector: 1 166 | space-after-bang: 167 | - 1 168 | - include: false 169 | space-after-colon: 170 | - 1 171 | - include: true 172 | space-after-comma: 173 | - 1 174 | - include: true 175 | space-before-bang: 176 | - 1 177 | - include: true 178 | space-before-brace: 179 | - 1 180 | - include: true 181 | space-before-colon: 1 182 | space-between-parens: 183 | - 1 184 | - include: false 185 | trailing-semicolon: 1 186 | url-quotes: 1 187 | variable-for-property: 188 | - 0 189 | - properties: [] 190 | variable-name-format: 191 | - 1 192 | - allow-leading-underscore: true 193 | convention: hyphenatedlowercase 194 | zero-unit: 1 -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # this is a node build 2 | language: node_js 3 | 4 | # cache node modules 5 | cache: 6 | directories: 7 | - "node_modules" 8 | 9 | # use npm 5.3.0 due to a bug (https://github.com/npm/npm/issues/19222) 10 | before_install: 11 | - npm i -g npm@5.3.0 12 | 13 | # install dependencies 14 | install: 15 | - npm install --silent 16 | 17 | # the jobs for building and deplyoing docs and demo 18 | jobs: 19 | include: 20 | - stage: build core, docs and demo 21 | script: npm run build:core 22 | before_deploy: 23 | - cd dist/core 24 | deploy: 25 | provider: npm 26 | skip_cleanup: true 27 | email: $NPM_EMAIL 28 | api_key: $NPM_KEY 29 | on: 30 | tags: true 31 | repo: ngx-material-keyboard/core 32 | branch: master 33 | - # stage name not required, will continue to use `build core, docs and demo` 34 | script: npm run build:docs 35 | deploy: 36 | provider: pages 37 | skip_cleanup: true 38 | local_dir: dist/docs 39 | github_token: $GITHUB_TOKEN 40 | target_branch: gh-pages 41 | on: 42 | repo: ngx-material-keyboard/core 43 | branch: master 44 | - # stage name not required, will continue to use `build core, docs and demo` 45 | script: npm run build:demo 46 | deploy: 47 | provider: pages 48 | skip_cleanup: true 49 | local_dir: dist/demo 50 | github_token: $GITHUB_TOKEN 51 | repo: ngx-material-keyboard/demo 52 | target_branch: master 53 | on: 54 | repo: ngx-material-keyboard/core 55 | branch: master -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [](https://travis-ci.org/ngx-material-keyboard/core) 2 | 3 | # ngx-material-keyboard 4 | Onscreen virtual keyboard for [Angular] using [Angular Material]. 5 | > Please note that the project is at a very early stage. Therefore one should refrain from productive usage. 6 | 7 |  8 | 9 | ## Demo 10 | A demo can be found [here][demo]. 11 | 12 | ## Docs 13 | Generated documentation can be found [here][docs]. 14 | 15 | ## Getting started 16 | 1. Install with your prefered packet manager (we're using `npm` here): 17 | `npm install --save @ngx-material-keyboard/core` 18 | > Be sure to fulfill the peer dependencies of this module, in particular [Angular] and [Angular Material]. 19 | 20 | 2. Add the module to your project, e.g. `app.module.ts`: 21 | ```:typescript 22 | import { NgModule } from '@angular/core'; 23 | import { FormsModule } from '@angular/forms'; 24 | import { BrowserModule } from '@angular/platform-browser'; 25 | import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; 26 | import { MatButtonModule } from '@angular/material/button'; 27 | ... 28 | import { MatKeyboardModule } from '@ngx-material-keyboard/core'; 29 | 30 | @NgModule({ 31 | imports: [ 32 | // Angular modules 33 | BrowserModule, 34 | BrowserAnimationsModule, 35 | FormsModule, 36 | 37 | // Material modules 38 | MatButtonModule, 39 | MatKeyboardModule, 40 | ], 41 | ... 42 | }) 43 | export class AppModule {} 44 | ``` 45 | 46 | 3. Use the [`MatKeyboardDirective`][docs:MatKeyboardDirective] on your input elements or textareas and set the name or locale of the layout. 47 | > If not provided the locale will be derieved from the `LOCALE_ID` or the browser. 48 | ```:angular2html 49 | 50 | ``` 51 | 52 | ## Providing custom layouts 53 | Most of the base configurations are provided as [injection tokens][InjectionToken]. Please read [the documentation][InjectionToken] to 54 | understand how to handle it. 55 | 56 | All layouts are based on (or directly inherited from) the [angular-virtual-keyboard][the-darc/angular-virtual-keyboard] which is based on 57 | [GreyWyvern VKI]. For details on how to structure a layout see the [comment derived from the original source code][VKI Readme]. 58 | 59 | > Note that this will most likely be changed in the near future. But for now a huge range of layouts is already usable because of the 60 | [great contribution][VKI Credits] back then. 61 | 62 | But basicly you just provide the configuration of your new layout in your `AppModule`: 63 | ```:typescript 64 | import { IKeyboardLayouts, keyboardLayouts, MAT_KEYBOARD_LAYOUTS, MatKeyboardModule } from '@ngx-material-keyboard/core'; 65 | 66 | const customLayouts: IKeyboardLayouts = { 67 | ...keyboardLayouts, 68 | 'Tölles Läyout': { 69 | 'name': 'Awesome layout', 70 | 'keys': [ 71 | [ 72 | ['1', '!'], 73 | ['2', '@'], 74 | ['3', '#'] 75 | ] 76 | ], 77 | 'lang': ['de-CH'] 78 | } 79 | }; 80 | 81 | @NgModule({ 82 | ... 83 | providers: [ 84 | { provide: MAT_KEYBOARD_LAYOUTS, useValue: customLayouts } 85 | ], 86 | ... 87 | }) 88 | export class AppModule {} 89 | ``` 90 | 91 | ## Development 92 | This repository is managed by and layed out for [ng-packagr]. 93 | 94 | ### Versioning 95 | The application uses [semver][SemVer] and is developed with the [git flow branching model][Git-Flow]. 96 | 97 | [Angular]: https://angular.io/ 98 | [Angular Material]: https://material.angular.io/ 99 | [the-darc/angular-virtual-keyboard]: https://github.com/the-darc/angular-virtual-keyboard 100 | [GreyWyvern VKI]: http://www.greywyvern.com/code/javascript/keyboard 101 | 102 | [SemVer]: http://semver.org/ 103 | [Git-Flow]: http://nvie.com/posts/a-successful-git-branching-model/ 104 | [ng-packagr]: https://github.com/dherges/ng-packagr 105 | 106 | [demo]: https://ngx-material-keyboard.github.io/demo/ 107 | [docs]: https://ngx-material-keyboard.github.io/core/ 108 | 109 | [docs:MatKeyboardDirective]: https://ngx-material-keyboard.github.io/core/directives/MatKeyboardDirective.html 110 | [InjectionToken]: https://angular.io/guide/dependency-injection-in-action#injectiontoken 111 | [VKI Readme]: https://goo.gl/fCDExr 112 | [VKI Credits]: https://goo.gl/NYqTwc 113 | 114 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/0.13/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular/cli'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage-istanbul-reporter'), 13 | require('@angular/cli/plugins/karma') 14 | ], 15 | client: { 16 | clearContext: false // leave Jasmine Spec Runner output visible in browser 17 | }, 18 | files: [ 19 | { pattern: './src/demo/test.ts', watched: false } 20 | ], 21 | preprocessors: { 22 | './src/demo/test.ts': ['@angular/cli'] 23 | }, 24 | mime: { 25 | 'text/x-typescript': ['ts', 'tsx'] 26 | }, 27 | coverageIstanbulReporter: { 28 | reports: ['html', 'lcovonly'], 29 | fixWebpackSourcePaths: true 30 | }, 31 | angularCli: { 32 | environment: 'dev' 33 | }, 34 | reporters: config.angularCli && config.angularCli.codeCoverage 35 | ? ['progress', 'coverage-istanbul'] 36 | : ['progress', 'kjhtml'], 37 | port: 9876, 38 | colors: true, 39 | logLevel: config.LOG_INFO, 40 | autoWatch: true, 41 | browsers: ['Chrome'], 42 | singleRun: false 43 | }); 44 | }; 45 | -------------------------------------------------------------------------------- /ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/ng-packagr/ng-package.schema.json", 3 | "src": "src/core", 4 | "dest": "dist/core", 5 | "workingDirectory": ".ng_build", 6 | "lib": { 7 | "entryFile": "src/public_api.ts", 8 | "externals": { 9 | "@angular/cdk/accordion": "ng.cdk.accordion", 10 | "@angular/cdk/layout": "ng.cdk.layout", 11 | "@angular/material": "ng.material", 12 | "@angular/material/autocomplete": "ng.material.autocomplete", 13 | "@angular/material/button": "ng.material.button", 14 | "@angular/material/button-toggle": "ng.material.button-toggle", 15 | "@angular/material/card": "ng.material.card", 16 | "@angular/material/checkbox": "ng.material.checkbox", 17 | "@angular/material/chips": "ng.material.chips", 18 | "@angular/material/core": "ng.material.core", 19 | "@angular/material/datepicker": "ng.material.datepicker", 20 | "@angular/material/dialog": "ng.material.dialog", 21 | "@angular/material/expansion": "ng.material.expansion", 22 | "@angular/material/form-field": "ng.material.form-field", 23 | "@angular/material/grid-list": "ng.material.grid-list", 24 | "@angular/material/icon": "ng.material.icon", 25 | "@angular/material/input": "ng.material.input", 26 | "@angular/material/list": "ng.material.list", 27 | "@angular/material/menu": "ng.material.menu", 28 | "@angular/material/paginator": "ng.material.paginator", 29 | "@angular/material/progress-bar": "ng.material.progress-bar", 30 | "@angular/material/progress-spinner": "ng.material.progress-spinner", 31 | "@angular/material/radio": "ng.material.radio", 32 | "@angular/material/select": "ng.material.select", 33 | "@angular/material/sidenav": "ng.material.sidenav", 34 | "@angular/material/slide-toggle": "ng.material.slide-toggle", 35 | "@angular/material/slider": "ng.material.slider", 36 | "@angular/material/snack-bar": "ng.material.snack-bar", 37 | "@angular/material/sort": "ng.material.sort", 38 | "@angular/material/stepper": "ng.material.stepper", 39 | "@angular/material/table": "ng.material.table", 40 | "@angular/material/tabs": "ng.material.tabs", 41 | "@angular/material/toolbar": "ng.material.toolbar", 42 | "@angular/material/tooltip": "ng.material.tooltip" 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ngx-material-keyboard-src", 3 | "version": "0.1.1", 4 | "description": "Onscreen virtual keyboard for Angular ≥ 5 (https://angular.io/) using Material 2 (https://material.angular.io/).", 5 | "repository": { 6 | "type": "git", 7 | "url": "git+https://github.com/ngx-material-keyboard/core.git" 8 | }, 9 | "author": "David Enke ", 10 | "license": "MIT", 11 | "bugs": { 12 | "url": "https://github.com/ngx-material-keyboard/core/issues" 13 | }, 14 | "homepage": "https://github.com/ngx-material-keyboard/core", 15 | "scripts": { 16 | "ng": "ng", 17 | "start": "npm run start:demo", 18 | "start:demo": "ng serve --host 0.0.0.0 --disable-host-check", 19 | "build": "npm run build:core && npm run build:demo && npm run build:docs", 20 | "build:core": "ng-packagr -p ./src/core/package.json && cpx -p README.md dist/core", 21 | "build:demo": "ng build --prod --locale=de --progress=false --base-href=/demo/", 22 | "build:docs": "compodoc ./src/core -p ./tsconfig.json --silent --disableCoverage --disablePrivateOrInternalSupport --theme readthedocs --output ./dist/docs", 23 | "test": "ng test", 24 | "lint": "npm run lint:ts && npm run lint:sass", 25 | "lint:ts": "ng lint --type-check=true", 26 | "lint:sass": "sass-lint --verbose --no-exit --config ./.sass-lint.yml", 27 | "postinstall": "npm rebuild node-sass" 28 | }, 29 | "dependencies": { 30 | "@angular/animations": "5.2.5", 31 | "@angular/cdk": "5.2.2", 32 | "@angular/common": "5.2.5", 33 | "@angular/compiler": "5.2.5", 34 | "@angular/core": "5.2.5", 35 | "@angular/forms": "5.2.5", 36 | "@angular/http": "5.2.5", 37 | "@angular/material": "5.2.2", 38 | "@angular/platform-browser": "5.2.5", 39 | "@angular/platform-browser-dynamic": "5.2.5", 40 | "@angular/router": "5.2.5", 41 | "classlist.js": "1.1.20150312", 42 | "core-js": "2.5.3", 43 | "hammerjs": "2.0.8", 44 | "reset-css": "2.2.1", 45 | "rxjs": "5.5.6", 46 | "web-animations-js": "2.3.1", 47 | "zone.js": "0.8.20" 48 | }, 49 | "devDependencies": { 50 | "@angular/cli": "1.7.0", 51 | "@angular/compiler-cli": "5.2.5", 52 | "@angular/language-service": "5.2.5", 53 | "@compodoc/compodoc": "1.1.5", 54 | "@types/jasmine": "2.8.6", 55 | "@types/node": "9.4.6", 56 | "codelyzer": "4.1.0", 57 | "cpx": "1.5.0", 58 | "ng-packagr": "2.1.0", 59 | "rxjs-tslint-rules": "3.14.0", 60 | "sass-lint": "1.12.1", 61 | "ts-node": "5.0.0", 62 | "tslint": "5.9.1", 63 | "typescript": "2.6.2" 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /protractor.conf.js: -------------------------------------------------------------------------------- 1 | // Protractor configuration file, see link for more information 2 | // https://github.com/angular/protractor/blob/master/lib/config.ts 3 | 4 | const { SpecReporter } = require('jasmine-spec-reporter'); 5 | 6 | exports.config = { 7 | allScriptsTimeout: 11000, 8 | specs: [ 9 | './e2e/**/*.e2e-spec.ts' 10 | ], 11 | capabilities: { 12 | 'browserName': 'chrome' 13 | }, 14 | directConnect: true, 15 | baseUrl: 'http://localhost:4200/', 16 | framework: 'jasmine', 17 | jasmineNodeOpts: { 18 | showColors: true, 19 | defaultTimeoutInterval: 30000, 20 | print: function () {} 21 | }, 22 | beforeLaunch: function () { 23 | require('ts-node') 24 | .register({ 25 | project: 'e2e/tsconfig.e2e.json' 26 | }); 27 | }, 28 | onPrepare() { 29 | jasmine.getEnv() 30 | .addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /screenshots/ngxmk-2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngx-material-keyboard/core/d076ea9e1f0cd6f861fdf2bca66ab4bbaf905588/screenshots/ngxmk-2.gif -------------------------------------------------------------------------------- /screenshots/ngxmk-2.stg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngx-material-keyboard/core/d076ea9e1f0cd6f861fdf2bca66ab4bbaf905588/screenshots/ngxmk-2.stg -------------------------------------------------------------------------------- /src/core/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../node_modules/ng-packagr/package.schema.json", 3 | "name": "@ngx-material-keyboard/core", 4 | "version": "0.1.1", 5 | "description": "Onscreen virtual keyboard for Angular ≥ 5 (https://angular.io/) using Material 5 (https://material.angular.io/).", 6 | "repository": { 7 | "type": "git", 8 | "url": "git+https://github.com/ngx-material-keyboard/core.git" 9 | }, 10 | "author": "David Enke ", 11 | "license": "MIT", 12 | "bugs": { 13 | "url": "https://github.com/ngx-material-keyboard/core/issues" 14 | }, 15 | "homepage": "https://github.com/ngx-material-keyboard/core", 16 | "publishConfig": { 17 | "access": "public" 18 | }, 19 | "peerDependencies": { 20 | "@angular/animations": "^5.0.0", 21 | "@angular/cdk": "5.2.2", 22 | "@angular/common": "^5.0.0", 23 | "@angular/core": "^5.0.0", 24 | "@angular/forms": "^5.0.0", 25 | "@angular/material": "5.2.2", 26 | "rxjs": "^5.5.2" 27 | }, 28 | "ngPackage": { 29 | "dest": "../../dist/core", 30 | "lib": { 31 | "entryFile": "src/public_api.ts" 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/core/src/classes/keyboard-ref.class.ts: -------------------------------------------------------------------------------- 1 | import { OverlayRef } from '@angular/cdk/overlay'; 2 | 3 | import { Observable } from 'rxjs/Observable'; 4 | import { Subject } from 'rxjs/Subject'; 5 | 6 | import { MatKeyboardComponent } from '../components/keyboard/keyboard.component'; 7 | import { MatKeyboardContainerComponent } from '../components/keyboard-container/keyboard-container.component'; 8 | 9 | /** 10 | * Reference to a keyboard dispatched from the keyboard service. 11 | */ 12 | export class MatKeyboardRef { 13 | 14 | /** Subject for notifying the user that the keyboard has closed. */ 15 | private _afterClosed: Subject = new Subject(); 16 | 17 | /** Subject for notifying the user that the keyboard has opened and appeared. */ 18 | private _afterOpened: Subject = new Subject(); 19 | 20 | /** The instance of the component making up the content of the keyboard. */ 21 | instance: MatKeyboardComponent; 22 | 23 | /** The instance of the component making up the content of the keyboard. */ 24 | containerInstance: MatKeyboardContainerComponent; 25 | 26 | constructor(instance: MatKeyboardComponent, 27 | containerInstance: MatKeyboardContainerComponent, 28 | private _overlayRef: OverlayRef) { 29 | // Sets the readonly instance of the keyboard content component. 30 | this.instance = instance; 31 | this.containerInstance = containerInstance; 32 | 33 | // Finish dismiss on exitting 34 | containerInstance.onExit.subscribe(() => this._finishDismiss()); 35 | } 36 | 37 | /** Dismisses the keyboard. */ 38 | dismiss() { 39 | if (!this._afterClosed.closed) { 40 | this.containerInstance.exit(); 41 | } 42 | } 43 | 44 | /** Marks the keyboard as opened */ 45 | _open() { 46 | if (!this._afterOpened.closed) { 47 | this._afterOpened.next(); 48 | this._afterOpened.complete(); 49 | } 50 | } 51 | 52 | /** Gets an observable that is notified when the keyboard is finished closing. */ 53 | afterDismissed(): Observable { 54 | return this._afterClosed.asObservable(); 55 | } 56 | 57 | /** Gets an observable that is notified when the keyboard has opened and appeared. */ 58 | afterOpened(): Observable { 59 | return this.containerInstance.onEnter; 60 | } 61 | 62 | /** Cleans up the DOM after closing. */ 63 | private _finishDismiss() { 64 | this._overlayRef.dispose(); 65 | 66 | this._afterClosed.next(); 67 | this._afterClosed.complete(); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/core/src/components/keyboard-container/keyboard-container.component.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/core/src/components/keyboard-container/keyboard-container.component.scss: -------------------------------------------------------------------------------- 1 | @import '~@angular/material/theming'; 2 | 3 | $mat-keyboard-min-width: 568px !default; 4 | $mat-keyboard-max-width: 960px !default; 5 | 6 | :host { 7 | @include mat-elevation(6); 8 | border-radius: 2px; 9 | box-sizing: border-box; 10 | display: block; 11 | margin: 0 auto; 12 | max-width: $mat-keyboard-max-width; 13 | min-width: $mat-keyboard-min-width; 14 | // Initial transformation is applied to start keyboard out of view. 15 | transform: translateY(100%); 16 | 17 | @include cdk-high-contrast { 18 | border: solid 1px; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/core/src/components/keyboard-container/keyboard-container.component.ts: -------------------------------------------------------------------------------- 1 | import { animate, AnimationEvent, state, style, transition, trigger } from '@angular/animations'; 2 | import { BasePortalOutlet, CdkPortalOutlet, ComponentPortal } from '@angular/cdk/portal'; 3 | import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ComponentRef, EmbeddedViewRef, HostBinding, HostListener, NgZone, OnDestroy, ViewChild } from '@angular/core'; 4 | import { AnimationCurves, AnimationDurations } from '@angular/material/core'; 5 | 6 | import { Observable } from 'rxjs/Observable'; 7 | import { Subject } from 'rxjs/Subject'; 8 | import 'rxjs/add/operator/first'; 9 | 10 | import { MatKeyboardConfig } from '../../configs/keyboard.config'; 11 | import { KeyboardAnimationState } from '../../enums/keyboard-animation-state.enum'; 12 | import { KeyboardAnimationTransition } from '../../enums/keyboard-animation-transition.enum'; 13 | 14 | // TODO: we can't use constants from animation.ts here because you can't use 15 | // a text interpolation in anything that is analyzed statically with ngc (for AoT compile). 16 | export const SHOW_ANIMATION = `${AnimationDurations.ENTERING} ${AnimationCurves.DECELERATION_CURVE}`; 17 | export const HIDE_ANIMATION = `${AnimationDurations.EXITING} ${AnimationCurves.ACCELERATION_CURVE}`; 18 | 19 | /** 20 | * Internal component that wraps user-provided keyboard content. 21 | * @docs-private 22 | */ 23 | @Component({ 24 | selector: 'mat-keyboard-container', 25 | templateUrl: './keyboard-container.component.html', 26 | styleUrls: ['./keyboard-container.component.scss'], 27 | changeDetection: ChangeDetectionStrategy.OnPush, 28 | preserveWhitespaces: false, 29 | // animations: [ 30 | // trigger('state', [ 31 | // state('visible', style({transform: 'translateY(0%)'})), 32 | // transition('visible => hidden', animate(HIDE_ANIMATION)), 33 | // transition('void => visible', animate(SHOW_ANIMATION)), 34 | // ]) 35 | // ] 36 | animations: [ 37 | trigger('state', [ 38 | state(`${KeyboardAnimationState.Visible}`, style({ transform: 'translateY(0%)' })), 39 | transition(`${KeyboardAnimationTransition.Hide}`, animate(HIDE_ANIMATION)), 40 | transition(`${KeyboardAnimationTransition.Show}`, animate(SHOW_ANIMATION)) 41 | ]) 42 | ] 43 | }) 44 | export class MatKeyboardContainerComponent extends BasePortalOutlet implements OnDestroy { 45 | 46 | /** Whether the component has been destroyed. */ 47 | private _destroyed = false; 48 | 49 | /** The portal outlet inside of this container into which the keyboard content will be loaded. */ 50 | @ViewChild(CdkPortalOutlet) 51 | private _portalOutlet: CdkPortalOutlet; 52 | 53 | /** The state of the keyboard animations. */ 54 | @HostBinding('@state') 55 | private _animationState: KeyboardAnimationState = KeyboardAnimationState.Void; 56 | 57 | /** Subject for notifying that the keyboard has exited from view. */ 58 | onExit: Subject = new Subject(); 59 | 60 | /** Subject for notifying that the keyboard has finished entering the view. */ 61 | onEnter: Subject = new Subject(); 62 | 63 | @HostBinding('attr.role') 64 | attrRole = 'alert'; 65 | 66 | // the keyboard configuration 67 | keyboardConfig: MatKeyboardConfig; 68 | 69 | constructor(private _ngZone: NgZone, 70 | private _changeDetectorRef: ChangeDetectorRef) { 71 | super(); 72 | } 73 | 74 | @HostListener('mousedown', ['$event']) 75 | onMousedown(event: MouseEvent) { 76 | event.preventDefault(); 77 | } 78 | 79 | /** Attach a component portal as content to this keyboard container. */ 80 | attachComponentPortal(portal: ComponentPortal): ComponentRef { 81 | if (this._portalOutlet.hasAttached()) { 82 | throw Error('Attempting to attach keyboard content after content is already attached'); 83 | } 84 | 85 | return this._portalOutlet.attachComponentPortal(portal); 86 | } 87 | 88 | // Attach a template portal as content to this keyboard container 89 | attachTemplatePortal(): EmbeddedViewRef { 90 | throw Error('Not yet implemented'); 91 | } 92 | 93 | /** Handle end of animations, updating the state of the keyboard. */ 94 | @HostListener('@state.done', ['$event']) 95 | onAnimationEnd(event: AnimationEvent) { 96 | const { fromState, toState } = event; 97 | 98 | if ((toState === KeyboardAnimationState.Void && fromState !== KeyboardAnimationState.Void) || toState.startsWith('hidden')) { 99 | this._completeExit(); 100 | } 101 | 102 | if (toState === KeyboardAnimationState.Visible) { 103 | // Note: we shouldn't use `this` inside the zone callback, 104 | // because it can cause a memory leak. 105 | const onEnter = this.onEnter; 106 | 107 | this._ngZone.run(() => { 108 | onEnter.next(); 109 | onEnter.complete(); 110 | }); 111 | } 112 | } 113 | 114 | /** Begin animation of keyboard entrance into view. */ 115 | enter() { 116 | if (!this._destroyed) { 117 | this._animationState = KeyboardAnimationState.Visible; 118 | this._changeDetectorRef.detectChanges(); 119 | } 120 | } 121 | 122 | /** Begin animation of the snack bar exiting from view. */ 123 | exit(): Observable { 124 | this._animationState = KeyboardAnimationState.Hidden; 125 | return this.onExit; 126 | } 127 | 128 | /** 129 | * Makes sure the exit callbacks have been invoked when the element is destroyed. 130 | */ 131 | ngOnDestroy() { 132 | this._destroyed = true; 133 | this._completeExit(); 134 | } 135 | 136 | /** 137 | * Waits for the zone to settle before removing the element. Helps prevent 138 | * errors where we end up removing an element which is in the middle of an animation. 139 | */ 140 | private _completeExit() { 141 | this._ngZone.onMicrotaskEmpty 142 | .asObservable() 143 | .first() 144 | .subscribe(() => { 145 | this.onExit.next(); 146 | this.onExit.complete(); 147 | }); 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /src/core/src/components/keyboard-key/keyboard-key.component.html: -------------------------------------------------------------------------------- 1 | 9 | {{ icon }} 10 | {{ key }} 11 | 12 | -------------------------------------------------------------------------------- /src/core/src/components/keyboard-key/keyboard-key.component.scss: -------------------------------------------------------------------------------- 1 | @import '~@angular/material/theming'; 2 | 3 | $mat-typography-config: mat-typography-config(); 4 | $mat-keyboard-button-distance: 8px !default; 5 | $mat-keyboard-capslock-animation-duration: $backdrop-animation-duration !default; 6 | $mat-keyboard-capslock-animation-timing-function: $backdrop-animation-timing-function !default; 7 | $mat-keyboard-capslock-led: #0f0 !default; 8 | $mat-keyboard-capslock-shine: #adff2f !default; 9 | 10 | :host { 11 | display: flex; 12 | font: { 13 | family: mat-font-family($mat-typography-config); 14 | size: mat-font-size($mat-typography-config, button); 15 | } 16 | justify-content: space-between; 17 | line-height: 20px; 18 | } 19 | 20 | .mat-keyboard { 21 | &-key { 22 | min-width: 0; 23 | width: 100%; 24 | 25 | &-active { 26 | background-color: mat-color($mat-grey, 300); 27 | } 28 | 29 | &-pressed { 30 | background-color: mat-color($mat-grey, 400); 31 | } 32 | 33 | // special treatment for caps lock 34 | &-capslock { 35 | background-color: mat-color($mat-grey, 'A100'); 36 | 37 | &:before { 38 | background-color: mat-color($mat-grey, 400); 39 | border-radius: 100%; 40 | content: ''; 41 | display: inline-block; 42 | height: 3px; 43 | left: 5px; 44 | position: absolute; 45 | top: 5px; 46 | transition: $mat-keyboard-capslock-animation-duration $mat-keyboard-capslock-animation-timing-function; 47 | transition-property: background-color, box-shadow; 48 | width: 3px; 49 | } 50 | 51 | &.mat-keyboard-key-active:before { 52 | background-color: $mat-keyboard-capslock-led; 53 | box-shadow: 0 0 §px $mat-keyboard-capslock-shine; 54 | } 55 | } 56 | } 57 | 58 | :host-context(.dark-theme) &-key { 59 | background-color: mat-color($mat-grey, 700); 60 | color: mat-color($mat-grey, 100); 61 | 62 | &-active { 63 | background-color: mat-color($mat-grey, 500); 64 | } 65 | 66 | &-pressed { 67 | background-color: mat-color($mat-grey, 600); 68 | } 69 | } 70 | 71 | // sass-lint:disable no-color-keywords no-color-literals 72 | :host-context(.debug) &-key { 73 | &-deadkey { 74 | background-color: cadetblue; 75 | } 76 | 77 | &-deadkey.mat-keyboard-key-active { 78 | background-color: lighten(cadetblue, 5%); 79 | } 80 | 81 | &-deadkey.mat-keyboard-key-pressed { 82 | background-color: lighten(cadetblue, 10%); 83 | } 84 | 85 | &-modifier { 86 | background-color: aquamarine; 87 | } 88 | 89 | &-modifier.mat-keyboard-key-active { 90 | background-color: lighten(aquamarine, 5%); 91 | } 92 | 93 | &-modifier.mat-keyboard-key-pressed { 94 | background-color: lighten(aquamarine, 10%); 95 | } 96 | } 97 | 98 | :host-context(.dark-theme.debug) &-key { 99 | &-deadkey { 100 | background-color: rebeccapurple; 101 | } 102 | 103 | &-deadkey.mat-keyboard-key-active { 104 | background-color: lighten(rebeccapurple, 5%); 105 | } 106 | 107 | &-deadkey.mat-keyboard-key-pressed { 108 | background-color: lighten(rebeccapurple, 10%); 109 | } 110 | 111 | &-modifier { 112 | background-color: mediumpurple; 113 | } 114 | 115 | &-modifier.mat-keyboard-key-active { 116 | background-color: lighten(mediumpurple, 5%); 117 | } 118 | 119 | &-modifier.mat-keyboard-key-pressed { 120 | background-color: lighten(mediumpurple, 10%); 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/core/src/components/keyboard-key/keyboard-key.component.ts: -------------------------------------------------------------------------------- 1 | import { ChangeDetectionStrategy, Component, ElementRef, EventEmitter, Inject, Input, OnInit, Output } from '@angular/core'; 2 | import { FormControl } from '@angular/forms'; 3 | 4 | import { BehaviorSubject } from 'rxjs/BehaviorSubject'; 5 | 6 | import { MAT_KEYBOARD_DEADKEYS } from '../../configs/keyboard-deadkey.config'; 7 | import { MAT_KEYBOARD_ICONS } from '../../configs/keyboard-icons.config'; 8 | import { KeyboardClassKey } from '../../enums/keyboard-class-key.enum'; 9 | import { IKeyboardDeadkeys } from '../../interfaces/keyboard-deadkeys.interface'; 10 | import { IKeyboardIcons } from '../../interfaces/keyboard-icons.interface'; 11 | 12 | export const VALUE_NEWLINE = '\n\r'; 13 | export const VALUE_SPACE = ' '; 14 | export const VALUE_TAB = '\t'; 15 | 16 | @Component({ 17 | selector: 'mat-keyboard-key', 18 | templateUrl: './keyboard-key.component.html', 19 | styleUrls: ['./keyboard-key.component.scss'], 20 | changeDetection: ChangeDetectionStrategy.OnPush, 21 | preserveWhitespaces: false 22 | }) 23 | export class MatKeyboardKeyComponent implements OnInit { 24 | 25 | private _deadkeyKeys: string[] = []; 26 | 27 | private _iconKeys: string[] = []; 28 | 29 | active$: BehaviorSubject = new BehaviorSubject(false); 30 | 31 | pressed$: BehaviorSubject = new BehaviorSubject(false); 32 | 33 | @Input() 34 | key: string | KeyboardClassKey; 35 | 36 | @Input() 37 | set active(active: boolean) { 38 | this.active$.next(active); 39 | } 40 | 41 | get active(): boolean { 42 | return this.active$.getValue(); 43 | } 44 | 45 | @Input() 46 | set pressed(pressed: boolean) { 47 | this.pressed$.next(pressed); 48 | } 49 | 50 | get pressed(): boolean { 51 | return this.pressed$.getValue(); 52 | } 53 | 54 | @Input() 55 | input?: ElementRef; 56 | 57 | @Input() 58 | control?: FormControl; 59 | 60 | @Output() 61 | genericClick = new EventEmitter(); 62 | 63 | @Output() 64 | enterClick = new EventEmitter(); 65 | 66 | @Output() 67 | bkspClick = new EventEmitter(); 68 | 69 | @Output() 70 | capsClick = new EventEmitter(); 71 | 72 | @Output() 73 | altClick = new EventEmitter(); 74 | 75 | @Output() 76 | shiftClick = new EventEmitter(); 77 | 78 | @Output() 79 | spaceClick = new EventEmitter(); 80 | 81 | @Output() 82 | tabClick = new EventEmitter(); 83 | 84 | @Output() 85 | keyClick = new EventEmitter(); 86 | 87 | get lowerKey(): string { 88 | return `${this.key}`.toLowerCase(); 89 | } 90 | 91 | get charCode(): number { 92 | return `${this.key}`.charCodeAt(0); 93 | } 94 | 95 | get isClassKey(): boolean { 96 | return this.key in KeyboardClassKey; 97 | } 98 | 99 | get isDeadKey(): boolean { 100 | return this._deadkeyKeys.some((deadKey: string) => deadKey === `${this.key}`); 101 | } 102 | 103 | get hasIcon(): boolean { 104 | return this._iconKeys.some((iconKey: string) => iconKey === `${this.key}`); 105 | } 106 | 107 | get icon(): string { 108 | return this._icons[this.key]; 109 | } 110 | 111 | get cssClass(): string { 112 | const classes = []; 113 | 114 | if (this.hasIcon) { 115 | classes.push('mat-keyboard-key-modifier'); 116 | classes.push(`mat-keyboard-key-${this.lowerKey}`); 117 | } 118 | 119 | if (this.isDeadKey) { 120 | classes.push('mat-keyboard-key-deadkey'); 121 | } 122 | 123 | return classes.join(' '); 124 | } 125 | 126 | get inputValue(): string { 127 | if (this.control) { 128 | return this.control.value; 129 | } else if (this.input && this.input.nativeElement && this.input.nativeElement.value) { 130 | return this.input.nativeElement.value; 131 | } else { 132 | return ''; 133 | } 134 | } 135 | 136 | set inputValue(inputValue: string) { 137 | if (this.control) { 138 | this.control.setValue(inputValue); 139 | } else if (this.input && this.input.nativeElement) { 140 | this.input.nativeElement.value = inputValue; 141 | } 142 | } 143 | 144 | // Inject dependencies 145 | constructor(@Inject(MAT_KEYBOARD_DEADKEYS) private _deadkeys: IKeyboardDeadkeys, 146 | @Inject(MAT_KEYBOARD_ICONS) private _icons: IKeyboardIcons) {} 147 | 148 | ngOnInit() { 149 | // read the deadkeys 150 | this._deadkeyKeys = Object.keys(this._deadkeys); 151 | 152 | // read the icons 153 | this._iconKeys = Object.keys(this._icons); 154 | } 155 | 156 | onClick(event: MouseEvent) { 157 | // Trigger a global key event 158 | // TODO: investigate 159 | this._triggerKeyEvent(); 160 | 161 | // Trigger generic click event 162 | this.genericClick.emit(event); 163 | 164 | // Manipulate the focused input / textarea value 165 | const value = this.inputValue; 166 | const caret = this.input ? this._getCursorPosition() : 0; 167 | 168 | let char: string; 169 | switch (this.key) { 170 | // this keys have no actions yet 171 | // TODO: add deadkeys and modifiers 172 | case KeyboardClassKey.Alt: 173 | case KeyboardClassKey.AltGr: 174 | case KeyboardClassKey.AltLk: 175 | this.altClick.emit(event); 176 | break; 177 | 178 | case KeyboardClassKey.Bksp: 179 | this.deleteSelectedText(); 180 | this.bkspClick.emit(event); 181 | break; 182 | 183 | case KeyboardClassKey.Caps: 184 | this.capsClick.emit(event); 185 | break; 186 | 187 | case KeyboardClassKey.Enter: 188 | if (this._isTextarea()) { 189 | char = VALUE_NEWLINE; 190 | } else { 191 | this.enterClick.emit(event); 192 | // TODO: trigger submit / onSubmit / ngSubmit properly (for the time being this has to be handled by the user himself) 193 | // console.log(this.control.ngControl.control.root) 194 | // this.input.nativeElement.form.submit(); 195 | } 196 | break; 197 | 198 | case KeyboardClassKey.Shift: 199 | this.shiftClick.emit(event); 200 | break; 201 | 202 | case KeyboardClassKey.Space: 203 | char = VALUE_SPACE; 204 | this.spaceClick.emit(event); 205 | break; 206 | 207 | case KeyboardClassKey.Tab: 208 | char = VALUE_TAB; 209 | this.tabClick.emit(event); 210 | break; 211 | 212 | default: 213 | // the key is not mapped or a string 214 | char = `${this.key}`; 215 | this.keyClick.emit(event); 216 | break; 217 | } 218 | 219 | if (char && this.input) { 220 | this.replaceSelectedText(char); 221 | this._setCursorPosition(caret + 1); 222 | } 223 | } 224 | 225 | private deleteSelectedText(): void { 226 | const value = this.inputValue; 227 | let caret = this.input ? this._getCursorPosition() : 0; 228 | let selectionLength = this._getSelectionLength(); 229 | if (selectionLength === 0) { 230 | if (caret === 0) { 231 | return; 232 | } 233 | 234 | caret--; 235 | selectionLength = 1; 236 | } 237 | 238 | const headPart = value.slice(0, caret); 239 | const endPart = value.slice(caret + selectionLength); 240 | 241 | this.inputValue = [headPart, endPart].join(''); 242 | this._setCursorPosition(caret); 243 | } 244 | 245 | private replaceSelectedText(char: string): void { 246 | const value = this.inputValue; 247 | const caret = this.input ? this._getCursorPosition() : 0; 248 | const selectionLength = this._getSelectionLength(); 249 | const headPart = value.slice(0, caret); 250 | const endPart = value.slice(caret + selectionLength); 251 | 252 | this.inputValue = [headPart, char, endPart].join(''); 253 | } 254 | 255 | private _triggerKeyEvent(): Event { 256 | const keyboardEvent = new KeyboardEvent('keydown'); 257 | // 258 | // keyboardEvent[initMethod]( 259 | // true, // bubbles 260 | // true, // cancelable 261 | // window, // viewArg: should be window 262 | // false, // ctrlKeyArg 263 | // false, // altKeyArg 264 | // false, // shiftKeyArg 265 | // false, // metaKeyArg 266 | // this.charCode, // keyCodeArg : unsigned long - the virtual key code, else 0 267 | // 0 // charCodeArgs : unsigned long - the Unicode character associated with the depressed key, else 0 268 | // ); 269 | // 270 | // window.document.dispatchEvent(keyboardEvent); 271 | 272 | return keyboardEvent; 273 | } 274 | 275 | // inspired by: 276 | // ref https://stackoverflow.com/a/2897510/1146207 277 | private _getCursorPosition(): number { 278 | if (!this.input) { 279 | return; 280 | } 281 | 282 | if ('selectionStart' in this.input.nativeElement) { 283 | // Standard-compliant browsers 284 | return this.input.nativeElement.selectionStart; 285 | } else if ('selection' in window.document) { 286 | // IE 287 | this.input.nativeElement.focus(); 288 | const sel = window.document['selection'].createRange(); 289 | const selLen = window.document['selection'].createRange().text.length; 290 | sel.moveStart('character', -this.control.value.length); 291 | 292 | return sel.text.length - selLen; 293 | } 294 | } 295 | 296 | private _getSelectionLength(): number { 297 | if (!this.input) { 298 | return; 299 | } 300 | 301 | if ('selectionEnd' in this.input.nativeElement) { 302 | // Standard-compliant browsers 303 | return this.input.nativeElement.selectionEnd - this.input.nativeElement.selectionStart; 304 | } 305 | 306 | if ('selection' in window.document) { 307 | // IE 308 | this.input.nativeElement.focus(); 309 | 310 | return window.document['selection'].createRange().text.length; 311 | } 312 | } 313 | 314 | // inspired by: 315 | // ref https://stackoverflow.com/a/12518737/1146207 316 | // tslint:disable one-line 317 | private _setCursorPosition(position: number): boolean { 318 | if (!this.input) { 319 | return; 320 | } 321 | 322 | this.inputValue = this.control.value; 323 | // ^ this is used to not only get "focus", but 324 | // to make sure we don't have it everything -selected- 325 | // (it causes an issue in chrome, and having it doesn't hurt any other browser) 326 | 327 | if ('createTextRange' in this.input.nativeElement) { 328 | const range = this.input.nativeElement.createTextRange(); 329 | range.move('character', position); 330 | range.select(); 331 | return true; 332 | } else { 333 | // (el.selectionStart === 0 added for Firefox bug) 334 | if (this.input.nativeElement.selectionStart || this.input.nativeElement.selectionStart === 0) { 335 | this.input.nativeElement.focus(); 336 | this.input.nativeElement.setSelectionRange(position, position); 337 | return true; 338 | } 339 | // fail city, fortunately this never happens (as far as I've tested) :) 340 | else { 341 | this.input.nativeElement.focus(); 342 | return false; 343 | } 344 | } 345 | } 346 | 347 | private _isTextarea(): boolean { 348 | return this.input && this.input.nativeElement && this.input.nativeElement.tagName === 'TEXTAREA'; 349 | } 350 | 351 | } 352 | -------------------------------------------------------------------------------- /src/core/src/components/keyboard/keyboard.component.html: -------------------------------------------------------------------------------- 1 | 5 | 6 | 9 | 10 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/core/src/components/keyboard/keyboard.component.scss: -------------------------------------------------------------------------------- 1 | @import '~@angular/material/theming'; 2 | 3 | $mat-typography-config: mat-typography-config(); 4 | $mat-keyboard-button-distance: 8px !default; 5 | $mat-keyboard-padding: 14px 24px !default; 6 | 7 | .mat-keyboard { 8 | &-wrapper { 9 | background-color: mat-color($mat-grey, 100); 10 | border-radius: 2px; 11 | display: flex; 12 | font: { 13 | family: mat-font-family($mat-typography-config); 14 | size: mat-font-size($mat-typography-config, button); 15 | } 16 | justify-content: space-between; 17 | //color: white; 18 | line-height: 20px; 19 | padding: $mat-keyboard-padding; 20 | 21 | &.dark-theme { 22 | background-color: mat-color($mat-grey, 800); 23 | } 24 | } 25 | 26 | &-action { 27 | background: none; 28 | color: inherit; 29 | flex-shrink: 0; 30 | font: { 31 | family: inherit; 32 | size: inherit; 33 | weight: 600; 34 | } 35 | line-height: 1; 36 | margin-left: $mat-keyboard-button-distance; 37 | text-transform: uppercase; 38 | } 39 | 40 | :host(.dark-theme) &-action { 41 | color: mat-color($mat-grey, 100); 42 | } 43 | 44 | &-layout { 45 | width: 100%; 46 | } 47 | 48 | &-row { 49 | align-items: stretch; 50 | display: flex; 51 | flex-direction: row; 52 | flex-wrap: nowrap; 53 | } 54 | 55 | &-col { 56 | box-sizing: border-box; 57 | flex: 1 1 auto; 58 | padding: $mat-keyboard-button-distance / 2; 59 | } 60 | 61 | &-key { 62 | min-width: 0; 63 | width: 100%; 64 | } 65 | 66 | :host(.dark-theme) &-key { 67 | background-color: mat-color($mat-grey, 700); 68 | color: mat-color($mat-grey, 100); 69 | } 70 | 71 | // sass-lint:disable no-color-keywords no-color-literals 72 | :host(.debug) &-key { 73 | &-deadkey { 74 | background-color: cadetblue; 75 | } 76 | 77 | &-modifier { 78 | background-color: aquamarine; 79 | } 80 | } 81 | 82 | :host(.debug.dark-theme) &-key { 83 | &-deadkey { 84 | background-color: rebeccapurple; 85 | } 86 | 87 | &-modifier { 88 | background-color: mediumpurple; 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/core/src/components/keyboard/keyboard.component.ts: -------------------------------------------------------------------------------- 1 | import { ChangeDetectionStrategy, Component, ElementRef, EventEmitter, HostBinding, HostListener, Inject, LOCALE_ID, OnInit, QueryList, ViewChildren } from '@angular/core'; 2 | import { AbstractControl } from '@angular/forms'; 3 | 4 | import { BehaviorSubject } from 'rxjs/BehaviorSubject'; 5 | import { Observable } from 'rxjs/Observable'; 6 | 7 | import { MatKeyboardRef } from '../../classes/keyboard-ref.class'; 8 | import { KeyboardClassKey } from '../../enums/keyboard-class-key.enum'; 9 | import { KeyboardModifier } from '../../enums/keyboard-modifier.enum'; 10 | import { IKeyboardLayout } from '../../interfaces/keyboard-layout.interface'; 11 | import { MatKeyboardService } from '../../services/keyboard.service'; 12 | import { MatKeyboardKeyComponent } from '../keyboard-key/keyboard-key.component'; 13 | 14 | /** 15 | * A component used to open as the default keyboard, matching material spec. 16 | * This should only be used internally by the keyboard service. 17 | */ 18 | @Component({ 19 | selector: 'mat-keyboard', 20 | templateUrl: './keyboard.component.html', 21 | styleUrls: ['./keyboard.component.scss'], 22 | changeDetection: ChangeDetectionStrategy.OnPush, 23 | preserveWhitespaces: false 24 | }) 25 | export class MatKeyboardComponent implements OnInit { 26 | 27 | private _darkTheme: BehaviorSubject = new BehaviorSubject(false); 28 | 29 | private _isDebug: BehaviorSubject = new BehaviorSubject(false); 30 | 31 | private _inputInstance$: BehaviorSubject = new BehaviorSubject(null); 32 | 33 | @ViewChildren(MatKeyboardKeyComponent) 34 | private _keys: QueryList; 35 | 36 | private _modifier: KeyboardModifier = KeyboardModifier.None; 37 | 38 | private _capsLocked = false; 39 | 40 | // the service provides a locale or layout optionally 41 | locale?: string; 42 | 43 | layout: IKeyboardLayout; 44 | 45 | control: AbstractControl; 46 | 47 | // the instance of the component making up the content of the keyboard 48 | keyboardRef: MatKeyboardRef; 49 | 50 | @HostBinding('class.mat-keyboard') 51 | cssClass = true; 52 | 53 | enterClick: EventEmitter = new EventEmitter(); 54 | 55 | capsClick: EventEmitter = new EventEmitter(); 56 | 57 | altClick: EventEmitter = new EventEmitter(); 58 | 59 | shiftClick: EventEmitter = new EventEmitter(); 60 | 61 | // returns an observable of the input instance 62 | get inputInstance(): Observable { 63 | return this._inputInstance$.asObservable(); 64 | } 65 | 66 | set darkTheme(darkTheme: boolean) { 67 | if (this._darkTheme.getValue() !== darkTheme) { 68 | this._darkTheme.next(darkTheme); 69 | } 70 | } 71 | 72 | set isDebug(isDebug: boolean) { 73 | if (this._isDebug.getValue() !== isDebug) { 74 | this._isDebug.next(isDebug); 75 | } 76 | } 77 | 78 | get darkTheme$(): Observable { 79 | return this._darkTheme.asObservable(); 80 | } 81 | 82 | get isDebug$(): Observable { 83 | return this._isDebug.asObservable(); 84 | } 85 | 86 | // inject dependencies 87 | constructor(@Inject(LOCALE_ID) private _locale: string, 88 | private _keyboardService: MatKeyboardService) {} 89 | 90 | setInputInstance(inputInstance: ElementRef) { 91 | this._inputInstance$.next(inputInstance); 92 | } 93 | 94 | attachControl(control: AbstractControl) { 95 | this.control = control; 96 | } 97 | 98 | ngOnInit() { 99 | // set a fallback using the locale 100 | if (!this.layout) { 101 | this.locale = this._keyboardService.mapLocale(this._locale) ? this._locale : 'en-US'; 102 | this.layout = this._keyboardService.getLayoutForLocale(this.locale); 103 | } 104 | } 105 | 106 | /** 107 | * dismisses the keyboard 108 | */ 109 | dismiss() { 110 | this.keyboardRef.dismiss(); 111 | } 112 | 113 | /** 114 | * checks if a given key is currently pressed 115 | * @param key 116 | * @param 117 | */ 118 | isActive(key: (string | KeyboardClassKey)[]): boolean { 119 | const modifiedKey: string = this.getModifiedKey(key); 120 | const isActiveCapsLock: boolean = modifiedKey === KeyboardClassKey.Caps && this._capsLocked; 121 | const isActiveModifier: boolean = modifiedKey === KeyboardModifier[this._modifier]; 122 | return isActiveCapsLock || isActiveModifier; 123 | } 124 | 125 | // retrieves modified key 126 | getModifiedKey(key: (string | KeyboardClassKey)[]): string { 127 | let modifier: KeyboardModifier = this._modifier; 128 | 129 | // `CapsLock` inverts the meaning of `Shift` 130 | if (this._capsLocked) { 131 | modifier = this._invertShiftModifier(this._modifier); 132 | } 133 | 134 | return key[modifier]; 135 | } 136 | 137 | /** 138 | * listens to users keyboard inputs to simulate on virtual keyboard, too 139 | * @param event 140 | */ 141 | @HostListener('document:keydown', ['$event']) 142 | onKeyDown(event: KeyboardEvent) { 143 | // 'activate' corresponding key 144 | this._keys 145 | .filter((key: MatKeyboardKeyComponent) => key.key === event.key) 146 | .forEach((key: MatKeyboardKeyComponent) => { 147 | key.pressed = true; 148 | }); 149 | 150 | // simulate modifier press 151 | if (event.key === KeyboardClassKey.Caps) { 152 | this.onCapsClick(event.getModifierState(KeyboardClassKey.Caps)); 153 | } 154 | if (event.key === KeyboardClassKey.Alt && this._modifier !== KeyboardModifier.Alt && this._modifier !== KeyboardModifier.ShiftAlt) { 155 | this.onAltClick(); 156 | } 157 | if (event.key === KeyboardClassKey.Shift && this._modifier !== KeyboardModifier.Shift && this._modifier !== KeyboardModifier.ShiftAlt) { 158 | this.onShiftClick(); 159 | } 160 | } 161 | 162 | /** 163 | * listens to users keyboard inputs to simulate on virtual keyboard, too 164 | * @param event 165 | */ 166 | @HostListener('document:keyup', ['$event']) 167 | onKeyUp(event: KeyboardEvent) { 168 | // 'deactivate' corresponding key 169 | this._keys 170 | .filter((key: MatKeyboardKeyComponent) => key.key === event.key) 171 | .forEach((key: MatKeyboardKeyComponent) => { 172 | key.pressed = false; 173 | }); 174 | 175 | // simulate modifier release 176 | if (event.key === KeyboardClassKey.Alt && (this._modifier === KeyboardModifier.Alt || this._modifier === KeyboardModifier.ShiftAlt)) { 177 | this.onAltClick(); 178 | } 179 | if (event.key === KeyboardClassKey.Shift && (this._modifier === KeyboardModifier.Shift || this._modifier === KeyboardModifier.ShiftAlt)) { 180 | this.onShiftClick(); 181 | } 182 | } 183 | 184 | /** 185 | * bubbles event if submit is potentially triggered 186 | */ 187 | onEnterClick() { 188 | // notify subscribers 189 | this.enterClick.next(); 190 | } 191 | 192 | /** 193 | * simulates clicking `CapsLock` key 194 | * @param targetState 195 | */ 196 | onCapsClick(targetState = !this._capsLocked) { 197 | // not implemented 198 | this._capsLocked = targetState; 199 | 200 | // notify subscribers 201 | this.capsClick.next(); 202 | } 203 | 204 | /** 205 | * simulates clicking `Alt` key 206 | */ 207 | onAltClick() { 208 | // invert modifier meaning 209 | this._modifier = this._invertAltModifier(this._modifier); 210 | 211 | // notify subscribers 212 | this.altClick.next(); 213 | } 214 | 215 | /** 216 | * simulates clicking `Shift` key 217 | */ 218 | onShiftClick() { 219 | // invert modifier meaning 220 | this._modifier = this._invertShiftModifier(this._modifier); 221 | 222 | // notify subscribers 223 | this.shiftClick.next(); 224 | } 225 | 226 | private _invertAltModifier(modifier: KeyboardModifier): KeyboardModifier { 227 | switch (modifier) { 228 | case KeyboardModifier.None: 229 | return KeyboardModifier.Alt; 230 | 231 | case KeyboardModifier.Shift: 232 | return KeyboardModifier.ShiftAlt; 233 | 234 | case KeyboardModifier.ShiftAlt: 235 | return KeyboardModifier.Shift; 236 | 237 | case KeyboardModifier.Alt: 238 | return KeyboardModifier.None; 239 | } 240 | } 241 | 242 | private _invertShiftModifier(modifier: KeyboardModifier): KeyboardModifier { 243 | switch (modifier) { 244 | case KeyboardModifier.None: 245 | return KeyboardModifier.Shift; 246 | 247 | case KeyboardModifier.Alt: 248 | return KeyboardModifier.ShiftAlt; 249 | 250 | case KeyboardModifier.ShiftAlt: 251 | return KeyboardModifier.Alt; 252 | 253 | case KeyboardModifier.Shift: 254 | return KeyboardModifier.None; 255 | } 256 | } 257 | 258 | } 259 | -------------------------------------------------------------------------------- /src/core/src/configs/keyboard-deadkey.config.ts: -------------------------------------------------------------------------------- 1 | // - Lay out each dead key set as an object of property/value 2 | // pairs. The rows below are wrapped so uppercase letters are 3 | // below their lowercase equivalents. 4 | // 5 | // - The property name is the letter pressed after the diacritic. 6 | // The property value is the letter this key-combo will generate. 7 | // 8 | // - Note that if you have created a new keyboard layout and want 9 | // it included in the distributed script, PLEASE TELL ME if you 10 | // have added additional dead keys to the ones below. 11 | import { InjectionToken } from '@angular/core'; 12 | import { IKeyboardDeadkeys } from '../interfaces/keyboard-deadkeys.interface'; 13 | 14 | const MAT_KEYBOARD_DEADKEYS = new InjectionToken('keyboard-deadkey.config'); 15 | const keyboardDeadkeys: IKeyboardDeadkeys = { 16 | '"': { 17 | 'a': '\u00e4', 18 | 'e': '\u00eb', 19 | 'i': '\u00ef', 20 | 'o': '\u00f6', 21 | 'u': '\u00fc', 22 | 'y': '\u00ff', 23 | '\u03b9': '\u03ca', 24 | '\u03c5': '\u03cb', 25 | '\u016B': '\u01D6', 26 | '\u00FA': '\u01D8', 27 | '\u01D4': '\u01DA', 28 | '\u00F9': '\u01DC', 29 | 'A': '\u00c4', 30 | 'E': '\u00cb', 31 | 'I': '\u00cf', 32 | 'O': '\u00d6', 33 | 'U': '\u00dc', 34 | 'Y': '\u0178', 35 | '\u0399': '\u03aa', 36 | '\u03a5': '\u03ab', 37 | '\u016A': '\u01D5', 38 | '\u00DA': '\u01D7', 39 | '\u01D3': '\u01D9', 40 | '\u00D9': '\u01DB', 41 | '\u304b': '\u304c', 42 | '\u304d': '\u304e', 43 | '\u304f': '\u3050', 44 | '\u3051': '\u3052', 45 | '\u3053': '\u3054', 46 | '\u305f': '\u3060', 47 | '\u3061': '\u3062', 48 | '\u3064': '\u3065', 49 | '\u3066': '\u3067', 50 | '\u3068': '\u3069', 51 | '\u3055': '\u3056', 52 | '\u3057': '\u3058', 53 | '\u3059': '\u305a', 54 | '\u305b': '\u305c', 55 | '\u305d': '\u305e', 56 | '\u306f': '\u3070', 57 | '\u3072': '\u3073', 58 | '\u3075': '\u3076', 59 | '\u3078': '\u3079', 60 | '\u307b': '\u307c', 61 | '\u30ab': '\u30ac', 62 | '\u30ad': '\u30ae', 63 | '\u30af': '\u30b0', 64 | '\u30b1': '\u30b2', 65 | '\u30b3': '\u30b4', 66 | '\u30bf': '\u30c0', 67 | '\u30c1': '\u30c2', 68 | '\u30c4': '\u30c5', 69 | '\u30c6': '\u30c7', 70 | '\u30c8': '\u30c9', 71 | '\u30b5': '\u30b6', 72 | '\u30b7': '\u30b8', 73 | '\u30b9': '\u30ba', 74 | '\u30bb': '\u30bc', 75 | '\u30bd': '\u30be', 76 | '\u30cf': '\u30d0', 77 | '\u30d2': '\u30d3', 78 | '\u30d5': '\u30d6', 79 | '\u30d8': '\u30d9', 80 | '\u30db': '\u30dc' 81 | }, 82 | '~': { // Tilde / Stroke 83 | 'a': '\u00e3', 'l': '\u0142', 'n': '\u00f1', 'o': '\u00f5', 84 | 'A': '\u00c3', 'L': '\u0141', 'N': '\u00d1', 'O': '\u00d5' 85 | }, 86 | '^': { // Circumflex 87 | 'a': '\u00e2', 'e': '\u00ea', 'i': '\u00ee', 'o': '\u00f4', 'u': '\u00fb', 'w': '\u0175', 'y': '\u0177', 88 | 'A': '\u00c2', 'E': '\u00ca', 'I': '\u00ce', 'O': '\u00d4', 'U': '\u00db', 'W': '\u0174', 'Y': '\u0176' 89 | }, 90 | '\u02c7': { // Baltic caron 91 | 'c': '\u010D', 92 | 'd': '\u010f', 93 | 'e': '\u011b', 94 | 's': '\u0161', 95 | 'l': '\u013e', 96 | 'n': '\u0148', 97 | 'r': '\u0159', 98 | 't': '\u0165', 99 | 'u': '\u01d4', 100 | 'z': '\u017E', 101 | '\u00fc': '\u01da', 102 | 'C': '\u010C', 103 | 'D': '\u010e', 104 | 'E': '\u011a', 105 | 'S': '\u0160', 106 | 'L': '\u013d', 107 | 'N': '\u0147', 108 | 'R': '\u0158', 109 | 'T': '\u0164', 110 | 'U': '\u01d3', 111 | 'Z': '\u017D', 112 | '\u00dc': '\u01d9' 113 | }, 114 | '\u02d8': { // Romanian and Turkish breve 115 | 'a': '\u0103', 'g': '\u011f', 116 | 'A': '\u0102', 'G': '\u011e' 117 | }, 118 | '-': { // Macron 119 | 'a': '\u0101', 120 | 'e': '\u0113', 121 | 'i': '\u012b', 122 | 'o': '\u014d', 123 | 'u': '\u016B', 124 | 'y': '\u0233', 125 | '\u00fc': '\u01d6', 126 | 'A': '\u0100', 127 | 'E': '\u0112', 128 | 'I': '\u012a', 129 | 'O': '\u014c', 130 | 'U': '\u016A', 131 | 'Y': '\u0232', 132 | '\u00dc': '\u01d5' 133 | }, 134 | '`': { // Grave 135 | 'a': '\u00e0', 'e': '\u00e8', 'i': '\u00ec', 'o': '\u00f2', 'u': '\u00f9', '\u00fc': '\u01dc', 136 | 'A': '\u00c0', 'E': '\u00c8', 'I': '\u00cc', 'O': '\u00d2', 'U': '\u00d9', '\u00dc': '\u01db' 137 | }, 138 | '\'': { // Acute / Greek Tonos 139 | 'a': '\u00e1', 140 | 'e': '\u00e9', 141 | 'i': '\u00ed', 142 | 'o': '\u00f3', 143 | 'u': '\u00fa', 144 | 'y': '\u00fd', 145 | '\u03b1': '\u03ac', 146 | '\u03b5': '\u03ad', 147 | '\u03b7': '\u03ae', 148 | '\u03b9': '\u03af', 149 | '\u03bf': '\u03cc', 150 | '\u03c5': '\u03cd', 151 | '\u03c9': '\u03ce', 152 | '\u00fc': '\u01d8', 153 | 'A': '\u00c1', 154 | 'E': '\u00c9', 155 | 'I': '\u00cd', 156 | 'O': '\u00d3', 157 | 'U': '\u00da', 158 | 'Y': '\u00dd', 159 | '\u0391': '\u0386', 160 | '\u0395': '\u0388', 161 | '\u0397': '\u0389', 162 | '\u0399': '\u038a', 163 | '\u039f': '\u038c', 164 | '\u03a5': '\u038e', 165 | '\u03a9': '\u038f', 166 | '\u00dc': '\u01d7' 167 | }, 168 | '\u02dd': {// Hungarian Double Acute Accent 169 | 'o': '\u0151', 'u': '\u0171', 170 | 'O': '\u0150', 'U': '\u0170' 171 | }, 172 | '\u0385': { // Greek Dialytika + Tonos 173 | '\u03b9': '\u0390', '\u03c5': '\u03b0' 174 | }, 175 | '\u00b0': { // Ring 176 | 'a': '\u00e5', 'u': '\u016f', 177 | 'A': '\u00c5', 'U': '\u016e' 178 | }, 179 | '\u02DB': { // Ogonek 180 | 'a': '\u0106', 'e': '\u0119', 'i': '\u012f', 'o': '\u01eb', 'u': '\u0173', 'y': '\u0177', 181 | 'A': '\u0105', 'E': '\u0118', 'I': '\u012e', 'O': '\u01ea', 'U': '\u0172', 'Y': '\u0176' 182 | }, 183 | '\u02D9': { // Dot-above 184 | 'c': '\u010B', 'e': '\u0117', 'g': '\u0121', 'z': '\u017C', 185 | 'C': '\u010A', 'E': '\u0116', 'G': '\u0120', 'Z': '\u017B' 186 | }, 187 | '\u00B8': { // Cedilla 188 | 'c': '\u00e7', 's': '\u015F', 189 | 'C': '\u00c7', 'S': '\u015E' 190 | }, 191 | /*',': { // Comma 192 | 's': (this.VKI_isIElt8) ? '\u015F' : '\u0219', 't': (this.VKI_isIElt8) ? '\u0163' : '\u021B', 193 | 'S': (this.VKI_isIElt8) ? '\u015E' : '\u0218', 'T': (this.VKI_isIElt8) ? '\u0162' : '\u021A' 194 | },*/ 195 | '\u3002': { // Hiragana/Katakana Point 196 | '\u306f': '\u3071', '\u3072': '\u3074', '\u3075': '\u3077', '\u3078': '\u307a', '\u307b': '\u307d', 197 | '\u30cf': '\u30d1', '\u30d2': '\u30d4', '\u30d5': '\u30d7', '\u30d8': '\u30da', '\u30db': '\u30dd' 198 | } 199 | }; 200 | 201 | // aliases 202 | // Macron 203 | keyboardDeadkeys['\u00af'] = keyboardDeadkeys['-']; 204 | // Umlaut / Diaeresis / Greek Dialytika / Hiragana/Katakana Voiced Sound Mark 205 | keyboardDeadkeys['\u00a8'] = keyboardDeadkeys['\u309B'] = keyboardDeadkeys['"']; 206 | // Acute / Greek Tonos 207 | keyboardDeadkeys['\u00b4'] = keyboardDeadkeys['\u0384'] = keyboardDeadkeys['\'']; 208 | // Ring 209 | keyboardDeadkeys['\u00ba'] = keyboardDeadkeys['\u00b0']; 210 | keyboardDeadkeys['\u201a'] = keyboardDeadkeys['\u00B8']; 211 | 212 | export { IKeyboardDeadkeys, MAT_KEYBOARD_DEADKEYS, keyboardDeadkeys }; 213 | -------------------------------------------------------------------------------- /src/core/src/configs/keyboard-icons.config.ts: -------------------------------------------------------------------------------- 1 | import { InjectionToken } from '@angular/core'; 2 | import { KeyboardClassKey } from '../enums/keyboard-class-key.enum'; 3 | import { IKeyboardIcons } from '../interfaces/keyboard-icons.interface'; 4 | 5 | const MAT_KEYBOARD_ICONS = new InjectionToken('keyboard-icons.config'); 6 | const keyboardIcons: IKeyboardIcons = { 7 | [KeyboardClassKey.Bksp]: 'keyboard_backspace', 8 | [KeyboardClassKey.Caps]: 'keyboard_capslock', 9 | [KeyboardClassKey.Enter]: 'keyboard_return', 10 | [KeyboardClassKey.Shift]: 'keyboard_arrow_up', 11 | [KeyboardClassKey.Space]: ' ', 12 | [KeyboardClassKey.Tab]: 'keyboard_tab' 13 | }; 14 | 15 | export { IKeyboardIcons, MAT_KEYBOARD_ICONS, keyboardIcons }; 16 | -------------------------------------------------------------------------------- /src/core/src/configs/keyboard-numpad.config.js.ref: -------------------------------------------------------------------------------- 1 | angular 2 | .module('material.components.keyboard') 3 | .constant('keyboardNumpad', [ 4 | [["$"], ["\u00a3"], ["\u20ac"], ["\u00a5"]], 5 | [["7"], ["8"], ["9"], ["/"]], 6 | [["4"], ["5"], ["6"], ["*"]], 7 | [["1"], ["2"], ["3"], ["-"]], 8 | [["0"], ["."], ["="], ["+"]] 9 | ]); 10 | -------------------------------------------------------------------------------- /src/core/src/configs/keyboard-symbols.config.js.ref: -------------------------------------------------------------------------------- 1 | angular 2 | .module('material.components.keyboard') 3 | .constant('keyboardSymbols', { 4 | '\u00a0': "NB\nSP", '\u200b': "ZW\nSP", '\u200c': "ZW\nNJ", '\u200d': "ZW\nJ" 5 | }); 6 | -------------------------------------------------------------------------------- /src/core/src/configs/keyboard.config.ts: -------------------------------------------------------------------------------- 1 | import { ViewContainerRef } from '@angular/core'; 2 | import { NgControl } from '@angular/forms'; 3 | import { AriaLivePoliteness } from '@angular/cdk/a11y'; 4 | 5 | export class MatKeyboardConfig { 6 | /** The politeness level for the MatAriaLiveAnnouncer announcement. */ 7 | politeness?: AriaLivePoliteness = 'assertive'; 8 | 9 | /** Message to be announced by the MatAriaLiveAnnouncer */ 10 | announcementMessage? = ''; 11 | 12 | /** The view container to place the overlay for the keyboard into. */ 13 | viewContainerRef?: ViewContainerRef = null; 14 | 15 | /** The length of time in milliseconds to wait before automatically dismissing the keyboard after blur. */ 16 | duration? = 0; 17 | 18 | /** Enable a dark keyboard **/ 19 | darkTheme? = null; 20 | 21 | /** Enable the debug view **/ 22 | isDebug? = false; 23 | 24 | /** Enable the debug view **/ 25 | ngControl?: NgControl; 26 | } 27 | -------------------------------------------------------------------------------- /src/core/src/directives/keyboard.directive.ts: -------------------------------------------------------------------------------- 1 | import { Directive, ElementRef, EventEmitter, HostListener, Input, OnDestroy, Optional, Output, Self } from '@angular/core'; 2 | import { NgControl } from '@angular/forms'; 3 | 4 | import { MatKeyboardRef } from '../classes/keyboard-ref.class'; 5 | import { MatKeyboardComponent } from '../components/keyboard/keyboard.component'; 6 | import { MatKeyboardService } from '../services/keyboard.service'; 7 | 8 | @Directive({ 9 | selector: 'input[matKeyboard], textarea[matKeyboard]' 10 | }) 11 | export class MatKeyboardDirective implements OnDestroy { 12 | 13 | private _keyboardRef: MatKeyboardRef; 14 | 15 | @Input() matKeyboard: string; 16 | 17 | @Input() darkTheme: boolean; 18 | 19 | @Input() duration: number; 20 | 21 | @Input() isDebug: boolean; 22 | 23 | @Output() enterClick: EventEmitter = new EventEmitter(); 24 | 25 | @Output() capsClick: EventEmitter = new EventEmitter(); 26 | 27 | @Output() altClick: EventEmitter = new EventEmitter(); 28 | 29 | @Output() shiftClick: EventEmitter = new EventEmitter(); 30 | 31 | constructor(private _elementRef: ElementRef, 32 | private _keyboardService: MatKeyboardService, 33 | @Optional() @Self() private _control?: NgControl) {} 34 | 35 | ngOnDestroy() { 36 | this._hideKeyboard(); 37 | } 38 | 39 | @HostListener('focus', ['$event']) 40 | private _showKeyboard() { 41 | this._keyboardRef = this._keyboardService.open(this.matKeyboard, { 42 | darkTheme: this.darkTheme, 43 | duration: this.duration, 44 | isDebug: this.isDebug 45 | }); 46 | 47 | // reference the input element 48 | this._keyboardRef.instance.setInputInstance(this._elementRef); 49 | 50 | // set control if given, cast to smth. non-abstract 51 | if (this._control) { 52 | this._keyboardRef.instance.attachControl(this._control.control); 53 | } 54 | 55 | // connect outputs 56 | this._keyboardRef.instance.enterClick.subscribe(() => this.enterClick.next()); 57 | this._keyboardRef.instance.capsClick.subscribe(() => this.capsClick.next()); 58 | this._keyboardRef.instance.altClick.subscribe(() => this.altClick.next()); 59 | this._keyboardRef.instance.shiftClick.subscribe(() => this.shiftClick.next()); 60 | } 61 | 62 | @HostListener('blur', ['$event']) 63 | private _hideKeyboard() { 64 | if (this._keyboardRef) { 65 | this._keyboardRef.dismiss(); 66 | } 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /src/core/src/enums/keyboard-animation-state.enum.ts: -------------------------------------------------------------------------------- 1 | export enum KeyboardAnimationState { 2 | Void = 'void', 3 | Visible = 'visible', 4 | Hidden = 'hidden' 5 | } 6 | -------------------------------------------------------------------------------- /src/core/src/enums/keyboard-animation-transition.enum.ts: -------------------------------------------------------------------------------- 1 | // import { KeyboardAnimationState } from './keyboard-animation-state.enum'; 2 | // 3 | // export enum KeyboardAnimationTransition { 4 | // Hide = `${KeyboardAnimationState.Visible} => ${KeyboardAnimationState.Hidden}`, 5 | // Show = `${KeyboardAnimationState.Void} => ${KeyboardAnimationState.Visible}` 6 | // } 7 | 8 | export enum KeyboardAnimationTransition { 9 | Hide = 'visible => hidden', 10 | Show = 'void => visible' 11 | } 12 | -------------------------------------------------------------------------------- /src/core/src/enums/keyboard-class-key.enum.ts: -------------------------------------------------------------------------------- 1 | // TODO: use real string based enums (available sine typescript 1.4) if 2 | // [tslint](https://github.com/palantir/tslint/issues/2993) and more important 3 | // [rollup](https://github.com/angular/angular/issues/17516) support it 4 | export enum KeyboardClassKey { 5 | Alt = 'Alt', 6 | AltGr = 'AltGraph', 7 | AltLk = 'AltLk', 8 | Bksp = 'Backspace', 9 | Caps = 'CapsLock', 10 | Enter = 'Enter', 11 | Shift = 'Shift', 12 | Space = ' ', 13 | Tab = 'Tab' 14 | } 15 | -------------------------------------------------------------------------------- /src/core/src/enums/keyboard-modifier.enum.ts: -------------------------------------------------------------------------------- 1 | // this enum index has to be number based because it is used 2 | // to access the keyboard configs alternative key assignment 3 | export enum KeyboardModifier { 4 | None, 5 | Shift, 6 | Alt, 7 | ShiftAlt 8 | } 9 | -------------------------------------------------------------------------------- /src/core/src/interfaces/keyboard-deadkeys.interface.ts: -------------------------------------------------------------------------------- 1 | export interface IKeyboardDeadkeys { 2 | [deadkey: string]: { 3 | [target: string]: string; 4 | }; 5 | } 6 | -------------------------------------------------------------------------------- /src/core/src/interfaces/keyboard-icons.interface.ts: -------------------------------------------------------------------------------- 1 | export interface IKeyboardIcons { 2 | [key: string]: string; 3 | } 4 | -------------------------------------------------------------------------------- /src/core/src/interfaces/keyboard-layout.interface.ts: -------------------------------------------------------------------------------- 1 | import { KeyboardClassKey } from '../enums/keyboard-class-key.enum'; 2 | 3 | export interface IKeyboardLayout { 4 | name: string; 5 | keys: (string | KeyboardClassKey)[][][]; 6 | lang?: string[]; 7 | } 8 | -------------------------------------------------------------------------------- /src/core/src/interfaces/keyboard-layouts.interface.ts: -------------------------------------------------------------------------------- 1 | import { IKeyboardLayout } from './keyboard-layout.interface'; 2 | 3 | export interface IKeyboardLayouts { 4 | [layout: string]: IKeyboardLayout; 5 | } 6 | -------------------------------------------------------------------------------- /src/core/src/interfaces/locale-map.interface.ts: -------------------------------------------------------------------------------- 1 | export interface ILocaleMap { 2 | [locale: string]: string; 3 | } 4 | -------------------------------------------------------------------------------- /src/core/src/keyboard.module.ts: -------------------------------------------------------------------------------- 1 | // External modules 2 | import { CommonModule } from '@angular/common'; 3 | import { NgModule } from '@angular/core'; 4 | // Angular CDK 5 | import { LIVE_ANNOUNCER_PROVIDER } from '@angular/cdk/a11y'; 6 | import { OverlayModule } from '@angular/cdk/overlay'; 7 | import { PortalModule } from '@angular/cdk/portal'; 8 | // Angular material 9 | import { MatCommonModule } from '@angular/material/core'; 10 | import { MatButtonModule } from '@angular/material/button'; 11 | import { MatIconModule } from '@angular/material/icon'; 12 | import { MatInputModule } from '@angular/material/input'; 13 | // Configs 14 | import { keyboardDeadkeys, MAT_KEYBOARD_DEADKEYS } from './configs/keyboard-deadkey.config'; 15 | import { keyboardIcons, MAT_KEYBOARD_ICONS } from './configs/keyboard-icons.config'; 16 | import { keyboardLayouts, MAT_KEYBOARD_LAYOUTS } from './configs/keyboard-layouts.config'; 17 | // Components and directives 18 | import { MatKeyboardContainerComponent } from './components/keyboard-container/keyboard-container.component'; 19 | import { MatKeyboardKeyComponent } from './components/keyboard-key/keyboard-key.component'; 20 | import { MatKeyboardComponent } from './components/keyboard/keyboard.component'; 21 | import { MatKeyboardDirective } from './directives/keyboard.directive'; 22 | // Providers 23 | import { MatKeyboardKebabCasePipe } from './pipes/kebab-case.pipe'; 24 | import { MatKeyboardService } from './services/keyboard.service'; 25 | 26 | @NgModule({ 27 | imports: [ 28 | // Angular modules 29 | CommonModule, 30 | OverlayModule, 31 | 32 | // Cdk modules 33 | PortalModule, 34 | 35 | // Material modules 36 | MatButtonModule, 37 | MatCommonModule, 38 | MatIconModule, 39 | MatInputModule 40 | ], 41 | exports: [ 42 | MatKeyboardComponent, 43 | MatKeyboardContainerComponent, 44 | MatKeyboardKeyComponent, 45 | MatKeyboardDirective 46 | ], 47 | declarations: [ 48 | MatKeyboardKebabCasePipe, 49 | MatKeyboardComponent, 50 | MatKeyboardContainerComponent, 51 | MatKeyboardKeyComponent, 52 | MatKeyboardDirective 53 | ], 54 | entryComponents: [ 55 | MatKeyboardComponent, 56 | MatKeyboardContainerComponent, 57 | MatKeyboardKeyComponent 58 | ], 59 | providers: [ 60 | MatKeyboardService, 61 | LIVE_ANNOUNCER_PROVIDER, 62 | { provide: MAT_KEYBOARD_DEADKEYS, useValue: keyboardDeadkeys }, 63 | { provide: MAT_KEYBOARD_ICONS, useValue: keyboardIcons }, 64 | { provide: MAT_KEYBOARD_LAYOUTS, useValue: keyboardLayouts } 65 | ] 66 | }) 67 | export class MatKeyboardModule {} 68 | -------------------------------------------------------------------------------- /src/core/src/pipes/kebab-case.pipe.ts: -------------------------------------------------------------------------------- 1 | import { Pipe, PipeTransform } from '@angular/core'; 2 | 3 | @Pipe({ 4 | name: 'matKeyboardKebabCase', 5 | pure: false 6 | }) 7 | export class MatKeyboardKebabCasePipe implements PipeTransform { 8 | 9 | transform(value: string): string { 10 | return value.replace(/([a-z])([A-Z])/g, '$1-$2') 11 | .replace(/\s+/g, '-') 12 | .toLowerCase(); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/core/src/public_api.ts: -------------------------------------------------------------------------------- 1 | export * from './classes/keyboard-ref.class'; 2 | 3 | export * from './components/keyboard/keyboard.component'; 4 | export * from './components/keyboard-container/keyboard-container.component'; 5 | export * from './components/keyboard-key/keyboard-key.component'; 6 | 7 | export * from './configs/keyboard.config'; 8 | export * from './configs/keyboard-deadkey.config'; 9 | export * from './configs/keyboard-icons.config'; 10 | export * from './configs/keyboard-layouts.config'; 11 | 12 | export * from './directives/keyboard.directive'; 13 | 14 | export * from './enums/keyboard-class-key.enum'; 15 | export * from './enums/keyboard-modifier.enum'; 16 | export * from './enums/keyboard-animation-state.enum'; 17 | export * from './enums/keyboard-animation-transition.enum'; 18 | 19 | export * from './interfaces/keyboard-deadkeys.interface'; 20 | export * from './interfaces/keyboard-icons.interface'; 21 | export * from './interfaces/keyboard-layout.interface'; 22 | export * from './interfaces/keyboard-layouts.interface'; 23 | export * from './interfaces/locale-map.interface'; 24 | 25 | export * from './pipes/kebab-case.pipe'; 26 | 27 | export * from './services/keyboard.service'; 28 | 29 | export * from './utils/keyboard.utils'; 30 | 31 | export * from './keyboard.module'; 32 | -------------------------------------------------------------------------------- /src/core/src/services/keyboard.service.ts: -------------------------------------------------------------------------------- 1 | import { LiveAnnouncer } from '@angular/cdk/a11y'; 2 | import { Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay'; 3 | import { ComponentPortal } from '@angular/cdk/portal'; 4 | import { ComponentRef, Inject, Injectable, LOCALE_ID, Optional, SkipSelf } from '@angular/core'; 5 | 6 | import { MatKeyboardRef } from '../classes/keyboard-ref.class'; 7 | import { MatKeyboardContainerComponent } from '../components/keyboard-container/keyboard-container.component'; 8 | import { MatKeyboardComponent } from '../components/keyboard/keyboard.component'; 9 | import { MAT_KEYBOARD_LAYOUTS } from '../configs/keyboard-layouts.config'; 10 | import { MatKeyboardConfig } from '../configs/keyboard.config'; 11 | import { IKeyboardLayout } from '../interfaces/keyboard-layout.interface'; 12 | import { IKeyboardLayouts } from '../interfaces/keyboard-layouts.interface'; 13 | import { ILocaleMap } from '../interfaces/locale-map.interface'; 14 | import { _applyAvailableLayouts, _applyConfigDefaults } from '../utils/keyboard.utils'; 15 | 16 | /** 17 | * Service to dispatch Material Design keyboard. 18 | */ 19 | @Injectable() 20 | export class MatKeyboardService { 21 | /** 22 | * Reference to the current keyboard in the view *at this level* (in the Angular injector tree). 23 | * If there is a parent keyboard service, all operations should delegate to that parent 24 | * via `_openedKeyboardRef`. 25 | */ 26 | private _keyboardRefAtThisLevel: MatKeyboardRef | null = null; 27 | 28 | private _availableLocales: ILocaleMap = {}; 29 | 30 | /** Reference to the currently opened keyboard at *any* level. */ 31 | private get _openedKeyboardRef(): MatKeyboardRef | null { 32 | const parent = this._parentKeyboard; 33 | return parent ? parent._openedKeyboardRef : this._keyboardRefAtThisLevel; 34 | } 35 | 36 | private set _openedKeyboardRef(value: MatKeyboardRef) { 37 | if (this._parentKeyboard) { 38 | this._parentKeyboard._openedKeyboardRef = value; 39 | } else { 40 | this._keyboardRefAtThisLevel = value; 41 | } 42 | } 43 | 44 | get availableLocales(): ILocaleMap { 45 | return this._availableLocales; 46 | } 47 | 48 | get isOpened(): boolean { 49 | return !!this._openedKeyboardRef; 50 | } 51 | 52 | constructor(private _overlay: Overlay, 53 | private _live: LiveAnnouncer, 54 | @Inject(LOCALE_ID) private _defaultLocale: string, 55 | @Inject(MAT_KEYBOARD_LAYOUTS) private _layouts: IKeyboardLayouts, 56 | @Optional() @SkipSelf() private _parentKeyboard: MatKeyboardService) { 57 | // prepare available layouts mapping 58 | this._availableLocales = _applyAvailableLayouts(_layouts); 59 | } 60 | 61 | /** 62 | * Creates and dispatches a keyboard with a custom component for the content, removing any 63 | * currently opened keyboards. 64 | * 65 | * @param layoutOrLocale layout or locale to use. 66 | * @param config Extra configuration for the keyboard. 67 | */ 68 | openFromComponent(layoutOrLocale: string, config: MatKeyboardConfig): MatKeyboardRef { 69 | const keyboardRef: MatKeyboardRef = this._attachKeyboardContent(config); 70 | 71 | keyboardRef.instance.darkTheme = config.darkTheme; 72 | keyboardRef.instance.isDebug = config.isDebug; 73 | 74 | // a locale is provided 75 | if (this.availableLocales[layoutOrLocale]) { 76 | keyboardRef.instance.locale = layoutOrLocale; 77 | keyboardRef.instance.layout = this.getLayoutForLocale(layoutOrLocale); 78 | } 79 | 80 | // a layout name is provided 81 | if (this._layouts[layoutOrLocale]) { 82 | keyboardRef.instance.layout = this._layouts[layoutOrLocale]; 83 | keyboardRef.instance.locale = this._layouts[layoutOrLocale].lang && this._layouts[layoutOrLocale].lang.pop(); 84 | } 85 | 86 | // When the keyboard is dismissed, lower the keyboard counter. 87 | keyboardRef 88 | .afterDismissed() 89 | .subscribe(() => { 90 | // Clear the keyboard ref if it hasn't already been replaced by a newer keyboard. 91 | if (this._openedKeyboardRef === keyboardRef) { 92 | this._openedKeyboardRef = null; 93 | } 94 | }); 95 | 96 | if (this._openedKeyboardRef) { 97 | // If a keyboard is already in view, dismiss it and enter the 98 | // new keyboard after exit animation is complete. 99 | this._openedKeyboardRef 100 | .afterDismissed() 101 | .subscribe(() => { 102 | keyboardRef.containerInstance.enter(); 103 | }); 104 | this._openedKeyboardRef.dismiss(); 105 | } else { 106 | // If no keyboard is in view, enter the new keyboard. 107 | keyboardRef.containerInstance.enter(); 108 | } 109 | 110 | // If a dismiss timeout is provided, set up dismiss based on after the keyboard is opened. 111 | // if (configs.duration > 0) { 112 | // keyboardRef.afterOpened().subscribe(() => { 113 | // setTimeout(() => keyboardRef.dismiss(), configs.duration); 114 | // }); 115 | // } 116 | 117 | if (config.announcementMessage) { 118 | this._live.announce(config.announcementMessage, config.politeness); 119 | } 120 | 121 | this._openedKeyboardRef = keyboardRef; 122 | return this._openedKeyboardRef; 123 | } 124 | 125 | /** 126 | * Opens a keyboard with a message and an optional action. 127 | * @param layoutOrLocale A string representing the locale or the layout name to be used. 128 | * @param config Additional configuration options for the keyboard. 129 | */ 130 | open(layoutOrLocale: string = this._defaultLocale, config: MatKeyboardConfig = {}): MatKeyboardRef { 131 | const _config = _applyConfigDefaults(config); 132 | 133 | return this.openFromComponent(layoutOrLocale, _config); 134 | } 135 | 136 | /** 137 | * Dismisses the currently-visible keyboard. 138 | */ 139 | dismiss() { 140 | if (this._openedKeyboardRef) { 141 | this._openedKeyboardRef.dismiss(); 142 | } 143 | } 144 | 145 | /** 146 | * Map a given locale to a layout name. 147 | * @param locale The layout name 148 | */ 149 | mapLocale(locale: string = this._defaultLocale): string { 150 | let layout: string; 151 | const country = locale 152 | .split('-') 153 | .shift(); 154 | 155 | // search for layout matching the 156 | // first part, the country code 157 | if (this.availableLocales[country]) { 158 | layout = this.availableLocales[locale]; 159 | } 160 | 161 | // look if the detailed locale matches any layout 162 | if (this.availableLocales[locale]) { 163 | layout = this.availableLocales[locale]; 164 | } 165 | 166 | if (!layout) { 167 | throw Error(`No layout found for locale ${locale}`); 168 | } 169 | 170 | return layout; 171 | } 172 | 173 | getLayoutForLocale(locale: string): IKeyboardLayout { 174 | return this._layouts[this.mapLocale(locale)]; 175 | } 176 | 177 | /** 178 | * Attaches the keyboard container component to the overlay. 179 | */ 180 | private _attachKeyboardContainer(overlayRef: OverlayRef, config: MatKeyboardConfig): MatKeyboardContainerComponent { 181 | const containerPortal = new ComponentPortal(MatKeyboardContainerComponent, config.viewContainerRef); 182 | const containerRef: ComponentRef = overlayRef.attach(containerPortal); 183 | 184 | // set config 185 | containerRef.instance.keyboardConfig = config; 186 | 187 | return containerRef.instance; 188 | } 189 | 190 | /** 191 | * Places a new component as the content of the keyboard container. 192 | */ 193 | private _attachKeyboardContent(config: MatKeyboardConfig): MatKeyboardRef { 194 | const overlayRef = this._createOverlay(); 195 | const container = this._attachKeyboardContainer(overlayRef, config); 196 | const portal = new ComponentPortal(MatKeyboardComponent); 197 | const contentRef = container.attachComponentPortal(portal); 198 | return new MatKeyboardRef(contentRef.instance, container, overlayRef) as MatKeyboardRef; 199 | } 200 | 201 | /** 202 | * Creates a new overlay and places it in the correct location. 203 | */ 204 | private _createOverlay(): OverlayRef { 205 | const state = new OverlayConfig({ 206 | width: '100%' 207 | }); 208 | 209 | state.positionStrategy = this._overlay 210 | .position() 211 | .global() 212 | .centerHorizontally() 213 | .bottom('0'); 214 | 215 | return this._overlay.create(state); 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /src/core/src/utils/keyboard.utils.ts: -------------------------------------------------------------------------------- 1 | import { MatKeyboardConfig } from '../configs/keyboard.config'; 2 | import { IKeyboardLayouts } from '../interfaces/keyboard-layouts.interface'; 3 | import { ILocaleMap } from '../interfaces/locale-map.interface'; 4 | 5 | /** 6 | * Applies default options to the keyboard configs. 7 | * @param config The configuration to which the defaults will be applied. 8 | * @returns The new configuration object with defaults applied. 9 | */ 10 | export function _applyConfigDefaults(config: MatKeyboardConfig): MatKeyboardConfig { 11 | return Object.assign(new MatKeyboardConfig(), config); 12 | } 13 | 14 | /** 15 | * Applies available layouts. 16 | * @param layouts 17 | */ 18 | export function _applyAvailableLayouts(layouts: IKeyboardLayouts): ILocaleMap { 19 | const _availableLocales: ILocaleMap = {}; 20 | 21 | Object 22 | .keys(layouts) 23 | .filter((layout: string) => 'lang' in layouts[layout]) 24 | .forEach((layout: string) => { 25 | layouts[layout].lang.forEach((lang: string) => { 26 | _availableLocales[lang] = layout; 27 | }); 28 | }); 29 | 30 | return _availableLocales; 31 | } 32 | -------------------------------------------------------------------------------- /src/core/tslint.yml: -------------------------------------------------------------------------------- 1 | extends: 2 | - ../../tslint.yml 3 | rules: 4 | directive-selector: 5 | - true 6 | - attribute 7 | - camelCase 8 | - matKeyboard 9 | component-selector: 10 | - true 11 | - element 12 | - kebab-case 13 | - mat-keyboard 14 | pipe-naming: 15 | - true 16 | - camelCase 17 | - matKeyboard 18 | -------------------------------------------------------------------------------- /src/demo/app/app.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | keyboard 6 | Keyboard variants 7 | 8 | 9 | 10 | 11 | 12 | 13 | 18 | Debug special keys 19 | 20 | 21 | 22 | 23 | 28 | Dark theme 29 | 30 | 31 | 32 | 33 | 34 | 35 | 41 | Open default keyboard ({{defaultLocale}}) 42 | 43 | 44 | 45 | 46 | 51 | 55 | {{layout.layout.name}} 56 | ({{layout.name}}) 57 | 58 | 59 | 60 | 66 | Open selected keyboard ({{locale}}) 67 | 68 | 69 | 70 | 71 | 77 | Close keyboard 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 92 | Input to attach keyboard to 93 | 94 | 95 | {{ attachModelValue }} 96 | 97 | 98 | 99 | 105 | Attach default keyboard ({{defaultLocale}}) 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | assignment 116 | Test form 117 | 118 | 119 | 124 | 125 | 126 | 127 | 128 | 136 | Using ngModel two way binding 137 | 138 | 139 | {{ testModelValue }} 140 | 141 | 142 | 143 | 144 | 154 | Simple Element.value binding 155 | 156 | 157 | {{ testSimpleValue.value }} 158 | 159 | 160 | 161 | 162 | 170 | Reactive binding with formControl 171 | 172 | 173 | {{ testControlValue.value }} 174 | 175 | 176 | 177 | 178 | 187 | ngModel access by template binding 188 | 189 | 190 | {{ testModelReference.value }} 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | {{ submitedValue.control }}: {{ submitedValue.value }} 199 | 200 | 201 | 202 | 203 | 204 | 209 | Submit 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | -------------------------------------------------------------------------------- /src/demo/app/app.component.scss: -------------------------------------------------------------------------------- 1 | .content { 2 | margin: 40px 32px; 3 | } 4 | 5 | .row { 6 | align-items: flex-end; 7 | display: grid; 8 | grid-gap: 16px; 9 | grid-template-columns: repeat(auto-fit, minmax(245px, 1fr)); 10 | padding: 8px; 11 | 12 | .content-test-form &:last-of-type > .col:last-of-type { 13 | align-self: flex-start; 14 | padding-top: 8px; 15 | } 16 | } 17 | 18 | .col { 19 | position: relative; 20 | 21 | > .mat-input-container { 22 | display: block; 23 | } 24 | } 25 | 26 | .mat-raised-button { 27 | width: 100%; 28 | 29 | .mat-select + & { 30 | margin-top: 24px; 31 | } 32 | } 33 | 34 | .mat-select { 35 | display: block; 36 | } 37 | 38 | pre { 39 | background-color: #e0e0e0; 40 | border-radius: 3px; 41 | display: block; 42 | font: 11px/1.5 Monospaced, monospace; 43 | margin: 8px 0; 44 | min-height: 16px; 45 | padding: 4px 8px; 46 | white-space: normal; 47 | } 48 | -------------------------------------------------------------------------------- /src/demo/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, ElementRef, Inject, LOCALE_ID, OnDestroy, OnInit, ViewChild } from '@angular/core'; 2 | import { FormControl, NgControl, NgForm, NgModel } from '@angular/forms'; 3 | import { MatSlideToggleChange } from '@angular/material/slide-toggle'; 4 | 5 | import { IKeyboardLayout, MAT_KEYBOARD_LAYOUTS, MatKeyboardComponent, MatKeyboardRef, MatKeyboardService } from '@ngx-material-keyboard/core'; 6 | 7 | import { BehaviorSubject } from 'rxjs/BehaviorSubject'; 8 | import { Observable } from 'rxjs/Observable'; 9 | import { Subscription } from 'rxjs/Subscription'; 10 | import { MatInput } from '@angular/material'; 11 | 12 | @Component({ 13 | selector: 'mat-keyboard-demo-root', 14 | templateUrl: './app.component.html', 15 | styleUrls: ['./app.component.scss'] 16 | }) 17 | export class AppComponent implements OnInit, OnDestroy { 18 | 19 | private _enterSubscription: Subscription; 20 | 21 | private _keyboardRef: MatKeyboardRef; 22 | 23 | private _submittedForms = new BehaviorSubject<{ control: string, value: string }[][]>([]); 24 | 25 | @ViewChild('attachTo', { read: ElementRef }) 26 | private _attachToElement: ElementRef; 27 | 28 | @ViewChild('attachTo', { read: NgModel }) 29 | private _attachToControl: NgControl; 30 | 31 | get submittedForms(): Observable<{ control: string, value: string }[][]> { 32 | return this._submittedForms.asObservable(); 33 | } 34 | 35 | darkTheme: boolean; 36 | 37 | duration: number; 38 | 39 | isDebug: boolean; 40 | 41 | defaultLocale: string; 42 | 43 | layout: string; 44 | 45 | layouts: { 46 | name: string; 47 | layout: IKeyboardLayout; 48 | }[]; 49 | 50 | testModelValue = 'Sushi'; 51 | 52 | attachModelValue = ''; 53 | 54 | testControlValue = new FormControl({ value: 'Emmentaler', disabled: false }); 55 | 56 | get keyboardVisible(): boolean { 57 | return this._keyboardService.isOpened; 58 | } 59 | 60 | constructor(private _keyboardService: MatKeyboardService, 61 | @Inject(LOCALE_ID) public locale, 62 | @Inject(MAT_KEYBOARD_LAYOUTS) private _layouts) {} 63 | 64 | ngOnInit() { 65 | this.defaultLocale = ` ${this.locale}`.slice(1); 66 | this.layouts = Object 67 | .keys(this._layouts) 68 | .map((name: string) => ({ 69 | name, 70 | layout: this._layouts[name] 71 | })) 72 | .sort((a, b) => a.layout.name.localeCompare(b.layout.name)); 73 | } 74 | 75 | ngOnDestroy() { 76 | this.closeCurrentKeyboard(); 77 | } 78 | 79 | submitForm(form?: NgForm) { 80 | const submittedForms = this._submittedForms.getValue(); 81 | const submittedForm = Object 82 | .keys(form.controls) 83 | .map((control: string) => ({ 84 | control, 85 | value: form.controls[control].value 86 | })); 87 | submittedForms.push(submittedForm); 88 | this._submittedForms.next(submittedForms); 89 | } 90 | 91 | openKeyboard(locale = this.defaultLocale) { 92 | this._keyboardRef = this._keyboardService.open(locale, { 93 | darkTheme: this.darkTheme, 94 | duration: this.duration, 95 | isDebug: this.isDebug 96 | }); 97 | this._enterSubscription = this._keyboardRef.instance.enterClick.subscribe(() => { 98 | this.submitForm(); 99 | }); 100 | } 101 | 102 | closeCurrentKeyboard() { 103 | if (this._keyboardRef) { 104 | this._keyboardRef.dismiss(); 105 | } 106 | 107 | if (this._enterSubscription) { 108 | this._enterSubscription.unsubscribe(); 109 | } 110 | } 111 | 112 | openAttachedKeyboard(locale = this.defaultLocale) { 113 | this._keyboardRef = this._keyboardService.open(locale, { 114 | darkTheme: this.darkTheme, 115 | duration: this.duration, 116 | isDebug: this.isDebug 117 | }); 118 | 119 | // reference the input element 120 | this._keyboardRef.instance.setInputInstance(this._attachToElement); 121 | 122 | // set control 123 | this._keyboardRef.instance.attachControl(this._attachToControl.control); 124 | } 125 | 126 | toggleDebug(toggle: MatSlideToggleChange) { 127 | this.isDebug = toggle.checked; 128 | this._keyboardRef.instance.isDebug = this.isDebug; 129 | } 130 | 131 | toggleDarkTheme(toggle: MatSlideToggleChange) { 132 | this.darkTheme = toggle.checked; 133 | this._keyboardRef.instance.darkTheme = this.darkTheme; 134 | } 135 | 136 | } 137 | -------------------------------------------------------------------------------- /src/demo/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { FormsModule, ReactiveFormsModule } from '@angular/forms'; 3 | import { BrowserModule } from '@angular/platform-browser'; 4 | import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; 5 | 6 | import { MatButtonModule } from '@angular/material/button'; 7 | import { MatIconModule } from '@angular/material/icon'; 8 | import { MatInputModule } from '@angular/material/input'; 9 | import { MatSelectModule } from '@angular/material/select'; 10 | import { MatSlideToggleModule } from '@angular/material/slide-toggle'; 11 | import { MatTabsModule } from '@angular/material/tabs'; 12 | 13 | import { IKeyboardLayouts, keyboardLayouts, MAT_KEYBOARD_LAYOUTS, MatKeyboardModule } from '@ngx-material-keyboard/core'; 14 | 15 | import { AppComponent } from './app.component'; 16 | 17 | const customLyouts: IKeyboardLayouts = { 18 | ...keyboardLayouts, 19 | 'Tolles Layout': { 20 | 'name': 'Awesome layout', 21 | 'keys': [ 22 | [ 23 | ['1', '!'], 24 | ['2', '@'], 25 | ['3', '#'] 26 | ] 27 | ], 28 | 'lang': ['de-CH'] 29 | } 30 | }; 31 | 32 | @NgModule({ 33 | declarations: [ 34 | AppComponent 35 | ], 36 | imports: [ 37 | // Angular modules 38 | BrowserModule, 39 | BrowserAnimationsModule, 40 | FormsModule, 41 | ReactiveFormsModule, 42 | 43 | // Material modules 44 | MatButtonModule, 45 | MatIconModule, 46 | MatInputModule, 47 | MatSelectModule, 48 | MatSlideToggleModule, 49 | MatTabsModule, 50 | 51 | MatKeyboardModule 52 | ], 53 | providers: [ 54 | { provide: MAT_KEYBOARD_LAYOUTS, useValue: customLyouts } 55 | ], 56 | bootstrap: [ 57 | AppComponent 58 | ] 59 | }) 60 | export class AppModule {} 61 | -------------------------------------------------------------------------------- /src/demo/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngx-material-keyboard/core/d076ea9e1f0cd6f861fdf2bca66ab4bbaf905588/src/demo/assets/.gitkeep -------------------------------------------------------------------------------- /src/demo/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /src/demo/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // The file contents for the current environment will overwrite these during build. 2 | // The build system defaults to the dev environment which uses `environment.ts`, but if you do 3 | // `ng build --env=prod` then `environment.prod.ts` will be used instead. 4 | // The list of which env maps to which file can be found in `.angular-cli.json`. 5 | 6 | export const environment = { 7 | production: false 8 | }; 9 | -------------------------------------------------------------------------------- /src/demo/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngx-material-keyboard/core/d076ea9e1f0cd6f861fdf2bca66ab4bbaf905588/src/demo/favicon.ico -------------------------------------------------------------------------------- /src/demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Demo 6 | 7 | 8 | 11 | 15 | 18 | 19 | 20 | Loading... 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/demo/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 3 | import 'hammerjs'; 4 | 5 | import { AppModule } from './app/app.module'; 6 | import { environment } from './environments/environment'; 7 | 8 | if (environment.production) { 9 | enableProdMode(); 10 | } 11 | 12 | platformBrowserDynamic() 13 | .bootstrapModule(AppModule); 14 | -------------------------------------------------------------------------------- /src/demo/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file includes polyfills needed by Angular and is loaded before the app. 3 | * You can add your own extra polyfills to this file. 4 | * 5 | * This file is divided into 2 sections: 6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 8 | * file. 9 | * 10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), 12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html 15 | */ 16 | /*************************************************************************************************** 17 | * BROWSER POLYFILLS 18 | */ 19 | /** IE9, IE10 and IE11 requires all of the following polyfills. **/ 20 | import 'core-js/es6/symbol'; 21 | import 'core-js/es6/object'; 22 | import 'core-js/es6/function'; 23 | import 'core-js/es6/parse-int'; 24 | import 'core-js/es6/parse-float'; 25 | import 'core-js/es6/number'; 26 | import 'core-js/es6/math'; 27 | import 'core-js/es6/string'; 28 | import 'core-js/es6/date'; 29 | import 'core-js/es6/array'; 30 | import 'core-js/es6/regexp'; 31 | import 'core-js/es6/map'; 32 | import 'core-js/es6/set'; 33 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */ 34 | import 'classlist.js'; // Run `npm install --save classlist.js`. 35 | /** IE10 and IE11 requires the following to support `@angular/animation`. */ 36 | import 'web-animations-js'; // Run `npm install --save web-animations-js`. 37 | /** Evergreen browsers require these. **/ 38 | import 'core-js/es6/reflect'; 39 | import 'core-js/es7/reflect'; 40 | /** ALL Firefox browsers require the following to support `@angular/animation`. **/ 41 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`. 42 | /*************************************************************************************************** 43 | * Zone JS is required by Angular itself. 44 | */ 45 | import 'zone.js/dist/zone'; // Included with Angular CLI. 46 | 47 | 48 | /*************************************************************************************************** 49 | * APPLICATION IMPORTS 50 | */ 51 | 52 | /** 53 | * Date, currency, decimal and percent pipes. 54 | * Needed for: All but Chrome, Firefox, Edge, IE11 and Safari 10 55 | */ 56 | // import 'intl'; // Run `npm install --save intl`. 57 | /** 58 | * Need to import at least one locale-data with intl. 59 | */ 60 | // import 'intl/locale-data/jsonp/en'; 61 | -------------------------------------------------------------------------------- /src/demo/styles.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngx-material-keyboard/core/d076ea9e1f0cd6f861fdf2bca66ab4bbaf905588/src/demo/styles.scss -------------------------------------------------------------------------------- /src/demo/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js/dist/long-stack-trace-zone'; 4 | import 'zone.js/dist/proxy.js'; 5 | import 'zone.js/dist/sync-test'; 6 | import 'zone.js/dist/jasmine-patch'; 7 | import 'zone.js/dist/async-test'; 8 | import 'zone.js/dist/fake-async-test'; 9 | import { getTestBed } from '@angular/core/testing'; 10 | import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing'; 11 | 12 | // Unfortunately there's no typing for the `__karma__` variable. Just declare it as any. 13 | declare var __karma__: any; 14 | declare var require: any; 15 | 16 | // Prevent Karma from running prematurely. 17 | __karma__.loaded = function () {}; 18 | 19 | // First, initialize the Angular testing environment. 20 | getTestBed() 21 | .initTestEnvironment( 22 | BrowserDynamicTestingModule, 23 | platformBrowserDynamicTesting() 24 | ); 25 | // Then we find all the tests. 26 | const context = require.context('./', true, /\.spec\.ts$/); 27 | // And load the modules. 28 | context.keys() 29 | .map(context); 30 | // Finally, start Karma to run the tests. 31 | __karma__.start(); 32 | -------------------------------------------------------------------------------- /src/demo/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../out-tsc/app", 5 | "module": "es2015", 6 | "types": [] 7 | }, 8 | "exclude": [ 9 | "test.ts", 10 | "**/*.spec.ts" 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /src/demo/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../out-tsc/spec", 5 | "module": "commonjs", 6 | "target": "es5", 7 | "types": [ 8 | "jasmine", 9 | "node" 10 | ] 11 | }, 12 | "files": [ 13 | "test.ts" 14 | ], 15 | "include": [ 16 | "**/*.spec.ts", 17 | "**/*.d.ts" 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /src/demo/tslint.yml: -------------------------------------------------------------------------------- 1 | extends: 2 | - ../../tslint.yml 3 | rules: 4 | directive-selector: 5 | - true 6 | - attribute 7 | - camelCase 8 | - matKeyboardDemo 9 | component-selector: 10 | - true 11 | - element 12 | - kebab-case 13 | - mat-keyboard-demo 14 | pipe-naming: 15 | - true 16 | - camelCase 17 | - matKeyboardDemo 18 | -------------------------------------------------------------------------------- /src/demo/typings.d.ts: -------------------------------------------------------------------------------- 1 | /* SystemJS module definition */ 2 | declare var module: NodeModule; 3 | 4 | interface NodeModule { 5 | id: string; 6 | } 7 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "outDir": "./dist/out-tsc", 5 | "baseUrl": "./", 6 | "sourceMap": true, 7 | "declaration": false, 8 | "moduleResolution": "node", 9 | "emitDecoratorMetadata": true, 10 | "experimentalDecorators": true, 11 | "noUnusedParameters": false, 12 | "target": "es5", 13 | "typeRoots": [ 14 | "node_modules/@types" 15 | ], 16 | "lib": [ 17 | "es2017", 18 | "dom" 19 | ], 20 | "paths": { 21 | "@ngx-material-keyboard/core": [ "./src/core/src/public_api.ts" ] 22 | } 23 | }, 24 | "angularCompilerOptions": { 25 | "paths": { 26 | "@ngx-material-keyboard/core": [ "./src/core/src/public_api.ts" ] 27 | } 28 | }, 29 | "exclude": [ 30 | ".ng_build" 31 | ] 32 | } -------------------------------------------------------------------------------- /tslint.yml: -------------------------------------------------------------------------------- 1 | extends: 2 | - tslint:latest 3 | - rxjs-tslint-rules 4 | rules: 5 | # tslint rules 6 | array-type: 7 | - true 8 | - array 9 | experimentalDecorators: 10 | - true 11 | quotemark: 12 | - true 13 | - single 14 | interface-name: 15 | - never-prefix 16 | max-line-length: 17 | - true 18 | - 180 19 | member-access: 20 | - true 21 | - no-public 22 | member-ordering: 23 | - true 24 | - order: 25 | - public-static-field 26 | - public-static-method 27 | - private-instance-field 28 | - public-instance-field 29 | - public-constructor 30 | - public-instance-method 31 | - private-instance-method 32 | no-console: false 33 | no-string-literal: false 34 | no-submodule-imports: false 35 | object-literal-key-quotes: 36 | - consistent-as-needed 37 | object-literal-sort-keys: false 38 | trailing-comma: false 39 | typedef-whitespace: 40 | - true 41 | - call-signature: nospace 42 | index-signature: nospace 43 | parameter: nospace 44 | property-declaration: nospace 45 | variable-declaration: nospace 46 | variable-name: 47 | - true 48 | - check-format 49 | - allow-leading-underscore 50 | 51 | # rxjs-tslint-rules 52 | rxjs-no-add: 53 | severity: error 54 | rxjs-no-operator: 55 | severity: error 56 | rxjs-no-patched: 57 | severity: error 58 | rxjs-no-wholesale: 59 | severity: error 60 | --------------------------------------------------------------------------------
{{ attachModelValue }}
ngModel
{{ testModelValue }}
Element.value
{{ testSimpleValue.value }}
formControl
{{ testControlValue.value }}
{{ testModelReference.value }}
197 | 198 | {{ submitedValue.control }}: {{ submitedValue.value }} 199 | 200 |