├── .eslintignore
├── .eslintrc.json
├── .github
├── FUNDING.yml
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
├── dependabot.yml
├── github.md
├── pull_request_template.md
├── stale.yml
└── workflows
│ ├── main.yml
│ ├── publish.yml
│ ├── pull_request.yml
│ ├── pull_request_compl.yml
│ └── pull_request_dependabot.yml
├── .gitignore
├── .npmignore
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── babel.config.js
├── build
├── components
│ ├── Keyboard.d.ts
│ └── KeyboardModern.d.ts
├── css
│ └── index.css
├── index.d.ts
├── index.js
├── index.modern.d.ts
├── index.modern.esm.js
├── index.modern.esm.js.map
├── index.modern.js
├── index.modern.js.map
├── interfaces.d.ts
├── polyfills.d.ts
└── services
│ └── Utilities.d.ts
├── package-lock.json
├── package.json
├── scripts
├── generateKeyboardTypes.js
├── getPackageJson.js
├── loaderMock.js
└── testMock.js
├── src
├── demo
│ ├── App.tsx
│ ├── css
│ │ └── App.css
│ ├── images
│ │ ├── demo.gif
│ │ ├── keyboard.PNG
│ │ └── simple-keyboard.png
│ └── index.tsx
├── lib
│ ├── components
│ │ ├── Keyboard.tsx
│ │ ├── KeyboardModern.tsx
│ │ └── tests
│ │ │ ├── Keyboard.test.tsx
│ │ │ └── KeyboardModern.test.tsx
│ ├── index.modern.ts
│ ├── index.ts
│ ├── interfaces.d.ts
│ ├── polyfills.js
│ └── services
│ │ └── Utilities.ts
└── utils
│ ├── BaseDOM.js
│ └── TestUtility.js
├── tsconfig.json
├── webpack.config.demo.js
├── webpack.config.js
├── webpack.config.modern.js
└── webpack.config.modern_esm.js
/.eslintignore:
--------------------------------------------------------------------------------
1 | build/*
2 | /**/*.d.ts
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "@typescript-eslint/parser",
3 | "parserOptions": {
4 | "ecmaVersion": 6,
5 | "sourceType": "module",
6 | "ecmaFeatures": {
7 | "modules": true,
8 | "experimentalObjectRestSpread": true
9 | }
10 | },
11 | "plugins": ["@typescript-eslint"],
12 | "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
13 | "rules": {
14 | "comma-dangle": 0,
15 | "no-unused-vars": "warn",
16 | "no-unexpected-multiline": "warn",
17 | "prefer-const": "warn",
18 | "@typescript-eslint/no-empty-function": "off",
19 | "@typescript-eslint/explicit-module-boundary-types": "off",
20 | "@typescript-eslint/no-explicit-any": "off",
21 | "@typescript-eslint/no-var-requires": "off"
22 | },
23 | "settings": {},
24 | "env": {
25 | "browser": true,
26 | "node": true,
27 | "jasmine": true,
28 | "jest": true,
29 | "es6": true
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | buy_me_a_coffee: hodgef
2 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help improve react-simple-keyboard
4 | title: ''
5 | labels: ''
6 | assignees: hodgef
7 |
8 | ---
9 |
10 | **React-simple-keyboard version**
11 | As some bugs have been addressed in later versions, please ensure you are running the latest.
12 |
13 | **Describe the bug**
14 | A clear and concise description of what the bug is. Providing a [sandbox example](https://codesandbox.io/s/new) or code depicting the issue is important, as this will help us reproduce the issue.
15 |
16 | **Screenshots**
17 | If applicable, add screenshots to help explain your problem.
18 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea you'd like to see in react-simple-keyboard
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
21 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: npm
4 | directory: "/"
5 | schedule:
6 | interval: daily
7 | time: "04:00"
8 | timezone: America/Montreal
9 | open-pull-requests-limit: 10
10 | versioning-strategy: increase
11 |
--------------------------------------------------------------------------------
/.github/github.md:
--------------------------------------------------------------------------------
1 | # About .github
2 |
3 | This directory handles special features to be used on react-simple-keyboard's Github repository.
4 | It is not needed for simple-keyboard to run and can be safely removed.
5 |
--------------------------------------------------------------------------------
/.github/pull_request_template.md:
--------------------------------------------------------------------------------
1 | ## Description
2 |
3 | A few sentences describing the overall goals of the pull request's commits.
4 |
5 | ## Checks
6 |
7 | - [ ] I have read and followed the [Contributing Guidelines](https://github.com/hodgef/react-simple-keyboard/blob/master/CONTRIBUTING.md).
8 |
--------------------------------------------------------------------------------
/.github/stale.yml:
--------------------------------------------------------------------------------
1 | # Number of days of inactivity before an issue becomes stale
2 | daysUntilStale: 5
3 | # Number of days of inactivity before a stale issue is closed
4 | daysUntilClose: 2
5 | # Issues with these labels will never be considered stale
6 | exemptLabels:
7 | - pinned
8 | - security
9 | # Label to use when marking an issue as stale
10 | staleLabel: Stale
11 | # Comment to post when marking an issue as stale. Set to `false` to disable
12 | markComment: >
13 | This issue has been automatically marked as stale because it has not had
14 | recent activity. It will be closed if no further activity occurs. Thank you
15 | for your contributions.
16 | # Comment to post when closing a stale issue. Set to `false` to disable
17 | closeComment: false
18 |
--------------------------------------------------------------------------------
/.github/workflows/main.yml:
--------------------------------------------------------------------------------
1 | name: Build
2 | on:
3 | push:
4 | paths-ignore:
5 | - README.md
6 | - .gitignore
7 | - .github/**
8 |
9 | jobs:
10 | build:
11 | runs-on: ${{ matrix.os }}
12 | if: contains(github.ref, 'master')
13 | strategy:
14 | matrix:
15 | node-version: [20.x]
16 | os: [ubuntu-latest]
17 | steps:
18 | - uses: actions/checkout@v1
19 | - name: Use Node.js ${{ matrix.node_version }}
20 | uses: actions/setup-node@v1
21 | with:
22 | node-version: ${{ matrix.node_version }}
23 | - name: npm install, build, and test
24 | run: |
25 | export NODE_OPTIONS=--openssl-legacy-provider
26 | npm install
27 | npm run coverage
28 |
29 | - name: Discord notification
30 | continue-on-error: true
31 | if: success()
32 | env:
33 | DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
34 | uses: Ilshidur/action-discord@master
35 | with:
36 | args: "react-simple-keyboard - CI Build Passed"
37 |
38 | - name: Discord notification
39 | continue-on-error: true
40 | if: failure()
41 | env:
42 | DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
43 | uses: Ilshidur/action-discord@master
44 | with:
45 | args: "react-simple-keyboard - CI Build Failed"
46 |
--------------------------------------------------------------------------------
/.github/workflows/publish.yml:
--------------------------------------------------------------------------------
1 | name: Publish
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 | paths-ignore:
8 | - README.md
9 | - .gitignore
10 | - .github/**
11 |
12 | permissions:
13 | contents: write
14 | id-token: write
15 |
16 | jobs:
17 | publish:
18 | runs-on: ${{ matrix.os }}
19 | strategy:
20 | matrix:
21 | node-version: [20.x]
22 | os: [ubuntu-latest]
23 | steps:
24 | - uses: actions/checkout@v1
25 | - name: Use Node.js ${{ matrix.node_version }}
26 | uses: actions/setup-node@v1
27 | with:
28 | node-version: ${{ matrix.node_version }}
29 |
30 | - name: npm install, build, and test
31 | run: |
32 | export NODE_OPTIONS=--openssl-legacy-provider
33 | npm install
34 | npm run test -- --coverage --watchAll=false
35 |
36 | - name: Setup GIT
37 | run: |
38 | git reset --hard
39 | git config --local --list
40 | git checkout master
41 | git config user.email "$GH_EMAIL"
42 | git config user.name "Francisco Hodge"
43 | env:
44 | GH_EMAIL: ${{secrets.GH_EMAIL}}
45 |
46 | - name: Bump version
47 | run: |
48 | git reset --hard
49 | export NODE_OPTIONS=--openssl-legacy-provider
50 | npm version patch
51 | npm run build
52 | git add . || true
53 | git commit -m "Build update" || true
54 | git push "https://$GITHUB_ACTOR:$GITHUB_TOKEN@github.com/$GITHUB_REPOSITORY"
55 | env:
56 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
57 |
58 | - name: npm publish
59 | run: |
60 | export NODE_OPTIONS=--openssl-legacy-provider
61 | npm config set //registry.npmjs.org/:_authToken=$NODE_AUTH_TOKEN
62 | npm publish --provenance --access public
63 | env:
64 | NODE_AUTH_TOKEN: ${{secrets.NODE_AUTH_TOKEN}}
65 |
--------------------------------------------------------------------------------
/.github/workflows/pull_request.yml:
--------------------------------------------------------------------------------
1 | name: Build PR (Standard)
2 |
3 | on: pull_request
4 |
5 | jobs:
6 | build:
7 | runs-on: ${{ matrix.os }}
8 | if: ${{ github.actor != 'dependabot[bot]' }}
9 | strategy:
10 | matrix:
11 | node-version: [20.x]
12 | os: [ubuntu-latest]
13 | steps:
14 | - uses: actions/checkout@v1
15 | - name: Use Node.js ${{ matrix.node_version }}
16 | uses: actions/setup-node@v1
17 | with:
18 | node-version: ${{ matrix.node_version }}
19 | - name: npm install, build, and test
20 | run: |
21 | export NODE_OPTIONS=--openssl-legacy-provider
22 | npm install
23 | npm run coverage
24 | env:
25 | CI: true
26 |
--------------------------------------------------------------------------------
/.github/workflows/pull_request_compl.yml:
--------------------------------------------------------------------------------
1 | name: PR Compliance
2 |
3 | on:
4 | pull_request_target:
5 | types: [opened]
6 |
7 | jobs:
8 | build:
9 | runs-on: ${{ matrix.os }}
10 | if: ${{ github.actor != 'dependabot[bot]' }}
11 | strategy:
12 | matrix:
13 | node-version: [20.x]
14 | os: [ubuntu-latest]
15 | steps:
16 | - name: Checkout
17 | uses: actions/checkout@v3
18 |
19 | - name: Comment PR
20 | uses: thollander/actions-comment-pull-request@v2
21 | with:
22 | message: |
23 | :wave: Hello @${{ github.actor }}! Please make sure to review the [Contributing Guidelines](https://github.com/hodgef/react-simple-keyboard/blob/master/CONTRIBUTING.md) to ensure your PR is compliant. Thank you!
24 | reactions: eyes
25 |
--------------------------------------------------------------------------------
/.github/workflows/pull_request_dependabot.yml:
--------------------------------------------------------------------------------
1 | name: Build PR (Dependabot)
2 |
3 | on: pull_request_target
4 |
5 | jobs:
6 | build:
7 | runs-on: ${{ matrix.os }}
8 | if: ${{ github.actor == 'dependabot[bot]' }}
9 | strategy:
10 | matrix:
11 | node-version: [20.x]
12 | os: [ubuntu-latest]
13 | steps:
14 | - uses: actions/checkout@v2
15 | with:
16 | ref: ${{ github.event.pull_request.head.sha }}
17 | - name: Use Node.js ${{ matrix.node_version }}
18 | uses: actions/setup-node@v1
19 | with:
20 | node-version: ${{ matrix.node_version }}
21 | - name: npm install, build, and test
22 | run: |
23 | export NODE_OPTIONS=--openssl-legacy-provider
24 | npm install
25 | npm run coverage
26 | env:
27 | CI: true
28 | - name: Merge PR
29 | if: success()
30 | uses: "actions/github-script@v2"
31 | with:
32 | github-token: "${{ secrets.GH_KEY }}"
33 | script: |
34 | const pullRequest = context.payload.pull_request
35 | const repository = context.repo
36 |
37 | await github.pulls.merge({
38 | merge_method: "merge",
39 | owner: repository.owner,
40 | pull_number: pullRequest.number,
41 | repo: repository.repo,
42 | })
43 | - name: Reject PR
44 | if: failure()
45 | uses: peter-evans/close-pull@v1
46 | with:
47 | pull-request-number: ${{github.event.number}}
48 | comment: "Closing PR due to failing tests."
49 | delete-branch: true
50 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # misc
12 | .DS_Store
13 | .env.local
14 | .env.development.local
15 | .env.test.local
16 | .env.production.local
17 | .vscode
18 |
19 | npm-debug.log*
20 | yarn-debug.log*
21 | yarn-error.log*
22 | _git*
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | # dependencies
2 | /node_modules
3 |
4 | # testing
5 | /tests
6 | /coverage
7 |
8 | # docs
9 | /docs
10 |
11 | # misc
12 | .DS_Store
13 | .env.local
14 | .env.development.local
15 | .env.test.local
16 | .env.production.local
17 | /.github
18 | /demo
19 | .esdoc.json
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
25 | # Development folders and files
26 | public
27 | src
28 | scripts
29 | config
30 | .travis.yml
31 | CHANGELOG.md
32 | README.md
33 | CODE_OF_CONDUCT.md
34 | CONTRIBUTING.md
35 | tsconfig.json
36 | .eslintignore
37 | .eslintrc.json
38 | webpack.config.*
39 | babel.config.js
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as
6 | contributors and maintainers pledge to making participation in our project and
7 | our community a harassment-free experience for everyone, regardless of age, body
8 | size, disability, ethnicity, sex characteristics, gender identity and expression,
9 | level of experience, education, socio-economic status, nationality, personal
10 | appearance, race, religion, or sexual identity and orientation.
11 |
12 | ## Our Standards
13 |
14 | Examples of behavior that contributes to creating a positive environment
15 | include:
16 |
17 | * Using welcoming and inclusive language
18 | * Being respectful of differing viewpoints and experiences
19 | * Gracefully accepting constructive criticism
20 | * Focusing on what is best for the community
21 | * Showing empathy towards other community members
22 |
23 | Examples of unacceptable behavior by participants include:
24 |
25 | * The use of sexualized language or imagery and unwelcome sexual attention or
26 | advances
27 | * Trolling, insulting/derogatory comments, and personal or political attacks
28 | * Public or private harassment
29 | * Publishing others' private information, such as a physical or electronic
30 | address, without explicit permission
31 | * Other conduct which could reasonably be considered inappropriate in a
32 | professional setting
33 |
34 | ## Our Responsibilities
35 |
36 | Project maintainers are responsible for clarifying the standards of acceptable
37 | behavior and are expected to take appropriate and fair corrective action in
38 | response to any instances of unacceptable behavior.
39 |
40 | Project maintainers have the right and responsibility to remove, edit, or
41 | reject comments, commits, code, wiki edits, issues, and other contributions
42 | that are not aligned to this Code of Conduct, or to ban temporarily or
43 | permanently any contributor for other behaviors that they deem inappropriate,
44 | threatening, offensive, or harmful.
45 |
46 | ## Scope
47 |
48 | This Code of Conduct applies both within project spaces and in public spaces
49 | when an individual is representing the project or its community. Examples of
50 | representing a project or community include using an official project e-mail
51 | address, posting via an official social media account, or acting as an appointed
52 | representative at an online or offline event. Representation of a project may be
53 | further defined and clarified by project maintainers.
54 |
55 | ## Enforcement
56 |
57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
58 | reported by contacting the project team at opensource@hodgef.com. All
59 | complaints will be reviewed and investigated and will result in a response that
60 | is deemed necessary and appropriate to the circumstances. The project team is
61 | obligated to maintain confidentiality with regard to the reporter of an incident.
62 | Further details of specific enforcement policies may be posted separately.
63 |
64 | Project maintainers who do not follow or enforce the Code of Conduct in good
65 | faith may face temporary or permanent repercussions as determined by other
66 | members of the project's leadership.
67 |
68 | ## Attribution
69 |
70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
72 |
73 | [homepage]: https://www.contributor-covenant.org
74 |
75 | For answers to common questions about this code of conduct, see
76 | https://www.contributor-covenant.org/faq
77 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | When contributing to this repository, please first discuss the change you wish to make via issue,
4 | email, or any other method with the owners of this repository before working on a change.
5 |
6 | Please note we have a code of conduct, please follow it in all your interactions with the project.
7 |
8 | ## Pull Request Guidelines
9 |
10 | 1. Use PRs to fix issues. New features are discouraged as they bring more maintenance burden over time.
11 | 2. Discuss the change you wish to make with the owners of this repository **before** raising a PR. If you do not discuss it beforehand, your PR might end up being rejected and your work will be lost.
12 | 3. Please ensure your proposal will not significantly change current functionality or bring along breaking changes.
13 | 4. PRs only consisting of typo fixes (or other automated contributions), will not be accepted.
14 | 5. Do not add any dependencies, libraries or external codes to the project. All the code submitted should be authored by you.
15 | 6. Avoid refactors. The changes should be succint and fix a specific issue with the least code possible.
16 | 7. Document your changes thoroughly.
17 | 8. Ensure that none of the tests fail.
18 | 9. Be reactive to any comments, reviews or change requests entered in your pull request.
19 | 10. It's up to the maintainers discretion whether the PR is accepted or rejected. Remember, the maintainer will have to maintain the code you've added (and fix any issues filed about it) for the years to come! In case of a rejection, the maintainer will explain the reasons guiding the decision.
20 |
21 | Thank you for your contributions!
22 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Francisco Hodge and project contributors.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
Virtual Keyboard for React. Customizable, responsive and lightweight.
6 |
7 |
8 |
9 |
10 |
11 | ## 🚀 Demo
12 |
13 | [https://simple-keyboard.com/demo](https://simple-keyboard.com/demo)
14 |
15 | ## 📦 Installation & Usage
16 |
17 | Check out the [Getting Started](https://simple-keyboard.com/react/getting-started) docs to begin.
18 |
19 | ## 📖 Documentation
20 |
21 | Check out the [simple-keyboard documentation](https://simple-keyboard.com/react/documentation) site.
22 |
23 | Feel free to browse the [Questions & Answers](https://simple-keyboard.com/qa-use-cases/) page for common use-cases.
24 |
25 | ### To run demo on your own computer
26 |
27 | - Clone this repository
28 | - `npm install`
29 | - `npm start`
30 | - Visit [http://localhost:3000/](http://localhost:3000/)
31 |
32 | ### Other versions
33 |
34 | - [Vanilla JS](https://github.com/hodgef/simple-keyboard)
35 | - [Angular](https://simple-keyboard.com/demo)
36 | - [Vue.js](https://simple-keyboard.com/demo)
37 |
38 | ### Questions? Join the chat
39 |
40 |
41 |
42 | ## 🎯 Compatibility
43 |
44 | - Internet Explorer 11
45 | - Edge (Spartan) 16+
46 | - Edge (Anaheim/Edge Chromium) 79+
47 | - Chrome 49+
48 | - Safari 9+
49 | - Firefox 57+
50 | - iOS 9+
51 |
52 | > Note: If you don't want to support old browsers, you can use the Modern Browsers bundle ([index.modern.js](https://github.com/hodgef/react-simple-keyboard/blob/master/build)).
53 |
54 | ## ✅ Contributing
55 |
56 | PRs and issues are welcome. Feel free to submit any issues you have at:
57 | [https://github.com/hodgef/react-simple-keyboard/issues](https://github.com/hodgef/react-simple-keyboard/issues)
58 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | "@babel/preset-react",
4 | [
5 | "@babel/env",
6 | {
7 | corejs: "3",
8 | useBuiltIns: "entry",
9 | targets: {
10 | browsers: [
11 | "edge >= 16",
12 | "safari >= 9",
13 | "firefox >= 57",
14 | "ie >= 11",
15 | "ios >= 9",
16 | "chrome >= 49",
17 | ],
18 | },
19 | },
20 | ],
21 | "@babel/preset-typescript",
22 | ],
23 | plugins: [
24 | ["@babel/plugin-proposal-class-properties"],
25 | ["@babel/plugin-transform-typescript"],
26 | ],
27 | };
28 |
--------------------------------------------------------------------------------
/build/components/Keyboard.d.ts:
--------------------------------------------------------------------------------
1 | import "simple-keyboard/build/css/index.css";
2 | import { KeyboardReactInterface } from "../interfaces.d";
3 | declare const KeyboardReact: (props: KeyboardReactInterface["options"]) => any;
4 | export default KeyboardReact;
5 |
--------------------------------------------------------------------------------
/build/components/KeyboardModern.d.ts:
--------------------------------------------------------------------------------
1 | import "simple-keyboard/build/css/index.css";
2 | import { KeyboardReactInterface } from "../interfaces";
3 | declare const KeyboardReact: (props: KeyboardReactInterface["options"]) => any;
4 | export default KeyboardReact;
5 |
--------------------------------------------------------------------------------
/build/css/index.css:
--------------------------------------------------------------------------------
1 | /*!
2 | *
3 | * react-simple-keyboard v3.8.80
4 | * https://github.com/hodgef/react-simple-keyboard
5 | *
6 | * Copyright (c) Francisco Hodge (https://github.com/hodgef) and project contributors.
7 | *
8 | * This source code is licensed under the MIT license found in the
9 | * LICENSE file in the root directory of this source tree.
10 | *
11 | */
12 | /*!
13 | *
14 | * simple-keyboard v3.8.59
15 | * https://github.com/hodgef/simple-keyboard
16 | *
17 | * Copyright (c) Francisco Hodge (https://github.com/hodgef) and project contributors.
18 | *
19 | * This source code is licensed under the MIT license found in the
20 | * LICENSE file in the root directory of this source tree.
21 | *
22 | */.hg-theme-default{background-color:#ececec;border-radius:5px;box-sizing:border-box;font-family:HelveticaNeue-Light,Helvetica Neue Light,Helvetica Neue,Helvetica,Arial,Lucida Grande,sans-serif;overflow:hidden;padding:5px;touch-action:manipulation;-webkit-user-select:none;-moz-user-select:none;user-select:none;width:100%}.hg-theme-default .hg-button span,.hg-theme-default .hg-button span svg{pointer-events:none}.hg-theme-default button.hg-button{border-width:0;font-size:inherit;outline:0}.hg-theme-default .hg-button{display:inline-block;flex-grow:1}.hg-theme-default .hg-row{display:flex}.hg-theme-default .hg-row:not(:last-child){margin-bottom:5px}.hg-theme-default .hg-row .hg-button-container,.hg-theme-default .hg-row .hg-button:not(:last-child){margin-right:5px}.hg-theme-default .hg-row>div:last-child{margin-right:0}.hg-theme-default .hg-row .hg-button-container{display:flex}.hg-theme-default .hg-button{align-items:center;background:#fff;border-bottom:1px solid #b5b5b5;border-radius:5px;box-shadow:0 0 3px -1px rgba(0,0,0,.3);box-sizing:border-box;cursor:pointer;display:flex;height:40px;justify-content:center;padding:5px;-webkit-tap-highlight-color:rgba(0,0,0,0)}.hg-theme-default .hg-button.hg-standardBtn{width:20px}.hg-theme-default .hg-button.hg-activeButton{background:#efefef}.hg-theme-default.hg-layout-numeric .hg-button{align-items:center;display:flex;height:60px;justify-content:center;width:33.3%}.hg-theme-default .hg-button.hg-button-numpadadd,.hg-theme-default .hg-button.hg-button-numpadenter{height:85px}.hg-theme-default .hg-button.hg-button-numpad0{width:105px}.hg-theme-default .hg-button.hg-button-com{max-width:85px}.hg-theme-default .hg-button.hg-standardBtn.hg-button-at{max-width:45px}.hg-theme-default .hg-button.hg-selectedButton{background:rgba(5,25,70,.53);color:#fff}.hg-theme-default .hg-button.hg-standardBtn[data-skbtn=".com"]{max-width:82px}.hg-theme-default .hg-button.hg-standardBtn[data-skbtn="@"]{max-width:60px}.hg-candidate-box{background:#ececec;border-bottom:2px solid #b5b5b5;border-radius:5px;display:inline-flex;margin-top:-10px;position:absolute;transform:translateY(-100%);-webkit-user-select:none;-moz-user-select:none;user-select:none}ul.hg-candidate-box-list{display:flex;flex:1;list-style:none;margin:0;padding:0}li.hg-candidate-box-list-item{align-items:center;display:flex;height:40px;justify-content:center;width:40px}li.hg-candidate-box-list-item:hover{background:rgba(0,0,0,.03);cursor:pointer}li.hg-candidate-box-list-item:active{background:rgba(0,0,0,.1)}.hg-candidate-box-prev:before{content:"◄"}.hg-candidate-box-next:before{content:"►"}.hg-candidate-box-next,.hg-candidate-box-prev{align-items:center;color:#969696;cursor:pointer;display:flex;padding:0 10px}.hg-candidate-box-next{border-bottom-right-radius:5px;border-top-right-radius:5px}.hg-candidate-box-prev{border-bottom-left-radius:5px;border-top-left-radius:5px}.hg-candidate-box-btn-active{color:#444}
--------------------------------------------------------------------------------
/build/index.d.ts:
--------------------------------------------------------------------------------
1 | import "./polyfills";
2 | import KeyboardReact from "./components/Keyboard";
3 | export * from "./interfaces.d";
4 | export { KeyboardReact };
5 | export default KeyboardReact;
6 |
--------------------------------------------------------------------------------
/build/index.modern.d.ts:
--------------------------------------------------------------------------------
1 | import KeyboardReact from "./components/KeyboardModern";
2 | export * from "./interfaces.d";
3 | export { KeyboardReact };
4 | export default KeyboardReact;
5 |
--------------------------------------------------------------------------------
/build/index.modern.esm.js:
--------------------------------------------------------------------------------
1 | /*!
2 | *
3 | * react-simple-keyboard v3.8.80 (index.modern.esm.js - Modern Browsers bundle, ESM output)
4 | * https://github.com/hodgef/react-simple-keyboard
5 | *
6 | * NOTE: This modern browsers bundle (index.modern.esm.js) removes all polyfills
7 | * included in the standard version. Use this if you are supporting
8 | * modern browsers only. Otherwise, use the standard version (index.js).
9 | *
10 | * Copyright (c) Francisco Hodge (https://github.com/hodgef) and project contributors.
11 | *
12 | * This source code is licensed under the MIT license found in the
13 | * LICENSE file in the root directory of this source tree.
14 | *
15 | */
16 | import*as t from"react";var e={651:function(t){t.exports=function(){var t={d:function(e,n){for(var o in n)t.o(n,o)&&!t.o(e,o)&&Object.defineProperty(e,o,{enumerable:!0,get:n[o]})},o:function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},r:function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})}},e={};function n(t){return function(t){if(Array.isArray(t))return i(t)}(t)||function(t){if("undefined"!=typeof Symbol&&null!=t[Symbol.iterator]||null!=t["@@iterator"])return Array.from(t)}(t)||o(t)||function(){throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function o(t,e){if(t){if("string"==typeof t)return i(t,e);var n={}.toString.call(t).slice(8,-1);return"Object"===n&&t.constructor&&(n=t.constructor.name),"Map"===n||"Set"===n?Array.from(t):"Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)?i(t,e):void 0}}function i(t,e){(null==e||e>t.length)&&(e=t.length);for(var n=0,o=Array(e);n2&&void 0!==arguments[2]&&arguments[2]?Object.assign({},this.getDefaultDiplay(),e):e||this.getDefaultDiplay())[t]||t}},{key:"getUpdatedInput",value:function(t,e,n){var o=arguments.length>3&&void 0!==arguments[3]?arguments[3]:n,i=arguments.length>4&&void 0!==arguments[4]&&arguments[4],s=this.getOptions(),a=[n,o,i],r=e;return("{bksp}"===t||"{backspace}"===t)&&r.length>0?r=this.removeAt.apply(this,[r].concat(a)):("{delete}"===t||"{forwarddelete}"===t)&&r.length>0?r=this.removeForwardsAt.apply(this,[r].concat(a)):"{space}"===t?r=this.addStringAt.apply(this,[r," "].concat(a)):"{tab}"!==t||"boolean"==typeof s.tabCharOnTab&&!1===s.tabCharOnTab?"{enter}"!==t&&"{numpadenter}"!==t||!s.newLineOnEnter?t.includes("numpad")&&Number.isInteger(Number(t[t.length-2]))?r=this.addStringAt.apply(this,[r,t[t.length-2]].concat(a)):"{numpaddivide}"===t?r=this.addStringAt.apply(this,[r,"/"].concat(a)):"{numpadmultiply}"===t?r=this.addStringAt.apply(this,[r,"*"].concat(a)):"{numpadsubtract}"===t?r=this.addStringAt.apply(this,[r,"-"].concat(a)):"{numpadadd}"===t?r=this.addStringAt.apply(this,[r,"+"].concat(a)):"{numpaddecimal}"===t?r=this.addStringAt.apply(this,[r,"."].concat(a)):"{"===t||"}"===t?r=this.addStringAt.apply(this,[r,t].concat(a)):t.includes("{")||t.includes("}")||(r=this.addStringAt.apply(this,[r,t].concat(a))):r=this.addStringAt.apply(this,[r,"\n"].concat(a)):r=this.addStringAt.apply(this,[r,"\t"].concat(a)),s.debug&&console.log("Input will be: "+r),r}},{key:"updateCaretPos",value:function(t){var e=arguments.length>1&&void 0!==arguments[1]&&arguments[1],n=this.updateCaretPosAction(t,e);this.dispatch((function(t){t.setCaretPosition(n)}))}},{key:"updateCaretPosAction",value:function(t){var e=arguments.length>1&&void 0!==arguments[1]&&arguments[1],n=this.getOptions(),o=this.getCaretPosition();return null!=o&&(e?o>0&&(o-=t):o+=t),n.debug&&console.log("Caret at:",o),o}},{key:"addStringAt",value:function(t,e){var n,o=arguments.length>2&&void 0!==arguments[2]?arguments[2]:t.length,i=arguments.length>3&&void 0!==arguments[3]?arguments[3]:t.length,s=arguments.length>4&&void 0!==arguments[4]&&arguments[4];return o||0===o?(n=[t.slice(0,o),e,t.slice(i)].join(""),this.isMaxLengthReached()||s&&this.updateCaretPos(e.length)):n=t+e,n}},{key:"removeAt",value:function(t){var e,n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:t.length,o=arguments.length>2&&void 0!==arguments[2]?arguments[2]:t.length,i=arguments.length>3&&void 0!==arguments[3]&&arguments[3];if(0===n&&0===o)return t;if(n===o){var s=/([\uD800-\uDBFF][\uDC00-\uDFFF])/g;n&&n>=0?t.substring(n-2,n).match(s)?(e=t.substr(0,n-2)+t.substr(n),i&&this.updateCaretPos(2,!0)):(e=t.substr(0,n-1)+t.substr(n),i&&this.updateCaretPos(1,!0)):t.slice(-2).match(s)?(e=t.slice(0,-2),i&&this.updateCaretPos(2,!0)):(e=t.slice(0,-1),i&&this.updateCaretPos(1,!0))}else e=t.slice(0,n)+t.slice(o),i&&this.dispatch((function(t){t.setCaretPosition(n)}));return e}},{key:"removeForwardsAt",value:function(t){var e,n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:t.length,o=arguments.length>2&&void 0!==arguments[2]?arguments[2]:t.length,i=arguments.length>3&&void 0!==arguments[3]&&arguments[3];return null!=t&&t.length&&null!==n?(n===o?e=t.substring(n,n+2).match(/([\uD800-\uDBFF][\uDC00-\uDFFF])/g)?t.substr(0,n)+t.substr(n+2):t.substr(0,n)+t.substr(n+1):(e=t.slice(0,n)+t.slice(o),i&&this.dispatch((function(t){t.setCaretPosition(n)}))),e):t}},{key:"handleMaxLength",value:function(t,e){var n=this.getOptions(),o=n.maxLength,i=t[n.inputName||"default"],a=e.length-1>=o;if(e.length<=i.length)return!1;if(Number.isInteger(o))return n.debug&&console.log("maxLength (num) reached:",a),a?(this.maxLengthReached=!0,!0):(this.maxLengthReached=!1,!1);if("object"===s(o)){var r=e.length-1>=o[n.inputName||"default"];return n.debug&&console.log("maxLength (obj) reached:",r),r?(this.maxLengthReached=!0,!0):(this.maxLengthReached=!1,!1)}}},{key:"isMaxLengthReached",value:function(){return Boolean(this.maxLengthReached)}},{key:"isTouchDevice",value:function(){return"ontouchstart"in window||navigator.maxTouchPoints}},{key:"pointerEventsSupported",value:function(){return!!window.PointerEvent}},{key:"camelCase",value:function(t){return t?t.toLowerCase().trim().split(/[.\-_\s]/g).reduce((function(t,e){return e.length?t+e[0].toUpperCase()+e.slice(1):t})):""}},{key:"chunkArray",value:function(t,e){return n(Array(Math.ceil(t.length/e))).map((function(n,o){return t.slice(e*o,e+e*o)}))}},{key:"escapeRegex",value:function(t){return t.replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&")}},{key:"getRtlOffset",value:function(t,e){var n=t,o=e.indexOf("");return o=t.length?{done:!0}:{done:!1,value:t[i++]}},e:function(t){throw t},f:s}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}var a,r=!0,u=!1;return{s:function(){n=n.call(t)},n:function(){var t=n.next();return r=t.done,t},e:function(t){u=!0,a=t},f:function(){try{r||null==n.return||n.return()}finally{if(u)throw a}}}}(Object.getOwnPropertyNames(t.prototype));try{for(i.s();!(n=i.n()).done;){var s=n.value;"constructor"===s||"bindMethods"===s||(e[s]=e[s].bind(e))}}catch(t){i.e(t)}finally{i.f()}}}],e&&a(t.prototype,e),i&&a(t,i),Object.defineProperty(t,"prototype",{writable:!1}),t;var t,e,i}();r(l,"noop",(function(){}));var c=l;function d(t){return d="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},d(t)}function h(t,e){for(var n=0;n1?null===(e=n)||void 0===e?void 0:e.toLowerCase():n}},{key:"keyCodeToKey",value:function(t){return{8:"Backspace",9:"Tab",13:"Enter",16:"Shift",17:"Ctrl",18:"Alt",19:"Pause",20:"CapsLock",27:"Esc",32:"Space",33:"PageUp",34:"PageDown",35:"End",36:"Home",37:"ArrowLeft",38:"ArrowUp",39:"ArrowRight",40:"ArrowDown",45:"Insert",46:"Delete",48:"0",49:"1",50:"2",51:"3",52:"4",53:"5",54:"6",55:"7",56:"8",57:"9",65:"A",66:"B",67:"C",68:"D",69:"E",70:"F",71:"G",72:"H",73:"I",74:"J",75:"K",76:"L",77:"M",78:"N",79:"O",80:"P",81:"Q",82:"R",83:"S",84:"T",85:"U",86:"V",87:"W",88:"X",89:"Y",90:"Z",91:"Meta",96:"Numpad0",97:"Numpad1",98:"Numpad2",99:"Numpad3",100:"Numpad4",101:"Numpad5",102:"Numpad6",103:"Numpad7",104:"Numpad8",105:"Numpad9",106:"NumpadMultiply",107:"NumpadAdd",109:"NumpadSubtract",110:"NumpadDecimal",111:"NumpadDivide",112:"F1",113:"F2",114:"F3",115:"F4",116:"F5",117:"F6",118:"F7",119:"F8",120:"F9",121:"F10",122:"F11",123:"F12",144:"NumLock",145:"ScrollLock",186:";",187:"=",188:",",189:"-",190:".",191:"/",192:"`",219:"[",220:"\\",221:"]",222:"'"}[t]||""}}],e&&h(t.prototype,e),n&&h(t,n),Object.defineProperty(t,"prototype",{writable:!1}),t;var t,e,n}();function v(t){return v="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},v(t)}function m(t,e){for(var n=0;n0&&void 0!==arguments[0]?arguments[0]:i();return r(t,e)},u.appendChild(o)}));var l=s>0,c=document.createElement("div");c.classList.add("hg-candidate-box-prev"),l&&c.classList.add("hg-candidate-box-btn-active");var d=function(){l&&n.renderPage({candidateListPages:o,targetElement:i,pageIndex:s-1,nbPages:a,onItemSelected:r})};this.options.useTouchEvents?c.ontouchstart=d:c.onclick=d,this.candidateBoxElement.appendChild(c),this.candidateBoxElement.appendChild(u);var h=st.length)&&(e=t.length);for(var n=0,o=Array(e);n1&&void 0!==arguments[1]?arguments[1]:t;this.caretPosition=t,this.caretPositionEnd=e}},{key:"getInputCandidates",value:function(t){var e=this,n=this.options,o=n.layoutCandidates,i=n.layoutCandidatesCaseSensitiveMatch;if(!o||"object"!==C(o))return{};var s=Object.keys(o).filter((function(n){var o=t.substring(0,e.getCaretPositionEnd()||0)||t,s=new RegExp("".concat(e.utilities.escapeRegex(n),"$"),i?"g":"gi");return!!E(o.matchAll(s)).length}));if(s.length>1){var a=s.sort((function(t,e){return e.length-t.length}))[0];return{candidateKey:a,candidateValue:o[a]}}if(s.length){var r=s[0];return{candidateKey:r,candidateValue:o[r]}}return{}}},{key:"showCandidatesBox",value:function(t,e,n){var o=this;this.candidateBox&&this.candidateBox.show({candidateValue:e,targetElement:n,onSelect:function(e,n){var i=o.options,s=i.layoutCandidatesCaseSensitiveMatch,a=i.disableCandidateNormalization,r=i.enableLayoutCandidatesKeyPress,u=e;a||(u=e.normalize("NFD")),"function"==typeof o.options.beforeInputUpdate&&o.options.beforeInputUpdate(o);var l=o.getInput(o.options.inputName,!0),c=o.getCaretPositionEnd()||0,d=l.substring(0,c||0)||l,h=new RegExp("".concat(o.utilities.escapeRegex(t),"$"),s?"g":"gi"),p=d.replace(h,u),f=l.replace(d,p),y=p.length-d.length,v=(c||l.length)+y;v<0&&(v=0),o.setInput(f,o.options.inputName,!0),o.setCaretPosition(v),r&&"function"==typeof o.options.onKeyPress&&o.options.onKeyPress(e,n),"function"==typeof o.options.onChange&&o.options.onChange(o.getInput(o.options.inputName,!0),n),"function"==typeof o.options.onChangeAll&&o.options.onChangeAll(o.getAllInputs(),n)}})}},{key:"handleButtonClicked",value:function(t,e){var n=this.options,o=n.inputName,i=void 0===o?this.defaultName:o,s=n.debug;if("{//}"!==t){this.input[i]||(this.input[i]=""),"function"==typeof this.options.beforeInputUpdate&&this.options.beforeInputUpdate(this);var a=this.utilities.getUpdatedInput(t,this.input[i],this.caretPosition,this.caretPositionEnd);if(this.utilities.isStandardButton(t)&&this.activeInputElement&&this.input[i]&&this.input[i]===a&&0===this.caretPosition&&this.caretPositionEnd===a.length)return this.setInput("",this.options.inputName,!0),this.setCaretPosition(0),this.activeInputElement.value="",this.activeInputElement.setSelectionRange(0,0),void this.handleButtonClicked(t,e);if("function"==typeof this.options.onKeyPress&&this.options.onKeyPress(t,e),this.input[i]!==a&&(!this.options.inputPattern||this.options.inputPattern&&this.inputPatternIsValid(a))){if(this.options.maxLength&&this.utilities.handleMaxLength(this.input,a))return;var r=this.utilities.getUpdatedInput(t,this.input[i],this.caretPosition,this.caretPositionEnd,!0);if(this.setInput(r,this.options.inputName,!0),s&&console.log("Input changed:",this.getAllInputs()),this.options.debug&&console.log("Caret at: ",this.getCaretPosition(),this.getCaretPositionEnd(),"(".concat(this.keyboardDOMClass,")"),null==e?void 0:e.type),this.options.syncInstanceInputs&&this.syncInstanceInputs(),"function"==typeof this.options.onChange&&this.options.onChange(this.getInput(this.options.inputName,!0),e),"function"==typeof this.options.onChangeAll&&this.options.onChangeAll(this.getAllInputs(),e),null!=e&&e.target&&this.options.enableLayoutCandidates){var u,l=this.getInputCandidates(a),c=l.candidateKey,d=l.candidateValue;c&&d?this.showCandidatesBox(c,d,this.keyboardDOM):null===(u=this.candidateBox)||void 0===u||u.destroy()}}this.caretPositionEnd&&this.caretPosition!==this.caretPositionEnd&&(this.setCaretPosition(this.caretPositionEnd,this.caretPositionEnd),this.activeInputElement&&this.activeInputElement.setSelectionRange(this.caretPositionEnd,this.caretPositionEnd),this.options.debug&&console.log("Caret position aligned",this.caretPosition)),s&&console.log("Key pressed:",t)}}},{key:"getMouseHold",value:function(){return this.isMouseHold}},{key:"setMouseHold",value:function(t){this.options.syncInstanceInputs?this.dispatch((function(e){e.isMouseHold=t})):this.isMouseHold=t}},{key:"handleButtonMouseDown",value:function(t,e){var n=this;e&&(this.options.preventMouseDownDefault&&e.preventDefault(),this.options.stopMouseDownPropagation&&e.stopPropagation(),e.target.classList.add(this.activeButtonClass)),this.holdInteractionTimeout&&clearTimeout(this.holdInteractionTimeout),this.holdTimeout&&clearTimeout(this.holdTimeout),this.setMouseHold(!0),this.options.disableButtonHold||(this.holdTimeout=window.setTimeout((function(){(n.getMouseHold()&&(!t.includes("{")&&!t.includes("}")||"{delete}"===t||"{backspace}"===t||"{bksp}"===t||"{space}"===t||"{tab}"===t)||"{arrowright}"===t||"{arrowleft}"===t||"{arrowup}"===t||"{arrowdown}"===t)&&(n.options.debug&&console.log("Button held:",t),n.handleButtonHold(t)),clearTimeout(n.holdTimeout)}),500))}},{key:"handleButtonMouseUp",value:function(t,e){var n=this;e&&(this.options.preventMouseUpDefault&&e.preventDefault&&e.preventDefault(),this.options.stopMouseUpPropagation&&e.stopPropagation&&e.stopPropagation(),!(e.target===this.keyboardDOM||e.target&&this.keyboardDOM.contains(e.target)||this.candidateBox&&this.candidateBox.candidateBoxElement&&(e.target===this.candidateBox.candidateBoxElement||e.target&&this.candidateBox.candidateBoxElement.contains(e.target)))&&this.candidateBox&&this.candidateBox.destroy()),this.recurseButtons((function(t){t.classList.remove(n.activeButtonClass)})),this.setMouseHold(!1),this.holdInteractionTimeout&&clearTimeout(this.holdInteractionTimeout),t&&"function"==typeof this.options.onKeyReleased&&this.options.onKeyReleased(t,e)}},{key:"handleKeyboardContainerMouseDown",value:function(t){this.options.preventMouseDownDefault&&t.preventDefault()}},{key:"handleButtonHold",value:function(t){var e=this;this.holdInteractionTimeout&&clearTimeout(this.holdInteractionTimeout),this.holdInteractionTimeout=window.setTimeout((function(){e.getMouseHold()?(e.handleButtonClicked(t),e.handleButtonHold(t)):clearTimeout(e.holdInteractionTimeout)}),100)}},{key:"syncInstanceInputs",value:function(){var t=this;this.dispatch((function(e){e.replaceInput(t.input),e.setCaretPosition(t.caretPosition,t.caretPositionEnd)}))}},{key:"clearInput",value:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:this.options.inputName||this.defaultName;this.input[t]="",this.setCaretPosition(0),this.options.syncInstanceInputs&&this.syncInstanceInputs()}},{key:"getInput",value:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:this.options.inputName||this.defaultName,e=arguments.length>1&&void 0!==arguments[1]&&arguments[1];return this.options.syncInstanceInputs&&!e&&this.syncInstanceInputs(),this.options.rtl?""+this.input[t].replace("","").replace("","")+"":this.input[t]}},{key:"getAllInputs",value:function(){var t=this,e={};return Object.keys(this.input).forEach((function(n){e[n]=t.getInput(n,!0)})),e}},{key:"setInput",value:function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:this.options.inputName||this.defaultName,n=arguments.length>2?arguments[2]:void 0;this.input[e]=t,!n&&this.options.syncInstanceInputs&&this.syncInstanceInputs()}},{key:"replaceInput",value:function(t){this.input=t}},{key:"setOptions",value:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},e=this.changedOptions(t);this.options=Object.assign(this.options,t),e.length&&(this.options.debug&&console.log("changedOptions",e),this.onSetOptions(e),this.render())}},{key:"changedOptions",value:function(t){var e=this;return Object.keys(t).filter((function(n){return JSON.stringify(t[n])!==JSON.stringify(e.options[n])}))}},{key:"onSetOptions",value:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[];t.includes("layoutName")&&this.candidateBox&&this.candidateBox.destroy(),(t.includes("layoutCandidatesPageSize")||t.includes("layoutCandidates"))&&this.candidateBox&&(this.candidateBox.destroy(),this.candidateBox=new w({utilities:this.utilities,options:this.options}))}},{key:"resetRows",value:function(){this.keyboardRowsDOM&&this.keyboardRowsDOM.remove(),this.keyboardDOM.className=this.keyboardDOMClass,this.keyboardDOM.setAttribute("data-skInstance",this.currentInstanceName),this.buttonElements={}}},{key:"dispatch",value:function(t){if(!window.SimpleKeyboardInstances)throw console.warn("SimpleKeyboardInstances is not defined. Dispatch cannot be called."),new Error("INSTANCES_VAR_ERROR");return Object.keys(window.SimpleKeyboardInstances).forEach((function(e){t(window.SimpleKeyboardInstances[e],e)}))}},{key:"addButtonTheme",value:function(t,e){var n=this;e&&t&&(t.split(" ").forEach((function(o){e.split(" ").forEach((function(e){n.options.buttonTheme||(n.options.buttonTheme=[]);var i=!1;n.options.buttonTheme.map((function(t){if(null!=t&&t.class.split(" ").includes(e)){i=!0;var n=t.buttons.split(" ");n.includes(o)||(i=!0,n.push(o),t.buttons=n.join(" "))}return t})),i||n.options.buttonTheme.push({class:e,buttons:t})}))})),this.render())}},{key:"removeButtonTheme",value:function(t,e){var n=this;if(!t&&!e)return this.options.buttonTheme=[],void this.render();t&&Array.isArray(this.options.buttonTheme)&&this.options.buttonTheme.length&&(t.split(" ").forEach((function(t){var o;null===(o=n.options)||void 0===o||null===(o=o.buttonTheme)||void 0===o||o.map((function(o,i){if(o&&e&&e.includes(o.class)||!e){var s,a,r=null===(s=o)||void 0===s?void 0:s.buttons.split(" ").filter((function(e){return e!==t}));o&&null!=r&&r.length?o.buttons=r.join(" "):(null===(a=n.options.buttonTheme)||void 0===a||a.splice(i,1),o=null)}return o}))})),this.render())}},{key:"getButtonElement",value:function(t){var e,n=this.buttonElements[t];return n&&(e=n.length>1?n:n[0]),e}},{key:"inputPatternIsValid",value:function(t){var e,n=this.options.inputPattern;if((e=n instanceof RegExp?n:n[this.options.inputName||this.defaultName])&&t){var o=e.test(t);return this.options.debug&&console.log('inputPattern ("'.concat(e,'"): ').concat(o?"passed":"did not pass!")),o}return!0}},{key:"setEventListeners",value:function(){if(this.isFirstKeyboardInstance||!this.allKeyboardInstances){this.options.debug&&console.log("Caret handling started (".concat(this.keyboardDOMClass,")"));var t=this.options.physicalKeyboardHighlightPreventDefault,e=void 0!==t&&t;document.addEventListener("keyup",this.handleKeyUp,e),document.addEventListener("keydown",this.handleKeyDown,e),document.addEventListener("mouseup",this.handleMouseUp),document.addEventListener("touchend",this.handleTouchEnd),this.options.updateCaretOnSelectionChange&&document.addEventListener("selectionchange",this.handleSelectionChange),document.addEventListener("select",this.handleSelect)}}},{key:"handleKeyUp",value:function(t){this.caretEventHandler(t),this.options.physicalKeyboardHighlight&&this.physicalKeyboard.handleHighlightKeyUp(t)}},{key:"handleKeyDown",value:function(t){this.options.physicalKeyboardHighlight&&this.physicalKeyboard.handleHighlightKeyDown(t)}},{key:"handleMouseUp",value:function(t){this.caretEventHandler(t)}},{key:"handleTouchEnd",value:function(t){this.caretEventHandler(t)}},{key:"handleSelect",value:function(t){this.caretEventHandler(t)}},{key:"handleSelectionChange",value:function(t){navigator.userAgent.includes("Firefox")||this.caretEventHandler(t)}},{key:"caretEventHandler",value:function(t){var e,n=this;t.target.tagName&&(e=t.target.tagName.toLowerCase()),this.dispatch((function(o){var i=t.target===o.keyboardDOM||t.target&&o.keyboardDOM.contains(t.target);if(n.options.syncInstanceInputs&&Array.isArray(t.path)&&(i=t.path.some((function(t){var e;return null==t||null===(e=t.hasAttribute)||void 0===e?void 0:e.call(t,"data-skInstance")}))),("textarea"===e||"input"===e&&["text","search","url","tel","password"].includes(t.target.type))&&!o.options.disableCaretPositioning){var s=t.target.selectionStart,a=t.target.selectionEnd;o.options.rtl&&(s=o.utilities.getRtlOffset(s,o.getInput()),a=o.utilities.getRtlOffset(a,o.getInput())),o.setCaretPosition(s,a),o.activeInputElement=t.target,o.options.debug&&console.log("Caret at: ",o.getCaretPosition(),o.getCaretPositionEnd(),t&&t.target.tagName.toLowerCase(),"(".concat(o.keyboardDOMClass,")"),null==t?void 0:t.type)}else!o.options.disableCaretPositioning&&i||"selectionchange"===(null==t?void 0:t.type)||(o.setCaretPosition(null),o.activeInputElement=null,o.options.debug&&console.log('Caret position reset due to "'.concat(null==t?void 0:t.type,'" event'),t))}))}},{key:"recurseButtons",value:function(t){var e=this;t&&Object.keys(this.buttonElements).forEach((function(n){return e.buttonElements[n].forEach(t)}))}},{key:"destroy",value:function(){this.options.debug&&console.log("Destroying simple-keyboard instance: ".concat(this.currentInstanceName));var t=this.options.physicalKeyboardHighlightPreventDefault,e=void 0!==t&&t;document.removeEventListener("keyup",this.handleKeyUp,e),document.removeEventListener("keydown",this.handleKeyDown,e),document.removeEventListener("mouseup",this.handleMouseUp),document.removeEventListener("touchend",this.handleTouchEnd),document.removeEventListener("select",this.handleSelect),this.options.updateCaretOnSelectionChange&&document.removeEventListener("selectionchange",this.handleSelectionChange),document.onpointerup=null,document.ontouchend=null,document.ontouchcancel=null,document.onmouseup=null,this.recurseButtons((function(t){t&&(t.onpointerdown=null,t.onpointerup=null,t.onpointercancel=null,t.ontouchstart=null,t.ontouchend=null,t.ontouchcancel=null,t.onclick=null,t.onmousedown=null,t.onmouseup=null,t.remove(),t=null)})),this.keyboardDOM.onpointerdown=null,this.keyboardDOM.ontouchstart=null,this.keyboardDOM.onmousedown=null,this.resetRows(),this.candidateBox&&(this.candidateBox.destroy(),this.candidateBox=null),this.activeInputElement=null,this.keyboardDOM.removeAttribute("data-skInstance"),this.keyboardDOM.innerHTML="",window.SimpleKeyboardInstances[this.currentInstanceName]=null,delete window.SimpleKeyboardInstances[this.currentInstanceName],this.initialized=!1}},{key:"getButtonThemeClasses",value:function(t){var e=this.options.buttonTheme,n=[];return Array.isArray(e)&&e.forEach((function(e){if(e&&e.class&&"string"==typeof e.class&&e.buttons&&"string"==typeof e.buttons){var o=e.class.split(" ");e.buttons.split(" ").includes(t)&&(n=[].concat(E(n),E(o)))}else console.warn('Incorrect "buttonTheme". Please check the documentation.',e)})),n}},{key:"setDOMButtonAttributes",value:function(t,e){var n=this.options.buttonAttributes;Array.isArray(n)&&n.forEach((function(n){n.attribute&&"string"==typeof n.attribute&&n.value&&"string"==typeof n.value&&n.buttons&&"string"==typeof n.buttons?n.buttons.split(" ").includes(t)&&e(n.attribute,n.value):console.warn('Incorrect "buttonAttributes". Please check the documentation.',n)}))}},{key:"onTouchDeviceDetected",value:function(){this.processAutoTouchEvents(),this.disableContextualWindow()}},{key:"disableContextualWindow",value:function(){window.oncontextmenu=function(t){var e;if(null!==(e=t.target.classList)&&void 0!==e&&e.contains("hg-button"))return t.preventDefault(),t.stopPropagation(),!1}}},{key:"processAutoTouchEvents",value:function(){this.options.autoUseTouchEvents&&(this.options.useTouchEvents=!0,this.options.debug&&console.log("autoUseTouchEvents: Touch device detected, useTouchEvents enabled."))}},{key:"onInit",value:function(){this.options.debug&&console.log("".concat(this.keyboardDOMClass," Initialized")),this.setEventListeners(),"function"==typeof this.options.onInit&&this.options.onInit(this)}},{key:"beforeFirstRender",value:function(){this.utilities.isTouchDevice()&&this.onTouchDeviceDetected(),"function"==typeof this.options.beforeFirstRender&&this.options.beforeFirstRender(this),this.isFirstKeyboardInstance&&this.utilities.pointerEventsSupported()&&!this.options.useTouchEvents&&!this.options.useMouseEvents&&this.options.debug&&console.log("Using PointerEvents as it is supported by this browser"),this.options.useTouchEvents&&this.options.debug&&console.log("useTouchEvents has been enabled. Only touch events will be used.")}},{key:"beforeRender",value:function(){"function"==typeof this.options.beforeRender&&this.options.beforeRender(this)}},{key:"onRender",value:function(){"function"==typeof this.options.onRender&&this.options.onRender(this)}},{key:"onModulesLoaded",value:function(){"function"==typeof this.options.onModulesLoaded&&this.options.onModulesLoaded(this)}},{key:"loadModules",value:function(){var t=this;Array.isArray(this.options.modules)&&(this.options.modules.forEach((function(e){var n=t.utilities.isConstructor(e)?new e(t):e(t);n.init&&n.init(t)})),this.keyboardPluginClasses="modules-loaded",this.render(),this.onModulesLoaded())}},{key:"getModuleProp",value:function(t,e){return!!this.modules[t]&&this.modules[t][e]}},{key:"getModulesList",value:function(){return Object.keys(this.modules)}},{key:"parseRowDOMContainers",value:function(t,e,n,o){var i=this,s=Array.from(t.children),a=0;return s.length&&n.forEach((function(n,r){var u=o[r];if(!(u&&u>n))return!1;var l=n-a,c=u-a,d=document.createElement("div");d.className+="hg-button-container";var h="".concat(i.options.layoutName,"-r").concat(e,"c").concat(r);d.setAttribute("data-skUID",h);var p=s.splice(l,c-l+1);a+=c-l,p.forEach((function(t){return d.appendChild(t)})),s.splice(l,0,d),t.innerHTML="",s.forEach((function(e){return t.appendChild(e)})),i.options.debug&&console.log("rowDOMContainer",p,l,c,a+1)})),t}},{key:"render",value:function(){var t=this;this.resetRows(),this.initialized||this.beforeFirstRender(),this.beforeRender();var e="hg-layout-".concat(this.options.layoutName),n=this.options.layout||{default:["` 1 2 3 4 5 6 7 8 9 0 - = {bksp}","{tab} q w e r t y u i o p [ ] \\","{lock} a s d f g h j k l ; ' {enter}","{shift} z x c v b n m , . / {shift}",".com @ {space}"],shift:["~ ! @ # $ % ^ & * ( ) _ + {bksp}","{tab} Q W E R T Y U I O P { } |",'{lock} A S D F G H J K L : " {enter}',"{shift} Z X C V B N M < > ? {shift}",".com @ {space}"]},o=this.options.useTouchEvents||!1,i=o?"hg-touch-events":"",s=this.options.useMouseEvents||!1,a=this.options.disableRowButtonContainers;this.keyboardDOM.className=this.getKeyboardClassString(this.options.theme,e,this.keyboardPluginClasses,i),this.keyboardDOM.setAttribute("data-skInstance",this.currentInstanceName),this.keyboardRowsDOM=document.createElement("div"),this.keyboardRowsDOM.className="hg-rows",n[this.options.layoutName||this.defaultName].forEach((function(e,n){var i=e.split(" ");t.options.excludeFromLayout&&t.options.excludeFromLayout[t.options.layoutName||t.defaultName]&&(i=i.filter((function(e){return t.options.excludeFromLayout&&!t.options.excludeFromLayout[t.options.layoutName||t.defaultName].includes(e)})));var r=document.createElement("div");r.className+="hg-row";var u=[],l=[];i.forEach((function(e,i){var c,d=!a&&"string"==typeof e&&e.length>1&&0===e.indexOf("["),h=!a&&"string"==typeof e&&e.length>1&&e.indexOf("]")===e.length-1;d&&(u.push(i),e=e.replace(/\[/g,"")),h&&(l.push(i),e=e.replace(/\]/g,""));var p=t.utilities.getButtonClass(e),f=t.utilities.getButtonDisplayName(e,t.options.display,t.options.mergeDisplay),y=t.options.useButtonTag?"button":"div",v=document.createElement(y);v.className+="hg-button ".concat(p),(c=v.classList).add.apply(c,E(t.getButtonThemeClasses(e))),t.setDOMButtonAttributes(e,(function(t,e){v.setAttribute(t,e)})),t.activeButtonClass="hg-activeButton",!t.utilities.pointerEventsSupported()||o||s?o?(v.ontouchstart=function(n){t.handleButtonClicked(e,n),t.handleButtonMouseDown(e,n)},v.ontouchend=function(n){t.handleButtonMouseUp(e,n)},v.ontouchcancel=function(n){t.handleButtonMouseUp(e,n)}):(v.onclick=function(n){t.setMouseHold(!1),"function"==typeof t.options.onKeyReleased||t.options.useMouseEvents&&t.options.clickOnMouseDown||t.handleButtonClicked(e,n)},v.onmousedown=function(n){("function"==typeof t.options.onKeyReleased||t.options.useMouseEvents&&t.options.clickOnMouseDown)&&!t.isMouseHold&&t.handleButtonClicked(e,n),t.handleButtonMouseDown(e,n)},v.onmouseup=function(n){t.handleButtonMouseUp(e,n)}):(v.onpointerdown=function(n){t.handleButtonClicked(e,n),t.handleButtonMouseDown(e,n)},v.onpointerup=function(n){t.handleButtonMouseUp(e,n)},v.onpointercancel=function(n){t.handleButtonMouseUp(e,n)}),v.setAttribute("data-skBtn",e);var m="".concat(t.options.layoutName,"-r").concat(n,"b").concat(i);v.setAttribute("data-skBtnUID",m);var g=document.createElement("span");g.innerHTML=f,v.appendChild(g),t.buttonElements[e]||(t.buttonElements[e]=[]),t.buttonElements[e].push(v),r.appendChild(v)})),r=t.parseRowDOMContainers(r,n,u,l),t.keyboardRowsDOM.appendChild(r)})),this.keyboardDOM.appendChild(this.keyboardRowsDOM),this.onRender(),this.initialized||(this.initialized=!0,!this.utilities.pointerEventsSupported()||o||s?o?(document.ontouchend=function(e){return t.handleButtonMouseUp(void 0,e)},document.ontouchcancel=function(e){return t.handleButtonMouseUp(void 0,e)},this.keyboardDOM.ontouchstart=function(e){return t.handleKeyboardContainerMouseDown(e)}):o||(document.onmouseup=function(e){return t.handleButtonMouseUp(void 0,e)},this.keyboardDOM.onmousedown=function(e){return t.handleKeyboardContainerMouseDown(e)}):(document.onpointerup=function(e){return t.handleButtonMouseUp(void 0,e)},this.keyboardDOM.onpointerdown=function(e){return t.handleKeyboardContainerMouseDown(e)}),this.onInit())}}],e&&I(t.prototype,e),n&&I(t,n),Object.defineProperty(t,"prototype",{writable:!1}),t;var t,e,n}(),B=D,A=B;return e}()}},n={};function o(t){var i=n[t];if(void 0!==i)return i.exports;var s=n[t]={exports:{}};return e[t].call(s.exports,s,s.exports,o),s.exports}o.n=t=>{var e=t&&t.__esModule?()=>t.default:()=>t;return o.d(e,{a:e}),e},o.d=(t,e)=>{for(var n in e)o.o(e,n)&&!o.o(t,n)&&Object.defineProperty(t,n,{enumerable:!0,get:e[n]})},o.o=(t,e)=>Object.prototype.hasOwnProperty.call(t,e);const i=(t=>{var e={};return o.d(e,t),e})({createElement:()=>t.createElement,useEffect:()=>t.useEffect,useRef:()=>t.useRef});function s(t){return s="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},s(t)}function a(t,e){var n=Object.keys(t);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(t);e&&(o=o.filter((function(e){return Object.getOwnPropertyDescriptor(t,e).enumerable}))),n.push.apply(n,o)}return n}function r(t){for(var e=1;e void;
6 | };
7 | }
8 | // Generated by dts-bundle v0.7.3
9 | /**
10 | * Root class for simple-keyboard.
11 | * This class:
12 | * - Parses the options
13 | * - Renders the rows and buttons
14 | * - Handles button functionality
15 | */
16 | export interface SimpleKeyboard {
17 | input: KeyboardInput;
18 | options: KeyboardOptions;
19 | utilities: Utilities;
20 | caretPosition: number | null;
21 | caretPositionEnd: number | null;
22 | keyboardDOM: KeyboardElement;
23 | keyboardPluginClasses: string;
24 | keyboardDOMClass: string;
25 | buttonElements: KeyboardButtonElements;
26 | currentInstanceName: string;
27 | allKeyboardInstances: {
28 | [key: string]: SimpleKeyboard;
29 | };
30 | keyboardInstanceNames: string[];
31 | isFirstKeyboardInstance: boolean;
32 | physicalKeyboard: PhysicalKeyboard;
33 | modules: {
34 | [key: string]: any;
35 | };
36 | activeButtonClass: string;
37 | holdInteractionTimeout: number;
38 | holdTimeout: number;
39 | isMouseHold: boolean;
40 | initialized: boolean;
41 | candidateBox: CandidateBox | null;
42 | keyboardRowsDOM: KeyboardElement;
43 | defaultName: string;
44 | activeInputElement: HTMLInputElement | HTMLTextAreaElement | null;
45 | /**
46 | * Creates an instance of SimpleKeyboard
47 | * @param {Array} selectorOrOptions If first parameter is a string, it is considered the container class. The second parameter is then considered the options object. If first parameter is an object, it is considered the options object.
48 | */
49 | constructor: (selectorOrOptions?: string | HTMLDivElement | KeyboardOptions, keyboardOptions?: KeyboardOptions) => any
50 | /**
51 | * parseParams
52 | */
53 | handleParams: (selectorOrOptions?: string | HTMLDivElement | KeyboardOptions, keyboardOptions?: KeyboardOptions) => {
54 | keyboardDOMClass: string;
55 | keyboardDOM: KeyboardElement;
56 | options: Partial;
57 | };
58 | /**
59 | * Getters
60 | */
61 | getOptions: () => KeyboardOptions;
62 | getCaretPosition: () => number | null;
63 | getCaretPositionEnd: () => number | null;
64 | /**
65 | * Changes the internal caret position
66 | * @param {number} position The caret's start position
67 | * @param {number} positionEnd The caret's end position
68 | */
69 | setCaretPosition(position: number | null, endPosition?: number | null): void;
70 | /**
71 | * Retrieve the candidates for a given input
72 | * @param input The input string to check
73 | */
74 | getInputCandidates(input: string): {
75 | candidateKey: string;
76 | candidateValue: string;
77 | } | Record;
78 | /**
79 | * Shows a suggestion box with a list of candidate words
80 | * @param candidates The chosen candidates string as defined in the layoutCandidates option
81 | * @param targetElement The element next to which the candidates box will be shown
82 | */
83 | showCandidatesBox(candidateKey: string, candidateValue: string, targetElement: KeyboardElement): void;
84 | /**
85 | * Handles clicks made to keyboard buttons
86 | * @param {string} button The button's layout name.
87 | */
88 | handleButtonClicked(button: string, e?: KeyboardHandlerEvent): void;
89 | /**
90 | * Get mouse hold state
91 | */
92 | getMouseHold(): boolean;
93 | /**
94 | * Mark mouse hold state as set
95 | */
96 | setMouseHold(value: boolean): void;
97 | /**
98 | * Handles button mousedown
99 | */
100 | handleButtonMouseDown(button: string, e: KeyboardHandlerEvent): void;
101 | /**
102 | * Handles button mouseup
103 | */
104 | handleButtonMouseUp(button?: string, e?: KeyboardHandlerEvent): void;
105 | /**
106 | * Handles container mousedown
107 | */
108 | handleKeyboardContainerMouseDown(e: KeyboardHandlerEvent): void;
109 | /**
110 | * Handles button hold
111 | */
112 | handleButtonHold(button: string): void;
113 | /**
114 | * Send a command to all simple-keyboard instances (if you have several instances).
115 | */
116 | syncInstanceInputs(): void;
117 | /**
118 | * Clear the keyboard’s input.
119 | * @param {string} [inputName] optional - the internal input to select
120 | */
121 | clearInput(inputName?: string): void;
122 | /**
123 | * Get the keyboard’s input (You can also get it from the onChange prop).
124 | * @param {string} [inputName] optional - the internal input to select
125 | */
126 | getInput(inputName?: string, skipSync?: boolean): string;
127 | /**
128 | * Get all simple-keyboard inputs
129 | */
130 | getAllInputs(): KeyboardInput;
131 | /**
132 | * Set the keyboard’s input.
133 | * @param {string} input the input value
134 | * @param {string} inputName optional - the internal input to select
135 | */
136 | setInput(input: string, inputName?: string, skipSync?: boolean): void;
137 | /**
138 | * Replace the input object (`keyboard.input`)
139 | * @param {object} inputObj The input object
140 | */
141 | replaceInput(inputObj: KeyboardInput): void;
142 | /**
143 | * Set new option or modify existing ones after initialization.
144 | * @param {object} options The options to set
145 | */
146 | setOptions(options?: {}): void;
147 | /**
148 | * Detecting changes to non-function options
149 | * This allows us to ascertain whether a button re-render is needed
150 | */
151 | changedOptions(newOptions: Partial): string[];
152 | /**
153 | * Executing actions depending on changed options
154 | * @param {object} options The options to set
155 | */
156 | onSetOptions(changedOptions?: string[]): void;
157 | /**
158 | * Remove all keyboard rows and reset keyboard values.
159 | * Used internally between re-renders.
160 | */
161 | resetRows(): void;
162 | /**
163 | * Send a command to all simple-keyboard instances at once (if you have multiple instances).
164 | * @param {function(instance: object, key: string)} callback Function to run on every instance
165 | */
166 | dispatch(callback: (instance: SimpleKeyboard, key?: string) => void): void;
167 | /**
168 | * Adds/Modifies an entry to the `buttonTheme`. Basically a way to add a class to a button.
169 | * @param {string} buttons List of buttons to select (separated by a space).
170 | * @param {string} className Classes to give to the selected buttons (separated by space).
171 | */
172 | addButtonTheme(buttons: string, className: string): void;
173 | /**
174 | * Removes/Amends an entry to the `buttonTheme`. Basically a way to remove a class previously added to a button through buttonTheme or addButtonTheme.
175 | * @param {string} buttons List of buttons to select (separated by a space).
176 | * @param {string} className Classes to give to the selected buttons (separated by space).
177 | */
178 | removeButtonTheme(buttons: string, className: string): void;
179 | /**
180 | * Get the DOM Element of a button. If there are several buttons with the same name, an array of the DOM Elements is returned.
181 | * @param {string} button The button layout name to select
182 | */
183 | getButtonElement(button: string): KeyboardElement | KeyboardElement[] | undefined;
184 | /**
185 | * This handles the "inputPattern" option
186 | * by checking if the provided inputPattern passes
187 | */
188 | inputPatternIsValid(inputVal: string): boolean;
189 | /**
190 | * Handles simple-keyboard event listeners
191 | */
192 | setEventListeners(): void;
193 | /**
194 | * Event Handler: KeyUp
195 | */
196 | handleKeyUp(event: KeyboardHandlerEvent): void;
197 | /**
198 | * Event Handler: KeyDown
199 | */
200 | handleKeyDown(event: KeyboardHandlerEvent): void;
201 | /**
202 | * Event Handler: MouseUp
203 | */
204 | handleMouseUp(event: KeyboardHandlerEvent): void;
205 | /**
206 | * Event Handler: TouchEnd
207 | */
208 | handleTouchEnd(event: KeyboardHandlerEvent): void;
209 | /**
210 | * Event Handler: Select
211 | */
212 | handleSelect(event: KeyboardHandlerEvent): void;
213 | /**
214 | * Event Handler: SelectionChange
215 | */
216 | handleSelectionChange(event: KeyboardHandlerEvent): void;
217 | /**
218 | * Called by {@link setEventListeners} when an event that warrants a cursor position update is triggered
219 | */
220 | caretEventHandler(event: KeyboardHandlerEvent): void;
221 | /**
222 | * Execute an operation on each button
223 | */
224 | recurseButtons(fn: any): void;
225 | /**
226 | * Destroy keyboard listeners and DOM elements
227 | */
228 | destroy(): void;
229 | /**
230 | * Process buttonTheme option
231 | */
232 | getButtonThemeClasses(button: string): string[];
233 | /**
234 | * Process buttonAttributes option
235 | */
236 | setDOMButtonAttributes(button: string, callback: any): void;
237 | onTouchDeviceDetected(): void;
238 | /**
239 | * Disabling contextual window for hg-button
240 | */
241 | disableContextualWindow(): void;
242 | /**
243 | * Process autoTouchEvents option
244 | */
245 | processAutoTouchEvents(): void;
246 | /**
247 | * Executes the callback function once simple-keyboard is rendered for the first time (on initialization).
248 | */
249 | onInit(): void;
250 | /**
251 | * Executes the callback function before a simple-keyboard render.
252 | */
253 | beforeFirstRender(): void;
254 | /**
255 | * Executes the callback function before a simple-keyboard render.
256 | */
257 | beforeRender(): void;
258 | /**
259 | * Executes the callback function every time simple-keyboard is rendered (e.g: when you change layouts).
260 | */
261 | onRender(): void;
262 | /**
263 | * Executes the callback function once all modules have been loaded
264 | */
265 | onModulesLoaded(): void;
266 | /**
267 | * Register module
268 | */
269 | registerModule: (name: string, initCallback: any) => void;
270 | /**
271 | * Load modules
272 | */
273 | loadModules(): void;
274 | /**
275 | * Get module prop
276 | */
277 | getModuleProp(name: string, prop: string): any;
278 | /**
279 | * getModulesList
280 | */
281 | getModulesList(): string[];
282 | /**
283 | * Parse Row DOM containers
284 | */
285 | parseRowDOMContainers(rowDOM: HTMLDivElement, rowIndex: number, containerStartIndexes: number[], containerEndIndexes: number[]): HTMLDivElement;
286 | /**
287 | * getKeyboardClassString
288 | */
289 | getKeyboardClassString: (...baseDOMClasses: any[]) => string;
290 | /**
291 | * Renders rows and buttons as per options
292 | */
293 | render(): void;
294 | }
295 | export interface SKWindow extends Window {
296 | SimpleKeyboardInstances?: any;
297 | }
298 | export interface KeyboardLayoutObject {
299 | [key: string]: string[];
300 | }
301 | export type KeyboardButtonTheme = {
302 | class: string;
303 | buttons: string;
304 | } | null;
305 | export interface KeyboardButtonAttributes {
306 | attribute: string;
307 | value: string;
308 | buttons: string;
309 | }
310 | export interface KeyboardInput {
311 | [key: string]: string;
312 | }
313 | export type CandidateBoxParams = {
314 | utilities: Utilities;
315 | options: KeyboardOptions;
316 | };
317 | export type CandidateBoxShowParams = {
318 | candidateValue: string;
319 | targetElement: KeyboardElement;
320 | onSelect: (selectedCandidate: string, e: MouseEvent) => void;
321 | };
322 | export type CandidateBoxRenderParams = {
323 | candidateListPages: string[][];
324 | targetElement: KeyboardElement;
325 | pageIndex: number;
326 | nbPages: number;
327 | onItemSelected: (selectedCandidate: string, e: MouseEvent) => void;
328 | };
329 | export type KeyboardElement = HTMLDivElement | HTMLButtonElement;
330 | export type KeyboardHandlerEvent = any;
331 | export interface KeyboardButtonElements {
332 | [key: string]: KeyboardElement[];
333 | }
334 | export interface UtilitiesParams {
335 | getOptions: () => KeyboardOptions;
336 | getCaretPosition: () => number | null;
337 | getCaretPositionEnd: () => number | null;
338 | dispatch: any;
339 | }
340 | export interface PhysicalKeyboardParams {
341 | getOptions: () => KeyboardOptions;
342 | dispatch: any;
343 | }
344 | export interface KeyboardOptions {
345 | /**
346 | * Modify the keyboard layout.
347 | */
348 | layout?: KeyboardLayoutObject;
349 | /**
350 | * Specifies which layout should be used.
351 | */
352 | layoutName?: string;
353 | /**
354 | * Replaces variable buttons (such as `{bksp}`) with a human-friendly name (e.g.: `backspace`).
355 | */
356 | display?: {
357 | [button: string]: string;
358 | };
359 | /**
360 | * By default, when you set the display property, you replace the default one. This setting merges them instead.
361 | */
362 | mergeDisplay?: boolean;
363 | /**
364 | * A prop to add your own css classes to the keyboard wrapper. You can add multiple classes separated by a space.
365 | */
366 | theme?: string;
367 | /**
368 | * A prop to add your own css classes to one or several buttons.
369 | */
370 | buttonTheme?: KeyboardButtonTheme[];
371 | /**
372 | * A prop to add your own attributes to one or several buttons.
373 | */
374 | buttonAttributes?: KeyboardButtonAttributes[];
375 | /**
376 | * Runs a `console.log` every time a key is pressed. Displays the buttons pressed and the current input.
377 | */
378 | debug?: boolean;
379 | /**
380 | * Specifies whether clicking the "ENTER" button will input a newline (`\n`) or not.
381 | */
382 | newLineOnEnter?: boolean;
383 | /**
384 | * Specifies whether clicking the "TAB" button will input a tab character (`\t`) or not.
385 | */
386 | tabCharOnTab?: boolean;
387 | /**
388 | * Allows you to use a single simple-keyboard instance for several inputs.
389 | */
390 | inputName?: string;
391 | /**
392 | * `number`: Restrains all of simple-keyboard inputs to a certain length. This should be used in addition to the input element’s maxlengthattribute.
393 | *
394 | * `{ [inputName: string]: number }`: Restrains simple-keyboard’s individual inputs to a certain length. This should be used in addition to the input element’s maxlengthattribute.
395 | */
396 | maxLength?: any;
397 | /**
398 | * When set to true, this option synchronizes the internal input of every simple-keyboard instance.
399 | */
400 | syncInstanceInputs?: boolean;
401 | /**
402 | * Enable highlighting of keys pressed on physical keyboard.
403 | */
404 | physicalKeyboardHighlight?: boolean;
405 | /**
406 | * Calls handler for a button highlighted by physicalKeyboardHighlight
407 | * In other words, this calls keyboard.handleButtonClicked(buttonName) on the highlighted button
408 | */
409 | physicalKeyboardHighlightPress?: boolean;
410 | /**
411 | * Trigger click on a button's element when using physicalKeyboardHighlightPress
412 | * In other words, this calls button.click() on the highlighted button
413 | */
414 | physicalKeyboardHighlightPressUseClick?: boolean;
415 | /**
416 | * Whether physicalKeyboardHighlightPress should use pointer events to trigger buttons.
417 | */
418 | physicalKeyboardHighlightPressUsePointerEvents?: boolean;
419 | /**
420 | * Define the text color that the physical keyboard highlighted key should have.
421 | */
422 | physicalKeyboardHighlightTextColor?: string;
423 | /**
424 | * Define the background color that the physical keyboard highlighted key should have.
425 | */
426 | physicalKeyboardHighlightBgColor?: string;
427 | /**
428 | * Whether physicalKeyboardHighlight should use preventDefault to disable default browser actions.
429 | */
430 | physicalKeyboardHighlightPreventDefault?: boolean;
431 | /**
432 | * Calling preventDefault for the mousedown events keeps the focus on the input.
433 | */
434 | preventMouseDownDefault?: boolean;
435 | /**
436 | * Calling preventDefault for the mouseup events.
437 | */
438 | preventMouseUpDefault?: boolean;
439 | /**
440 | * Stops pointer down events on simple-keyboard buttons from bubbling to parent elements.
441 | */
442 | stopMouseDownPropagation?: boolean;
443 | /**
444 | * Stops pointer up events on simple-keyboard buttons from bubbling to parent elements.
445 | */
446 | stopMouseUpPropagation?: boolean;
447 | /**
448 | * Render buttons as a button element instead of a div element.
449 | */
450 | useButtonTag?: boolean;
451 | /**
452 | * A prop to ensure characters are always be added/removed at the end of the string.
453 | */
454 | disableCaretPositioning?: boolean;
455 | /**
456 | * Restrains input(s) change to the defined regular expression pattern.
457 | */
458 | inputPattern?: any;
459 | /**
460 | * Instructs simple-keyboard to use touch events instead of click events.
461 | */
462 | useTouchEvents?: boolean;
463 | /**
464 | * Enable useTouchEvents automatically when touch device is detected.
465 | */
466 | autoUseTouchEvents?: boolean;
467 | /**
468 | * Opt out of PointerEvents handling, falling back to the prior mouse event logic.
469 | */
470 | useMouseEvents?: boolean;
471 | /**
472 | * Disable button hold action.
473 | */
474 | disableButtonHold?: boolean;
475 | /**
476 | * Adds unicode right-to-left control characters to input return values.
477 | */
478 | rtl?: boolean;
479 | /**
480 | * Enable input method editor candidate list support.
481 | */
482 | enableLayoutCandidates?: boolean;
483 | /**
484 | * Character suggestions to be shown on certain key presses
485 | */
486 | layoutCandidates?: {
487 | [key: string]: string;
488 | };
489 | /**
490 | * Exclude buttons from layout
491 | */
492 | excludeFromLayout?: {
493 | [key: string]: string[];
494 | };
495 | /**
496 | * Determines size of layout candidate list
497 | */
498 | layoutCandidatesPageSize?: number;
499 | /**
500 | * Determines whether layout candidate match should be case sensitive.
501 | */
502 | layoutCandidatesCaseSensitiveMatch?: boolean;
503 | /**
504 | * Disables the automatic normalization for selected layout candidates
505 | */
506 | disableCandidateNormalization?: boolean;
507 | /**
508 | * Enables onKeyPress triggering for layoutCandidate items
509 | */
510 | enableLayoutCandidatesKeyPress?: boolean;
511 | /**
512 | * Updates caret when selectionchange event is fired
513 | */
514 | updateCaretOnSelectionChange?: boolean;
515 | /**
516 | * When useMouseEvents is enabled, this option allows you to trigger a button click event on mousedown
517 | */
518 | clickOnMouseDown?: boolean;
519 | /**
520 | * Executes the callback function every time simple-keyboard is rendered (e.g: when you change layouts).
521 | */
522 | onRender?: (instance: SimpleKeyboard) => void;
523 | /**
524 | * Executes the callback function once simple-keyboard is rendered for the first time (on initialization).
525 | */
526 | onInit?: (instance: SimpleKeyboard) => void;
527 | /**
528 | * Retrieves the current input
529 | */
530 | onChange?: (input: string, e?: MouseEvent) => any;
531 | /**
532 | * Retrieves all inputs
533 | */
534 | onChangeAll?: (inputObj: KeyboardInput, e?: MouseEvent) => any;
535 | /**
536 | * Retrieves the pressed key
537 | */
538 | onKeyPress?: (button: string, e?: MouseEvent) => any;
539 | /**
540 | * Retrieves the released key
541 | */
542 | onKeyReleased?: (button: string, e?: MouseEvent) => any;
543 | /**
544 | * Executes the callback function before an input is about to be updated
545 | */
546 | beforeInputUpdate?: (instance: SimpleKeyboard) => void;
547 | /**
548 | * Module options can have any format
549 | */
550 | [name: string]: any;
551 | }
552 | /**
553 | * Physical Keyboard Service
554 | */
555 | export interface PhysicalKeyboard {
556 | getOptions: () => KeyboardOptions;
557 | dispatch: any;
558 | /**
559 | * Creates an instance of the PhysicalKeyboard service
560 | */
561 | constructor: ({ dispatch, getOptions }: PhysicalKeyboardParams) => any
562 | handleHighlightKeyDown(e: KeyboardEvent): void;
563 | handleHighlightKeyUp(e: KeyboardEvent): void;
564 | /**
565 | * Transforms a KeyboardEvent's "key.code" string into a simple-keyboard layout format
566 | * @param {object} e The KeyboardEvent
567 | */
568 | getSimpleKeyboardLayoutKey(e: KeyboardEvent): string;
569 | /**
570 | * Retrieve key from keyCode
571 | */
572 | keyCodeToKey(keyCode: number): string;
573 | isModifierKey: (e: KeyboardEvent) => boolean;
574 | }
575 | /**
576 | * Utility Service
577 | */
578 | export interface Utilities {
579 | getOptions: () => KeyboardOptions;
580 | getCaretPosition: () => number | null;
581 | getCaretPositionEnd: () => number | null;
582 | dispatch: any;
583 | maxLengthReached: boolean;
584 | /**
585 | * Creates an instance of the Utility service
586 | */
587 | constructor: ({ getOptions, getCaretPosition, getCaretPositionEnd, dispatch, }: UtilitiesParams) => any
588 | /**
589 | * Retrieve button type
590 | *
591 | * @param {string} button The button's layout name
592 | * @return {string} The button type
593 | */
594 | getButtonType(button: string): string;
595 | /**
596 | * Adds default classes to a given button
597 | *
598 | * @param {string} button The button's layout name
599 | * @return {string} The classes to be added to the button
600 | */
601 | getButtonClass(button: string): string;
602 | /**
603 | * Default button display labels
604 | */
605 | getDefaultDiplay(): {
606 | "{bksp}": string;
607 | "{backspace}": string;
608 | "{enter}": string;
609 | "{shift}": string;
610 | "{shiftleft}": string;
611 | "{shiftright}": string;
612 | "{alt}": string;
613 | "{s}": string;
614 | "{tab}": string;
615 | "{lock}": string;
616 | "{capslock}": string;
617 | "{accept}": string;
618 | "{space}": string;
619 | "{//}": string;
620 | "{esc}": string;
621 | "{escape}": string;
622 | "{f1}": string;
623 | "{f2}": string;
624 | "{f3}": string;
625 | "{f4}": string;
626 | "{f5}": string;
627 | "{f6}": string;
628 | "{f7}": string;
629 | "{f8}": string;
630 | "{f9}": string;
631 | "{f10}": string;
632 | "{f11}": string;
633 | "{f12}": string;
634 | "{numpaddivide}": string;
635 | "{numlock}": string;
636 | "{arrowup}": string;
637 | "{arrowleft}": string;
638 | "{arrowdown}": string;
639 | "{arrowright}": string;
640 | "{prtscr}": string;
641 | "{scrolllock}": string;
642 | "{pause}": string;
643 | "{insert}": string;
644 | "{home}": string;
645 | "{pageup}": string;
646 | "{delete}": string;
647 | "{forwarddelete}": string;
648 | "{end}": string;
649 | "{pagedown}": string;
650 | "{numpadmultiply}": string;
651 | "{numpadsubtract}": string;
652 | "{numpadadd}": string;
653 | "{numpadenter}": string;
654 | "{period}": string;
655 | "{numpaddecimal}": string;
656 | "{numpad0}": string;
657 | "{numpad1}": string;
658 | "{numpad2}": string;
659 | "{numpad3}": string;
660 | "{numpad4}": string;
661 | "{numpad5}": string;
662 | "{numpad6}": string;
663 | "{numpad7}": string;
664 | "{numpad8}": string;
665 | "{numpad9}": string;
666 | };
667 | /**
668 | * Returns the display (label) name for a given button
669 | *
670 | * @param {string} button The button's layout name
671 | * @param {object} display The provided display option
672 | * @param {boolean} mergeDisplay Whether the provided param value should be merged with the default one.
673 | */
674 | getButtonDisplayName(button: string, display: KeyboardOptions["display"], mergeDisplay?: boolean): string;
675 | /**
676 | * Returns the updated input resulting from clicking a given button
677 | *
678 | * @param {string} button The button's layout name
679 | * @param {string} input The input string
680 | * @param {number} caretPos The cursor's current position
681 | * @param {number} caretPosEnd The cursor's current end position
682 | * @param {boolean} moveCaret Whether to update simple-keyboard's cursor
683 | */
684 | getUpdatedInput(button: string, input: string, caretPos: any, caretPosEnd?: any, moveCaret?: boolean): string;
685 | /**
686 | * Moves the cursor position by a given amount
687 | *
688 | * @param {number} length Represents by how many characters the input should be moved
689 | * @param {boolean} minus Whether the cursor should be moved to the left or not.
690 | */
691 | updateCaretPos(length: number, minus?: boolean): void;
692 | /**
693 | * Action method of updateCaretPos
694 | *
695 | * @param {number} length Represents by how many characters the input should be moved
696 | * @param {boolean} minus Whether the cursor should be moved to the left or not.
697 | */
698 | updateCaretPosAction(length: number, minus?: boolean): number | null;
699 | /**
700 | * Adds a string to the input at a given position
701 | *
702 | * @param {string} source The source input
703 | * @param {string} str The string to add
704 | * @param {number} position The (cursor) position where the string should be added
705 | * @param {boolean} moveCaret Whether to update simple-keyboard's cursor
706 | */
707 | addStringAt(source: string, str: string, position?: number, positionEnd?: number, moveCaret?: boolean): string;
708 | /**
709 | * Check whether the button is a standard button
710 | */
711 | isStandardButton: (button: string) => boolean | "";
712 | /**
713 | * Removes an amount of characters before a given position
714 | *
715 | * @param {string} source The source input
716 | * @param {number} position The (cursor) position from where the characters should be removed
717 | * @param {boolean} moveCaret Whether to update simple-keyboard's cursor
718 | */
719 | removeAt(source: string, position?: number, positionEnd?: number, moveCaret?: boolean): string;
720 | /**
721 | * Removes an amount of characters after a given position
722 | *
723 | * @param {string} source The source input
724 | * @param {number} position The (cursor) position from where the characters should be removed
725 | */
726 | removeForwardsAt(source: string, position?: number, positionEnd?: number, moveCaret?: boolean): string;
727 | /**
728 | * Determines whether the maxLength has been reached. This function is called when the maxLength option it set.
729 | *
730 | * @param {object} inputObj
731 | * @param {string} updatedInput
732 | */
733 | handleMaxLength(inputObj: KeyboardInput, updatedInput: string): boolean | undefined;
734 | /**
735 | * Gets the current value of maxLengthReached
736 | */
737 | isMaxLengthReached(): boolean;
738 | /**
739 | * Determines whether a touch device is being used
740 | */
741 | isTouchDevice(): number | true;
742 | /**
743 | * Determines whether pointer events are supported
744 | */
745 | pointerEventsSupported(): boolean;
746 | /**
747 | * Bind all methods in a given class
748 | */
749 | /**
750 | * Transforms an arbitrary string to camelCase
751 | *
752 | * @param {string} str The string to transform.
753 | */
754 | camelCase(str: string): string;
755 | /**
756 | * Split array into chunks
757 | */
758 | chunkArray(arr: T[], size: number): T[][];
759 | /**
760 | * Escape regex input
761 | */
762 | escapeRegex(str: string): string;
763 | /**
764 | * Calculate caret position offset when using rtl option
765 | */
766 | getRtlOffset(index: number, input: string): number;
767 | /**
768 | * Reusable empty function
769 | */
770 | /**
771 | * Check if a function is a constructor
772 | */
773 | isConstructor(f: any): boolean;
774 | }
775 | export interface CandidateBox {
776 | utilities: Utilities;
777 | options: KeyboardOptions;
778 | candidateBoxElement: HTMLDivElement;
779 | pageIndex: number;
780 | pageSize: number;
781 | constructor: ({ utilities, options }: CandidateBoxParams) => any
782 | destroy(): void;
783 | show({ candidateValue, targetElement, onSelect, }: CandidateBoxShowParams): void;
784 | renderPage({ candidateListPages, targetElement, pageIndex, nbPages, onItemSelected, }: CandidateBoxRenderParams): void;
785 | }
786 |
--------------------------------------------------------------------------------
/build/polyfills.d.ts:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hodgef/react-simple-keyboard/753756f72d49ab38aecbd69fd180dfff265fb61d/build/polyfills.d.ts
--------------------------------------------------------------------------------
/build/services/Utilities.d.ts:
--------------------------------------------------------------------------------
1 | import { KeyboardReactInterface } from "../interfaces";
2 | export declare const parseProps: (props: KeyboardReactInterface["options"]) => {
3 | theme: string;
4 | layout?: import("../interfaces").KeyboardLayoutObject | undefined;
5 | layoutName?: string | undefined;
6 | display?: {
7 | [button: string]: string;
8 | } | undefined;
9 | mergeDisplay?: boolean | undefined;
10 | buttonTheme?: import("../interfaces").KeyboardButtonTheme[] | undefined;
11 | buttonAttributes?: import("../interfaces").KeyboardButtonAttributes[] | undefined;
12 | debug?: boolean | undefined;
13 | newLineOnEnter?: boolean | undefined;
14 | tabCharOnTab?: boolean | undefined;
15 | inputName?: string | undefined;
16 | maxLength?: any;
17 | syncInstanceInputs?: boolean | undefined;
18 | physicalKeyboardHighlight?: boolean | undefined;
19 | physicalKeyboardHighlightPress?: boolean | undefined;
20 | physicalKeyboardHighlightPressUseClick?: boolean | undefined;
21 | physicalKeyboardHighlightPressUsePointerEvents?: boolean | undefined;
22 | physicalKeyboardHighlightTextColor?: string | undefined;
23 | physicalKeyboardHighlightBgColor?: string | undefined;
24 | physicalKeyboardHighlightPreventDefault?: boolean | undefined;
25 | preventMouseDownDefault?: boolean | undefined;
26 | preventMouseUpDefault?: boolean | undefined;
27 | stopMouseDownPropagation?: boolean | undefined;
28 | stopMouseUpPropagation?: boolean | undefined;
29 | useButtonTag?: boolean | undefined;
30 | disableCaretPositioning?: boolean | undefined;
31 | inputPattern?: any;
32 | useTouchEvents?: boolean | undefined;
33 | autoUseTouchEvents?: boolean | undefined;
34 | useMouseEvents?: boolean | undefined;
35 | disableButtonHold?: boolean | undefined;
36 | rtl?: boolean | undefined;
37 | enableLayoutCandidates?: boolean | undefined;
38 | layoutCandidates?: {
39 | [key: string]: string;
40 | } | undefined;
41 | excludeFromLayout?: {
42 | [key: string]: string[];
43 | } | undefined;
44 | layoutCandidatesPageSize?: number | undefined;
45 | layoutCandidatesCaseSensitiveMatch?: boolean | undefined;
46 | disableCandidateNormalization?: boolean | undefined;
47 | enableLayoutCandidatesKeyPress?: boolean | undefined;
48 | updateCaretOnSelectionChange?: boolean | undefined;
49 | clickOnMouseDown?: boolean | undefined;
50 | onRender?: ((instance: import("../interfaces").SimpleKeyboard) => void) | undefined;
51 | onInit?: ((instance: import("../interfaces").SimpleKeyboard) => void) | undefined;
52 | onChange?: ((input: string, e?: MouseEvent | undefined) => any) | undefined;
53 | onChangeAll?: ((inputObj: import("../interfaces").KeyboardInput, e?: MouseEvent | undefined) => any) | undefined;
54 | onKeyPress?: ((button: string, e?: MouseEvent | undefined) => any) | undefined;
55 | onKeyReleased?: ((button: string, e?: MouseEvent | undefined) => any) | undefined;
56 | beforeInputUpdate?: ((instance: import("../interfaces").SimpleKeyboard) => void) | undefined;
57 | keyboardRef?: ((r: any) => void) | undefined;
58 | };
59 | export declare const changedProps: (prevProps: KeyboardReactInterface["options"], props: KeyboardReactInterface["options"]) => string[];
60 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-simple-keyboard",
3 | "version": "3.8.80",
4 | "description": "React.js Virtual Keyboard",
5 | "main": "build/index.js",
6 | "scripts": {
7 | "start": "webpack serve --config webpack.config.demo.js",
8 | "build": "npm run generate-types && webpack && npm run build-modern && npm run build-modern-esm && tsc",
9 | "build-modern": "webpack --config webpack.config.modern.js",
10 | "build-modern-esm": "webpack --config webpack.config.modern_esm.js",
11 | "test": "jest --silent",
12 | "coverage": "npm run test -- --coverage",
13 | "prepare": "npm run build",
14 | "generate-types": "node scripts/generateKeyboardTypes.js"
15 | },
16 | "repository": {
17 | "type": "git",
18 | "url": "https://github.com/hodgef/react-simple-keyboard"
19 | },
20 | "author": "Francisco Hodge (https://github.com/hodgef)",
21 | "bugs": {
22 | "url": "https://github.com/hodgef/react-simple-keyboard/issues"
23 | },
24 | "homepage": "https://virtual-keyboard.js.org/",
25 | "keywords": [
26 | "react",
27 | "reactjs",
28 | "digital",
29 | "keyboard",
30 | "onscreen",
31 | "virtual",
32 | "component",
33 | "screen-keyboard",
34 | "component",
35 | "virtual-keyboard",
36 | "touchscreen",
37 | "touch-screen",
38 | "kiosk",
39 | "osk",
40 | "js"
41 | ],
42 | "license": "MIT",
43 | "devDependencies": {
44 | "@babel/cli": "^7.27.2",
45 | "@babel/core": "^7.27.4",
46 | "@babel/plugin-proposal-class-properties": "^7.16.7",
47 | "@babel/plugin-transform-typescript": "^7.27.0",
48 | "@babel/preset-env": "^7.27.2",
49 | "@babel/preset-react": "^7.26.3",
50 | "@babel/preset-typescript": "^7.27.1",
51 | "@types/enzyme": "^3.10.12",
52 | "@types/jest": "^29.5.14",
53 | "@types/react": "^18.0.9",
54 | "@types/react-dom": "^18.0.3",
55 | "@typescript-eslint/eslint-plugin": "^4.33.0",
56 | "@typescript-eslint/parser": "^4.32.0",
57 | "@wojtekmaj/enzyme-adapter-react-17": "^0.8.0",
58 | "autoprefixer": "^10.4.21",
59 | "babel-eslint": "^10.1.0",
60 | "babel-loader": "^10.0.0",
61 | "babel-preset-minify": "^0.5.2",
62 | "copy-webpack-plugin": "^13.0.0",
63 | "core-js": "^3.42.0",
64 | "css-loader": "^7.1.2",
65 | "css-minimizer-webpack-plugin": "^7.0.2",
66 | "dts-bundle": "^0.7.3",
67 | "enzyme": "^3.11.0",
68 | "eslint": "^7.32.0",
69 | "file-loader": "^6.2.0",
70 | "html-webpack-plugin": "^5.6.3",
71 | "jest": "^29.7.0",
72 | "jest-environment-jsdom": "^29.7.0",
73 | "mini-css-extract-plugin": "^2.9.2",
74 | "postcss": "^8.5.4",
75 | "postcss-loader": "^8.1.1",
76 | "react": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
77 | "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
78 | "simple-keyboard": "3.8.59",
79 | "style-loader": "^4.0.0",
80 | "terser-webpack-plugin": "^5.3.14",
81 | "typescript": "^4.9.5",
82 | "url-loader": "^4.1.1",
83 | "webpack": "^5.99.9",
84 | "webpack-cli": "^6.0.1",
85 | "webpack-dev-server": "4.13.3"
86 | },
87 | "peerDependencies": {
88 | "react": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
89 | "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
90 | },
91 | "jest": {
92 | "testEnvironment": "jsdom",
93 | "roots": [
94 | "/src"
95 | ],
96 | "collectCoverageFrom": [
97 | "src/**/*.{js,jsx,ts,tsx}",
98 | "!src/**/*.d.ts",
99 | "!src/lib/index.js",
100 | "!src/lib/polyfills.js",
101 | "!src/demo/**",
102 | "!src/utils/**",
103 | "!src/**/*.d.ts",
104 | "!**/tests/**"
105 | ],
106 | "testMatch": [
107 | "/src/**/__tests__/**/*.{js,jsx,ts,tsx}",
108 | "/src/**/*.{spec,test}.{js,jsx,ts,tsx}"
109 | ],
110 | "transformIgnorePatterns": [
111 | "[/\\\\]node_modules[/\\\\].+\\.(js|jsx|ts|tsx)$",
112 | "^.+\\.module\\.(css|sass|scss)$"
113 | ],
114 | "modulePaths": [],
115 | "moduleNameMapper": {
116 | "\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "/scripts/testMock.js",
117 | "\\.(css|less)$": "/scripts/testMock.js"
118 | },
119 | "moduleFileExtensions": [
120 | "web.js",
121 | "js",
122 | "web.ts",
123 | "ts",
124 | "web.tsx",
125 | "tsx",
126 | "json",
127 | "web.jsx",
128 | "jsx",
129 | "node"
130 | ]
131 | }
132 | }
133 |
--------------------------------------------------------------------------------
/scripts/generateKeyboardTypes.js:
--------------------------------------------------------------------------------
1 | var fs = require('fs');
2 | var dts = require('dts-bundle');
3 |
4 | dts.bundle({
5 | name: 'simple-keyboard',
6 | main: 'node_modules/simple-keyboard/build/index.d.ts',
7 | out: '../../../src/lib/interfaces.d.ts',
8 | outputAsModuleFolder: true
9 | });
10 |
11 | let keyboardInterface = fs.readFileSync('src/lib/interfaces.d.ts', 'utf8');
12 | keyboardInterface = keyboardInterface.replace(/export default (.*);/g, "");
13 | keyboardInterface = keyboardInterface.replace(/export {(.*)};/g, "");
14 | keyboardInterface = keyboardInterface.replace(/import (.*);/g, "");
15 | keyboardInterface = keyboardInterface.replace(/class (.*) {/g, "export interface $1 {");
16 | keyboardInterface = keyboardInterface.replace(/static (.*);/g, "");
17 | keyboardInterface = keyboardInterface.replace(/constructor\((.*)\);/g, "constructor: ($1) => any");
18 | keyboardInterface = keyboardInterface.replace(/\n\s*\n/g, '\n');
19 | keyboardInterface = `
20 | export interface KeyboardReactInterface extends SimpleKeyboard {
21 | options: SimpleKeyboard["options"] & {
22 | // eslint-disable-next-line no-unused-vars
23 | keyboardRef?: (r: any) => void;
24 | };
25 | }
26 | ` + keyboardInterface;
27 | fs.writeFileSync('src/lib/interfaces.d.ts', keyboardInterface);
--------------------------------------------------------------------------------
/scripts/getPackageJson.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | const path = require('path');
3 |
4 | /**
5 | * A module to get package informations from package.json
6 | * @module getPackageJson
7 | * @param {...string} keys from package.json if no arguments passed it returns package.json content as object
8 | * @returns {object} with given keys or content of package.json as object
9 | */
10 |
11 | /**
12 | * Returns package info
13 | */
14 | const getPackageJson = function(...args) {
15 | const packageJSON = JSON.parse(fs.readFileSync(path.join(__dirname, '../package.json')));
16 | if (!args.length) {
17 | return packageJSON;
18 | }
19 | return args.reduce((out, key) => {
20 | out[key] = packageJSON[key];
21 | return out;
22 | }, {});
23 | };
24 |
25 | module.exports = getPackageJson;
--------------------------------------------------------------------------------
/scripts/loaderMock.js:
--------------------------------------------------------------------------------
1 | module.exports = () => "";
--------------------------------------------------------------------------------
/scripts/testMock.js:
--------------------------------------------------------------------------------
1 | module.exports = {};
--------------------------------------------------------------------------------
/src/demo/App.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import SimpleKeyboard from "simple-keyboard";
3 | import Keyboard from "../lib";
4 | import "./css/App.css";
5 |
6 | class App extends React.Component {
7 | state = {
8 | input: "",
9 | layoutName: "default",
10 | };
11 |
12 | keyboard: SimpleKeyboard;
13 |
14 | onChange = (input) =>
15 | this.setState({ input }, () => console.log("Input changed", input));
16 |
17 | onKeyPress = (button) => {
18 | console.log("Button pressed", button);
19 |
20 | /**
21 | * Shift functionality
22 | */
23 | if (["{capslock}", "{shiftleft}", "{shiftright}"].includes(button))
24 | this.handleShiftButton();
25 | };
26 |
27 | handleShiftButton = () => {
28 | const {
29 | state: { layoutName },
30 | } = this;
31 | const shiftToggle = layoutName === "default" ? "shift" : "default";
32 |
33 | this.setState({ layoutName: shiftToggle });
34 | };
35 |
36 | onChangeInput = (event) => {
37 | const input = event.target.value;
38 |
39 | this.setState({ input: event.target.value }, () =>
40 | this.keyboard.setInput(input)
41 | );
42 | };
43 |
44 | render() {
45 | const {
46 | state: { input, layoutName },
47 | onChangeInput,
48 | onChange,
49 | onKeyPress,
50 | } = this;
51 |
52 | return (
53 |
54 |
55 |
60 |
61 |
(this.keyboard = r)}
63 | onChange={onChange}
64 | onKeyPress={onKeyPress}
65 | layoutName={layoutName}
66 | />
67 |
68 | );
69 | }
70 | }
71 |
72 | export default App;
73 |
--------------------------------------------------------------------------------
/src/demo/css/App.css:
--------------------------------------------------------------------------------
1 | .demoPage {
2 | font-family: "HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue",
3 | Helvetica, Arial, "Lucida Grande", sans-serif;
4 | max-width: 850px;
5 | margin: 0 auto;
6 | padding-top: 20px;
7 | }
8 |
9 | .demoPage .screenContainer {
10 | background: rgba(0, 0, 0, 0.8);
11 | border: 20px solid rgba(0, 0, 0, 0.1);
12 | height: 200px;
13 | border-top-right-radius: 5px;
14 | border-top-left-radius: 5px;
15 | padding: 10px;
16 | box-sizing: border-box;
17 | }
18 |
19 | .demoPage .inputContainer {
20 | color: rgba(255, 255, 255, 0.9);
21 | background: transparent;
22 | border: none;
23 | outline: none;
24 | font-family: monospace;
25 | width: 100%;
26 | height: 100%;
27 | font-size: 18px;
28 | }
29 |
30 | .simple-keyboard.hg-layout-custom {
31 | border-top-left-radius: 0px;
32 | border-top-right-radius: 0px;
33 | }
34 |
35 | .simple-keyboard .hg-button.hg-selectedButton {
36 | background: rgba(5, 25, 70, 0.53);
37 | color: white;
38 | }
39 |
--------------------------------------------------------------------------------
/src/demo/images/demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hodgef/react-simple-keyboard/753756f72d49ab38aecbd69fd180dfff265fb61d/src/demo/images/demo.gif
--------------------------------------------------------------------------------
/src/demo/images/keyboard.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hodgef/react-simple-keyboard/753756f72d49ab38aecbd69fd180dfff265fb61d/src/demo/images/keyboard.PNG
--------------------------------------------------------------------------------
/src/demo/images/simple-keyboard.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hodgef/react-simple-keyboard/753756f72d49ab38aecbd69fd180dfff265fb61d/src/demo/images/simple-keyboard.png
--------------------------------------------------------------------------------
/src/demo/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 |
4 | import App from "./App";
5 |
6 | const root = document.createElement("div");
7 | root.className = "root";
8 |
9 | document.body.appendChild(root);
10 |
11 | ReactDOM.render(, root);
12 |
--------------------------------------------------------------------------------
/src/lib/components/Keyboard.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import Keyboard from "simple-keyboard";
3 | import { parseProps, changedProps } from "../services/Utilities";
4 | import "simple-keyboard/build/css/index.css";
5 | import { KeyboardReactInterface } from "../interfaces.d";
6 |
7 | const KeyboardReact = (props: KeyboardReactInterface["options"]) : any => {
8 | const cssClass = props.baseClass || "react-simple-keyboard";
9 | const initRef = React.useRef(null);
10 | const targetElemRef = React.useRef(null);
11 | const keyboardRef = React.useRef(null);
12 | const previousProps = React.useRef(props);
13 |
14 | React.useEffect(() => {
15 | /**
16 | * Whenever this component is unmounted, ensure that Keyboard object that
17 | * it created is destroyed so that it removes any event handlers that it
18 | * may have installed.
19 | */
20 | return () => {
21 | if (keyboardRef.current) {
22 | keyboardRef.current.destroy();
23 | }
24 | initRef.current = false;
25 | };
26 | }, []);
27 |
28 | React.useEffect(() => {
29 | const parsedProps = parseProps(props) as any;
30 |
31 | /**
32 | * Initialize simple-keyboard
33 | */
34 | if (!initRef.current) {
35 | initRef.current = true;
36 | parsedProps.debug && console.log("ReactSimpleKeyboard: Init");
37 | const targetElem = targetElemRef.current as HTMLDivElement;
38 | const targetClass = `.${cssClass}`;
39 | keyboardRef.current = new Keyboard(
40 | targetElem || targetClass,
41 | parsedProps
42 | ) as KeyboardReactInterface;
43 | parsedProps.keyboardRef && parsedProps.keyboardRef(keyboardRef.current);
44 | }
45 |
46 | const updatedProps = changedProps(previousProps.current, parsedProps);
47 |
48 | /**
49 | * Only trigger render if props changed
50 | */
51 | if (updatedProps.length) {
52 | const keyboard = keyboardRef.current;
53 | previousProps.current = parsedProps;
54 | keyboard?.setOptions(parsedProps);
55 | parsedProps.debug &&
56 | console.log(
57 | "ReactSimpleKeyboard - setOptions called due to updated props:",
58 | updatedProps
59 | );
60 | }
61 | }, [initRef, cssClass, previousProps, props]);
62 |
63 | return ;
64 | };
65 |
66 | export default KeyboardReact;
67 |
--------------------------------------------------------------------------------
/src/lib/components/KeyboardModern.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-unused-vars */
2 | import * as React from "react";
3 | import { parseProps, changedProps } from "../services/Utilities";
4 | import "simple-keyboard/build/css/index.css";
5 | import { KeyboardReactInterface } from "../interfaces";
6 | import Keyboard from "simple-keyboard/build/index.modern";
7 |
8 | const KeyboardReact = (props: KeyboardReactInterface["options"]) : any => {
9 | const cssClass = props.baseClass || "react-simple-keyboard";
10 | const initRef = React.useRef(null);
11 | const targetElemRef = React.useRef(null);
12 | const keyboardRef = React.useRef(null);
13 | const previousProps = React.useRef(props);
14 |
15 | React.useEffect(() => {
16 | /**
17 | * Whenever this component is unmounted, ensure that Keyboard object that
18 | * it created is destroyed so that it removes any event handlers that it
19 | * may have installed.
20 | */
21 | return () => {
22 | if (keyboardRef.current) {
23 | keyboardRef.current.destroy();
24 | }
25 | initRef.current = false;
26 | };
27 | }, []);
28 |
29 | React.useEffect(() => {
30 | const parsedProps = parseProps(props) as any;
31 |
32 | /**
33 | * Initialize simple-keyboard
34 | */
35 | if (!initRef.current) {
36 | initRef.current = true;
37 | parsedProps.debug && console.log("ReactSimpleKeyboard: Init");
38 | const targetElem = targetElemRef.current as HTMLDivElement;
39 | const targetClass = `.${cssClass}`;
40 | keyboardRef.current = new Keyboard(
41 | targetElem || targetClass,
42 | parsedProps
43 | ) as KeyboardReactInterface;
44 | parsedProps.keyboardRef && parsedProps.keyboardRef(keyboardRef.current);
45 | }
46 |
47 | const updatedProps = changedProps(previousProps.current, parsedProps);
48 |
49 | /**
50 | * Only trigger render if props changed
51 | */
52 | if (updatedProps.length) {
53 | const keyboard = keyboardRef.current;
54 | previousProps.current = parsedProps;
55 | keyboard?.setOptions(parsedProps);
56 | parsedProps.debug &&
57 | console.log(
58 | "ReactSimpleKeyboard - setOptions called due to updated props:",
59 | updatedProps
60 | );
61 | }
62 | }, [initRef, cssClass, previousProps, props]);
63 |
64 | return ;
65 | };
66 |
67 | export default KeyboardReact;
68 |
--------------------------------------------------------------------------------
/src/lib/components/tests/Keyboard.test.tsx:
--------------------------------------------------------------------------------
1 | import Enzyme from "enzyme";
2 | import Adapter from "@wojtekmaj/enzyme-adapter-react-17";
3 | import * as React from "react";
4 |
5 | import { setDOM } from "../../../utils/TestUtility";
6 | import Keyboard from "../Keyboard";
7 |
8 | Enzyme.configure({ adapter: new Adapter() });
9 |
10 | it("Keyboard renders without crashing", () => {
11 | setDOM("container");
12 | Enzyme.mount(, {
13 | attachTo: document.body.querySelector("#root"),
14 | });
15 | });
16 |
17 | it("Keyboard props duly set through setOptions", () => {
18 | setDOM("container");
19 |
20 | let keyboard;
21 | const props = {
22 | keyboardRef: (r) => (keyboard = r),
23 | };
24 | const wrapper = Enzyme.mount(, {
25 | attachTo: document.body.querySelector("#root"),
26 | });
27 |
28 | wrapper.setProps({
29 | maxLength: 5,
30 | });
31 |
32 | expect(keyboard.options.maxLength).toBe(5);
33 | });
34 |
35 | it("Keyboard getInput, setInput, clearInput will work", () => {
36 | setDOM("container");
37 |
38 | let keyboard;
39 | const props = {
40 | keyboardRef: (r) => (keyboard = r),
41 | };
42 | Enzyme.mount(, {
43 | attachTo: document.body.querySelector("#root"),
44 | });
45 |
46 | keyboard.setInput("test");
47 | expect(keyboard.getInput()).toBe("test");
48 |
49 | keyboard.clearInput();
50 | expect(keyboard.getInput()).toBeFalsy();
51 | });
52 |
53 | it("Keyboard getInput will work with param", () => {
54 | setDOM("container");
55 |
56 | let keyboard;
57 | const props = {
58 | keyboardRef: (r) => (keyboard = r),
59 | };
60 | Enzyme.mount(, {
61 | attachTo: document.body.querySelector("#root"),
62 | });
63 |
64 | keyboard.setInput("testy", "testIpt");
65 | expect(keyboard.getInput("testIpt")).toBe("testy");
66 | });
67 |
68 | it("Keyboard onKeyPress will work", () => {
69 | setDOM("container");
70 |
71 | let pressed = false;
72 | const props = {
73 | keyboardRef: (r) => (keyboard = r),
74 | onKeyPress: () => {
75 | pressed = true;
76 | },
77 | };
78 | let keyboard;
79 | Enzyme.mount(, {
80 | attachTo: document.body.querySelector("#root"),
81 | });
82 |
83 | keyboard.getButtonElement("q").onclick();
84 | expect(pressed).toBeTruthy();
85 | });
86 |
87 | it("Keyboard onKeyPress will work without onKeyPress prop", () => {
88 | setDOM("container");
89 |
90 | const props = {
91 | keyboardRef: (r) => (keyboard = r),
92 | debug: true,
93 | };
94 | let keyboard;
95 | Enzyme.mount(, {
96 | attachTo: document.body.querySelector("#root"),
97 | });
98 |
99 | keyboard.getButtonElement("q").onclick();
100 | });
101 |
102 | it("Keyboard onChange will work", () => {
103 | setDOM("container");
104 |
105 | let changed = false;
106 | const props = {
107 | keyboardRef: (r) => (keyboard = r),
108 | onChange: () => {
109 | changed = true;
110 | },
111 | };
112 |
113 | let keyboard;
114 | Enzyme.mount(, {
115 | attachTo: document.body.querySelector("#root"),
116 | });
117 |
118 | keyboard.getButtonElement("q").onclick();
119 | expect(changed).toBeTruthy();
120 | });
121 |
122 | it("Keyboard onChange will work without onChange prop", () => {
123 | setDOM("container");
124 |
125 | const props = {
126 | keyboardRef: (r) => (keyboard = r),
127 | };
128 | let keyboard;
129 | Enzyme.mount(, {
130 | attachTo: document.body.querySelector("#root"),
131 | });
132 |
133 | keyboard.getButtonElement("q").onclick();
134 | });
135 |
136 | it("Keyboard onChangeAll will work", () => {
137 | setDOM("container");
138 |
139 | let changed = false;
140 |
141 | const props = {
142 | keyboardRef: (r) => (keyboard = r),
143 | onChangeAll: () => {
144 | changed = true;
145 | },
146 | };
147 | let keyboard;
148 | Enzyme.mount(, {
149 | attachTo: document.body.querySelector("#root"),
150 | });
151 |
152 | keyboard.getButtonElement("q").onclick();
153 |
154 | expect(changed).toBeTruthy();
155 | });
156 |
157 | it("Keyboard will have default theme", () => {
158 | setDOM("container");
159 |
160 | let changed = false;
161 |
162 | const props = {
163 | keyboardRef: (r) => (keyboard = r),
164 | onChangeAll: () => {
165 | changed = true;
166 | },
167 | };
168 | let keyboard;
169 | Enzyme.mount(, {
170 | attachTo: document.body.querySelector("#root"),
171 | });
172 |
173 | keyboard.getButtonElement("q").onclick();
174 |
175 | expect(changed).toBeTruthy();
176 | });
177 |
--------------------------------------------------------------------------------
/src/lib/components/tests/KeyboardModern.test.tsx:
--------------------------------------------------------------------------------
1 | import Enzyme from "enzyme";
2 | import Adapter from "@wojtekmaj/enzyme-adapter-react-17";
3 | import * as React from "react";
4 |
5 | import { setDOM } from "../../../utils/TestUtility";
6 | import Keyboard from "../KeyboardModern";
7 |
8 | Enzyme.configure({ adapter: new Adapter() });
9 |
10 | it("Keyboard renders without crashing", () => {
11 | setDOM("container");
12 | Enzyme.mount(, {
13 | attachTo: document.body.querySelector("#root"),
14 | });
15 | });
16 |
17 | it("Keyboard props duly set through setOptions", () => {
18 | setDOM("container");
19 |
20 | let keyboard;
21 | const props = {
22 | keyboardRef: (r) => (keyboard = r),
23 | };
24 | const wrapper = Enzyme.mount(, {
25 | attachTo: document.body.querySelector("#root"),
26 | });
27 |
28 | wrapper.setProps({
29 | maxLength: 5,
30 | });
31 |
32 | expect(keyboard.options.maxLength).toBe(5);
33 | });
34 |
35 | it("Keyboard getInput, setInput, clearInput will work", () => {
36 | setDOM("container");
37 |
38 | let keyboard;
39 | const props = {
40 | keyboardRef: (r) => (keyboard = r),
41 | };
42 | Enzyme.mount(, {
43 | attachTo: document.body.querySelector("#root"),
44 | });
45 |
46 | keyboard.setInput("test");
47 | expect(keyboard.getInput()).toBe("test");
48 |
49 | keyboard.clearInput();
50 | expect(keyboard.getInput()).toBeFalsy();
51 | });
52 |
53 | it("Keyboard getInput will work with param", () => {
54 | setDOM("container");
55 |
56 | let keyboard;
57 | const props = {
58 | keyboardRef: (r) => (keyboard = r),
59 | };
60 | Enzyme.mount(, {
61 | attachTo: document.body.querySelector("#root"),
62 | });
63 |
64 | keyboard.setInput("testy", "testIpt");
65 | expect(keyboard.getInput("testIpt")).toBe("testy");
66 | });
67 |
68 | it("Keyboard onKeyPress will work", () => {
69 | setDOM("container");
70 |
71 | let pressed = false;
72 | const props = {
73 | keyboardRef: (r) => (keyboard = r),
74 | onKeyPress: () => {
75 | pressed = true;
76 | },
77 | };
78 | let keyboard;
79 | Enzyme.mount(, {
80 | attachTo: document.body.querySelector("#root"),
81 | });
82 |
83 | keyboard.getButtonElement("q").onclick();
84 | expect(pressed).toBeTruthy();
85 | });
86 |
87 | it("Keyboard onKeyPress will work without onKeyPress prop", () => {
88 | setDOM("container");
89 |
90 | const props = {
91 | keyboardRef: (r) => (keyboard = r),
92 | debug: true,
93 | };
94 | let keyboard;
95 | Enzyme.mount(, {
96 | attachTo: document.body.querySelector("#root"),
97 | });
98 |
99 | keyboard.getButtonElement("q").onclick();
100 | });
101 |
102 | it("Keyboard onChange will work", () => {
103 | setDOM("container");
104 |
105 | let changed = false;
106 | const props = {
107 | keyboardRef: (r) => (keyboard = r),
108 | onChange: () => {
109 | changed = true;
110 | },
111 | };
112 |
113 | let keyboard;
114 | Enzyme.mount(, {
115 | attachTo: document.body.querySelector("#root"),
116 | });
117 |
118 | keyboard.getButtonElement("q").onclick();
119 | expect(changed).toBeTruthy();
120 | });
121 |
122 | it("Keyboard onChange will work without onChange prop", () => {
123 | setDOM("container");
124 |
125 | const props = {
126 | keyboardRef: (r) => (keyboard = r),
127 | };
128 | let keyboard;
129 | Enzyme.mount(, {
130 | attachTo: document.body.querySelector("#root"),
131 | });
132 |
133 | keyboard.getButtonElement("q").onclick();
134 | });
135 |
136 | it("Keyboard onChangeAll will work", () => {
137 | setDOM("container");
138 |
139 | let changed = false;
140 |
141 | const props = {
142 | keyboardRef: (r) => (keyboard = r),
143 | onChangeAll: () => {
144 | changed = true;
145 | },
146 | };
147 | let keyboard;
148 | Enzyme.mount(, {
149 | attachTo: document.body.querySelector("#root"),
150 | });
151 |
152 | keyboard.getButtonElement("q").onclick();
153 |
154 | expect(changed).toBeTruthy();
155 | });
156 |
157 | it("Keyboard will have default theme", () => {
158 | setDOM("container");
159 |
160 | let changed = false;
161 |
162 | const props = {
163 | keyboardRef: (r) => (keyboard = r),
164 | onChangeAll: () => {
165 | changed = true;
166 | },
167 | };
168 | let keyboard;
169 | Enzyme.mount(, {
170 | attachTo: document.body.querySelector("#root"),
171 | });
172 |
173 | keyboard.getButtonElement("q").onclick();
174 |
175 | expect(changed).toBeTruthy();
176 | });
177 |
--------------------------------------------------------------------------------
/src/lib/index.modern.ts:
--------------------------------------------------------------------------------
1 | import KeyboardReact from "./components/KeyboardModern";
2 | export * from "./interfaces.d";
3 | export { KeyboardReact };
4 | export default KeyboardReact;
5 |
--------------------------------------------------------------------------------
/src/lib/index.ts:
--------------------------------------------------------------------------------
1 | import "./polyfills";
2 | import KeyboardReact from "./components/Keyboard";
3 | export * from "./interfaces.d";
4 | export { KeyboardReact };
5 | export default KeyboardReact;
6 |
--------------------------------------------------------------------------------
/src/lib/interfaces.d.ts:
--------------------------------------------------------------------------------
1 |
2 | export interface KeyboardReactInterface extends SimpleKeyboard {
3 | options: SimpleKeyboard["options"] & {
4 | // eslint-disable-next-line no-unused-vars
5 | keyboardRef?: (r: any) => void;
6 | };
7 | }
8 | // Generated by dts-bundle v0.7.3
9 | /**
10 | * Root class for simple-keyboard.
11 | * This class:
12 | * - Parses the options
13 | * - Renders the rows and buttons
14 | * - Handles button functionality
15 | */
16 | export interface SimpleKeyboard {
17 | input: KeyboardInput;
18 | options: KeyboardOptions;
19 | utilities: Utilities;
20 | caretPosition: number | null;
21 | caretPositionEnd: number | null;
22 | keyboardDOM: KeyboardElement;
23 | keyboardPluginClasses: string;
24 | keyboardDOMClass: string;
25 | buttonElements: KeyboardButtonElements;
26 | currentInstanceName: string;
27 | allKeyboardInstances: {
28 | [key: string]: SimpleKeyboard;
29 | };
30 | keyboardInstanceNames: string[];
31 | isFirstKeyboardInstance: boolean;
32 | physicalKeyboard: PhysicalKeyboard;
33 | modules: {
34 | [key: string]: any;
35 | };
36 | activeButtonClass: string;
37 | holdInteractionTimeout: number;
38 | holdTimeout: number;
39 | isMouseHold: boolean;
40 | initialized: boolean;
41 | candidateBox: CandidateBox | null;
42 | keyboardRowsDOM: KeyboardElement;
43 | defaultName: string;
44 | activeInputElement: HTMLInputElement | HTMLTextAreaElement | null;
45 | /**
46 | * Creates an instance of SimpleKeyboard
47 | * @param {Array} selectorOrOptions If first parameter is a string, it is considered the container class. The second parameter is then considered the options object. If first parameter is an object, it is considered the options object.
48 | */
49 | constructor: (selectorOrOptions?: string | HTMLDivElement | KeyboardOptions, keyboardOptions?: KeyboardOptions) => any
50 | /**
51 | * parseParams
52 | */
53 | handleParams: (selectorOrOptions?: string | HTMLDivElement | KeyboardOptions, keyboardOptions?: KeyboardOptions) => {
54 | keyboardDOMClass: string;
55 | keyboardDOM: KeyboardElement;
56 | options: Partial;
57 | };
58 | /**
59 | * Getters
60 | */
61 | getOptions: () => KeyboardOptions;
62 | getCaretPosition: () => number | null;
63 | getCaretPositionEnd: () => number | null;
64 | /**
65 | * Changes the internal caret position
66 | * @param {number} position The caret's start position
67 | * @param {number} positionEnd The caret's end position
68 | */
69 | setCaretPosition(position: number | null, endPosition?: number | null): void;
70 | /**
71 | * Retrieve the candidates for a given input
72 | * @param input The input string to check
73 | */
74 | getInputCandidates(input: string): {
75 | candidateKey: string;
76 | candidateValue: string;
77 | } | Record;
78 | /**
79 | * Shows a suggestion box with a list of candidate words
80 | * @param candidates The chosen candidates string as defined in the layoutCandidates option
81 | * @param targetElement The element next to which the candidates box will be shown
82 | */
83 | showCandidatesBox(candidateKey: string, candidateValue: string, targetElement: KeyboardElement): void;
84 | /**
85 | * Handles clicks made to keyboard buttons
86 | * @param {string} button The button's layout name.
87 | */
88 | handleButtonClicked(button: string, e?: KeyboardHandlerEvent): void;
89 | /**
90 | * Get mouse hold state
91 | */
92 | getMouseHold(): boolean;
93 | /**
94 | * Mark mouse hold state as set
95 | */
96 | setMouseHold(value: boolean): void;
97 | /**
98 | * Handles button mousedown
99 | */
100 | handleButtonMouseDown(button: string, e: KeyboardHandlerEvent): void;
101 | /**
102 | * Handles button mouseup
103 | */
104 | handleButtonMouseUp(button?: string, e?: KeyboardHandlerEvent): void;
105 | /**
106 | * Handles container mousedown
107 | */
108 | handleKeyboardContainerMouseDown(e: KeyboardHandlerEvent): void;
109 | /**
110 | * Handles button hold
111 | */
112 | handleButtonHold(button: string): void;
113 | /**
114 | * Send a command to all simple-keyboard instances (if you have several instances).
115 | */
116 | syncInstanceInputs(): void;
117 | /**
118 | * Clear the keyboard’s input.
119 | * @param {string} [inputName] optional - the internal input to select
120 | */
121 | clearInput(inputName?: string): void;
122 | /**
123 | * Get the keyboard’s input (You can also get it from the onChange prop).
124 | * @param {string} [inputName] optional - the internal input to select
125 | */
126 | getInput(inputName?: string, skipSync?: boolean): string;
127 | /**
128 | * Get all simple-keyboard inputs
129 | */
130 | getAllInputs(): KeyboardInput;
131 | /**
132 | * Set the keyboard’s input.
133 | * @param {string} input the input value
134 | * @param {string} inputName optional - the internal input to select
135 | */
136 | setInput(input: string, inputName?: string, skipSync?: boolean): void;
137 | /**
138 | * Replace the input object (`keyboard.input`)
139 | * @param {object} inputObj The input object
140 | */
141 | replaceInput(inputObj: KeyboardInput): void;
142 | /**
143 | * Set new option or modify existing ones after initialization.
144 | * @param {object} options The options to set
145 | */
146 | setOptions(options?: {}): void;
147 | /**
148 | * Detecting changes to non-function options
149 | * This allows us to ascertain whether a button re-render is needed
150 | */
151 | changedOptions(newOptions: Partial): string[];
152 | /**
153 | * Executing actions depending on changed options
154 | * @param {object} options The options to set
155 | */
156 | onSetOptions(changedOptions?: string[]): void;
157 | /**
158 | * Remove all keyboard rows and reset keyboard values.
159 | * Used internally between re-renders.
160 | */
161 | resetRows(): void;
162 | /**
163 | * Send a command to all simple-keyboard instances at once (if you have multiple instances).
164 | * @param {function(instance: object, key: string)} callback Function to run on every instance
165 | */
166 | dispatch(callback: (instance: SimpleKeyboard, key?: string) => void): void;
167 | /**
168 | * Adds/Modifies an entry to the `buttonTheme`. Basically a way to add a class to a button.
169 | * @param {string} buttons List of buttons to select (separated by a space).
170 | * @param {string} className Classes to give to the selected buttons (separated by space).
171 | */
172 | addButtonTheme(buttons: string, className: string): void;
173 | /**
174 | * Removes/Amends an entry to the `buttonTheme`. Basically a way to remove a class previously added to a button through buttonTheme or addButtonTheme.
175 | * @param {string} buttons List of buttons to select (separated by a space).
176 | * @param {string} className Classes to give to the selected buttons (separated by space).
177 | */
178 | removeButtonTheme(buttons: string, className: string): void;
179 | /**
180 | * Get the DOM Element of a button. If there are several buttons with the same name, an array of the DOM Elements is returned.
181 | * @param {string} button The button layout name to select
182 | */
183 | getButtonElement(button: string): KeyboardElement | KeyboardElement[] | undefined;
184 | /**
185 | * This handles the "inputPattern" option
186 | * by checking if the provided inputPattern passes
187 | */
188 | inputPatternIsValid(inputVal: string): boolean;
189 | /**
190 | * Handles simple-keyboard event listeners
191 | */
192 | setEventListeners(): void;
193 | /**
194 | * Event Handler: KeyUp
195 | */
196 | handleKeyUp(event: KeyboardHandlerEvent): void;
197 | /**
198 | * Event Handler: KeyDown
199 | */
200 | handleKeyDown(event: KeyboardHandlerEvent): void;
201 | /**
202 | * Event Handler: MouseUp
203 | */
204 | handleMouseUp(event: KeyboardHandlerEvent): void;
205 | /**
206 | * Event Handler: TouchEnd
207 | */
208 | handleTouchEnd(event: KeyboardHandlerEvent): void;
209 | /**
210 | * Event Handler: Select
211 | */
212 | handleSelect(event: KeyboardHandlerEvent): void;
213 | /**
214 | * Event Handler: SelectionChange
215 | */
216 | handleSelectionChange(event: KeyboardHandlerEvent): void;
217 | /**
218 | * Called by {@link setEventListeners} when an event that warrants a cursor position update is triggered
219 | */
220 | caretEventHandler(event: KeyboardHandlerEvent): void;
221 | /**
222 | * Execute an operation on each button
223 | */
224 | recurseButtons(fn: any): void;
225 | /**
226 | * Destroy keyboard listeners and DOM elements
227 | */
228 | destroy(): void;
229 | /**
230 | * Process buttonTheme option
231 | */
232 | getButtonThemeClasses(button: string): string[];
233 | /**
234 | * Process buttonAttributes option
235 | */
236 | setDOMButtonAttributes(button: string, callback: any): void;
237 | onTouchDeviceDetected(): void;
238 | /**
239 | * Disabling contextual window for hg-button
240 | */
241 | disableContextualWindow(): void;
242 | /**
243 | * Process autoTouchEvents option
244 | */
245 | processAutoTouchEvents(): void;
246 | /**
247 | * Executes the callback function once simple-keyboard is rendered for the first time (on initialization).
248 | */
249 | onInit(): void;
250 | /**
251 | * Executes the callback function before a simple-keyboard render.
252 | */
253 | beforeFirstRender(): void;
254 | /**
255 | * Executes the callback function before a simple-keyboard render.
256 | */
257 | beforeRender(): void;
258 | /**
259 | * Executes the callback function every time simple-keyboard is rendered (e.g: when you change layouts).
260 | */
261 | onRender(): void;
262 | /**
263 | * Executes the callback function once all modules have been loaded
264 | */
265 | onModulesLoaded(): void;
266 | /**
267 | * Register module
268 | */
269 | registerModule: (name: string, initCallback: any) => void;
270 | /**
271 | * Load modules
272 | */
273 | loadModules(): void;
274 | /**
275 | * Get module prop
276 | */
277 | getModuleProp(name: string, prop: string): any;
278 | /**
279 | * getModulesList
280 | */
281 | getModulesList(): string[];
282 | /**
283 | * Parse Row DOM containers
284 | */
285 | parseRowDOMContainers(rowDOM: HTMLDivElement, rowIndex: number, containerStartIndexes: number[], containerEndIndexes: number[]): HTMLDivElement;
286 | /**
287 | * getKeyboardClassString
288 | */
289 | getKeyboardClassString: (...baseDOMClasses: any[]) => string;
290 | /**
291 | * Renders rows and buttons as per options
292 | */
293 | render(): void;
294 | }
295 | export interface SKWindow extends Window {
296 | SimpleKeyboardInstances?: any;
297 | }
298 | export interface KeyboardLayoutObject {
299 | [key: string]: string[];
300 | }
301 | export type KeyboardButtonTheme = {
302 | class: string;
303 | buttons: string;
304 | } | null;
305 | export interface KeyboardButtonAttributes {
306 | attribute: string;
307 | value: string;
308 | buttons: string;
309 | }
310 | export interface KeyboardInput {
311 | [key: string]: string;
312 | }
313 | export type CandidateBoxParams = {
314 | utilities: Utilities;
315 | options: KeyboardOptions;
316 | };
317 | export type CandidateBoxShowParams = {
318 | candidateValue: string;
319 | targetElement: KeyboardElement;
320 | onSelect: (selectedCandidate: string, e: MouseEvent) => void;
321 | };
322 | export type CandidateBoxRenderParams = {
323 | candidateListPages: string[][];
324 | targetElement: KeyboardElement;
325 | pageIndex: number;
326 | nbPages: number;
327 | onItemSelected: (selectedCandidate: string, e: MouseEvent) => void;
328 | };
329 | export type KeyboardElement = HTMLDivElement | HTMLButtonElement;
330 | export type KeyboardHandlerEvent = any;
331 | export interface KeyboardButtonElements {
332 | [key: string]: KeyboardElement[];
333 | }
334 | export interface UtilitiesParams {
335 | getOptions: () => KeyboardOptions;
336 | getCaretPosition: () => number | null;
337 | getCaretPositionEnd: () => number | null;
338 | dispatch: any;
339 | }
340 | export interface PhysicalKeyboardParams {
341 | getOptions: () => KeyboardOptions;
342 | dispatch: any;
343 | }
344 | export interface KeyboardOptions {
345 | /**
346 | * Modify the keyboard layout.
347 | */
348 | layout?: KeyboardLayoutObject;
349 | /**
350 | * Specifies which layout should be used.
351 | */
352 | layoutName?: string;
353 | /**
354 | * Replaces variable buttons (such as `{bksp}`) with a human-friendly name (e.g.: `backspace`).
355 | */
356 | display?: {
357 | [button: string]: string;
358 | };
359 | /**
360 | * By default, when you set the display property, you replace the default one. This setting merges them instead.
361 | */
362 | mergeDisplay?: boolean;
363 | /**
364 | * A prop to add your own css classes to the keyboard wrapper. You can add multiple classes separated by a space.
365 | */
366 | theme?: string;
367 | /**
368 | * A prop to add your own css classes to one or several buttons.
369 | */
370 | buttonTheme?: KeyboardButtonTheme[];
371 | /**
372 | * A prop to add your own attributes to one or several buttons.
373 | */
374 | buttonAttributes?: KeyboardButtonAttributes[];
375 | /**
376 | * Runs a `console.log` every time a key is pressed. Displays the buttons pressed and the current input.
377 | */
378 | debug?: boolean;
379 | /**
380 | * Specifies whether clicking the "ENTER" button will input a newline (`\n`) or not.
381 | */
382 | newLineOnEnter?: boolean;
383 | /**
384 | * Specifies whether clicking the "TAB" button will input a tab character (`\t`) or not.
385 | */
386 | tabCharOnTab?: boolean;
387 | /**
388 | * Allows you to use a single simple-keyboard instance for several inputs.
389 | */
390 | inputName?: string;
391 | /**
392 | * `number`: Restrains all of simple-keyboard inputs to a certain length. This should be used in addition to the input element’s maxlengthattribute.
393 | *
394 | * `{ [inputName: string]: number }`: Restrains simple-keyboard’s individual inputs to a certain length. This should be used in addition to the input element’s maxlengthattribute.
395 | */
396 | maxLength?: any;
397 | /**
398 | * When set to true, this option synchronizes the internal input of every simple-keyboard instance.
399 | */
400 | syncInstanceInputs?: boolean;
401 | /**
402 | * Enable highlighting of keys pressed on physical keyboard.
403 | */
404 | physicalKeyboardHighlight?: boolean;
405 | /**
406 | * Calls handler for a button highlighted by physicalKeyboardHighlight
407 | * In other words, this calls keyboard.handleButtonClicked(buttonName) on the highlighted button
408 | */
409 | physicalKeyboardHighlightPress?: boolean;
410 | /**
411 | * Trigger click on a button's element when using physicalKeyboardHighlightPress
412 | * In other words, this calls button.click() on the highlighted button
413 | */
414 | physicalKeyboardHighlightPressUseClick?: boolean;
415 | /**
416 | * Whether physicalKeyboardHighlightPress should use pointer events to trigger buttons.
417 | */
418 | physicalKeyboardHighlightPressUsePointerEvents?: boolean;
419 | /**
420 | * Define the text color that the physical keyboard highlighted key should have.
421 | */
422 | physicalKeyboardHighlightTextColor?: string;
423 | /**
424 | * Define the background color that the physical keyboard highlighted key should have.
425 | */
426 | physicalKeyboardHighlightBgColor?: string;
427 | /**
428 | * Whether physicalKeyboardHighlight should use preventDefault to disable default browser actions.
429 | */
430 | physicalKeyboardHighlightPreventDefault?: boolean;
431 | /**
432 | * Calling preventDefault for the mousedown events keeps the focus on the input.
433 | */
434 | preventMouseDownDefault?: boolean;
435 | /**
436 | * Calling preventDefault for the mouseup events.
437 | */
438 | preventMouseUpDefault?: boolean;
439 | /**
440 | * Stops pointer down events on simple-keyboard buttons from bubbling to parent elements.
441 | */
442 | stopMouseDownPropagation?: boolean;
443 | /**
444 | * Stops pointer up events on simple-keyboard buttons from bubbling to parent elements.
445 | */
446 | stopMouseUpPropagation?: boolean;
447 | /**
448 | * Render buttons as a button element instead of a div element.
449 | */
450 | useButtonTag?: boolean;
451 | /**
452 | * A prop to ensure characters are always be added/removed at the end of the string.
453 | */
454 | disableCaretPositioning?: boolean;
455 | /**
456 | * Restrains input(s) change to the defined regular expression pattern.
457 | */
458 | inputPattern?: any;
459 | /**
460 | * Instructs simple-keyboard to use touch events instead of click events.
461 | */
462 | useTouchEvents?: boolean;
463 | /**
464 | * Enable useTouchEvents automatically when touch device is detected.
465 | */
466 | autoUseTouchEvents?: boolean;
467 | /**
468 | * Opt out of PointerEvents handling, falling back to the prior mouse event logic.
469 | */
470 | useMouseEvents?: boolean;
471 | /**
472 | * Disable button hold action.
473 | */
474 | disableButtonHold?: boolean;
475 | /**
476 | * Adds unicode right-to-left control characters to input return values.
477 | */
478 | rtl?: boolean;
479 | /**
480 | * Enable input method editor candidate list support.
481 | */
482 | enableLayoutCandidates?: boolean;
483 | /**
484 | * Character suggestions to be shown on certain key presses
485 | */
486 | layoutCandidates?: {
487 | [key: string]: string;
488 | };
489 | /**
490 | * Exclude buttons from layout
491 | */
492 | excludeFromLayout?: {
493 | [key: string]: string[];
494 | };
495 | /**
496 | * Determines size of layout candidate list
497 | */
498 | layoutCandidatesPageSize?: number;
499 | /**
500 | * Determines whether layout candidate match should be case sensitive.
501 | */
502 | layoutCandidatesCaseSensitiveMatch?: boolean;
503 | /**
504 | * Disables the automatic normalization for selected layout candidates
505 | */
506 | disableCandidateNormalization?: boolean;
507 | /**
508 | * Enables onKeyPress triggering for layoutCandidate items
509 | */
510 | enableLayoutCandidatesKeyPress?: boolean;
511 | /**
512 | * Updates caret when selectionchange event is fired
513 | */
514 | updateCaretOnSelectionChange?: boolean;
515 | /**
516 | * When useMouseEvents is enabled, this option allows you to trigger a button click event on mousedown
517 | */
518 | clickOnMouseDown?: boolean;
519 | /**
520 | * Executes the callback function every time simple-keyboard is rendered (e.g: when you change layouts).
521 | */
522 | onRender?: (instance: SimpleKeyboard) => void;
523 | /**
524 | * Executes the callback function once simple-keyboard is rendered for the first time (on initialization).
525 | */
526 | onInit?: (instance: SimpleKeyboard) => void;
527 | /**
528 | * Retrieves the current input
529 | */
530 | onChange?: (input: string, e?: MouseEvent) => any;
531 | /**
532 | * Retrieves all inputs
533 | */
534 | onChangeAll?: (inputObj: KeyboardInput, e?: MouseEvent) => any;
535 | /**
536 | * Retrieves the pressed key
537 | */
538 | onKeyPress?: (button: string, e?: MouseEvent) => any;
539 | /**
540 | * Retrieves the released key
541 | */
542 | onKeyReleased?: (button: string, e?: MouseEvent) => any;
543 | /**
544 | * Executes the callback function before an input is about to be updated
545 | */
546 | beforeInputUpdate?: (instance: SimpleKeyboard) => void;
547 | /**
548 | * Module options can have any format
549 | */
550 | [name: string]: any;
551 | }
552 | /**
553 | * Physical Keyboard Service
554 | */
555 | export interface PhysicalKeyboard {
556 | getOptions: () => KeyboardOptions;
557 | dispatch: any;
558 | /**
559 | * Creates an instance of the PhysicalKeyboard service
560 | */
561 | constructor: ({ dispatch, getOptions }: PhysicalKeyboardParams) => any
562 | handleHighlightKeyDown(e: KeyboardEvent): void;
563 | handleHighlightKeyUp(e: KeyboardEvent): void;
564 | /**
565 | * Transforms a KeyboardEvent's "key.code" string into a simple-keyboard layout format
566 | * @param {object} e The KeyboardEvent
567 | */
568 | getSimpleKeyboardLayoutKey(e: KeyboardEvent): string;
569 | /**
570 | * Retrieve key from keyCode
571 | */
572 | keyCodeToKey(keyCode: number): string;
573 | isModifierKey: (e: KeyboardEvent) => boolean;
574 | }
575 | /**
576 | * Utility Service
577 | */
578 | export interface Utilities {
579 | getOptions: () => KeyboardOptions;
580 | getCaretPosition: () => number | null;
581 | getCaretPositionEnd: () => number | null;
582 | dispatch: any;
583 | maxLengthReached: boolean;
584 | /**
585 | * Creates an instance of the Utility service
586 | */
587 | constructor: ({ getOptions, getCaretPosition, getCaretPositionEnd, dispatch, }: UtilitiesParams) => any
588 | /**
589 | * Retrieve button type
590 | *
591 | * @param {string} button The button's layout name
592 | * @return {string} The button type
593 | */
594 | getButtonType(button: string): string;
595 | /**
596 | * Adds default classes to a given button
597 | *
598 | * @param {string} button The button's layout name
599 | * @return {string} The classes to be added to the button
600 | */
601 | getButtonClass(button: string): string;
602 | /**
603 | * Default button display labels
604 | */
605 | getDefaultDiplay(): {
606 | "{bksp}": string;
607 | "{backspace}": string;
608 | "{enter}": string;
609 | "{shift}": string;
610 | "{shiftleft}": string;
611 | "{shiftright}": string;
612 | "{alt}": string;
613 | "{s}": string;
614 | "{tab}": string;
615 | "{lock}": string;
616 | "{capslock}": string;
617 | "{accept}": string;
618 | "{space}": string;
619 | "{//}": string;
620 | "{esc}": string;
621 | "{escape}": string;
622 | "{f1}": string;
623 | "{f2}": string;
624 | "{f3}": string;
625 | "{f4}": string;
626 | "{f5}": string;
627 | "{f6}": string;
628 | "{f7}": string;
629 | "{f8}": string;
630 | "{f9}": string;
631 | "{f10}": string;
632 | "{f11}": string;
633 | "{f12}": string;
634 | "{numpaddivide}": string;
635 | "{numlock}": string;
636 | "{arrowup}": string;
637 | "{arrowleft}": string;
638 | "{arrowdown}": string;
639 | "{arrowright}": string;
640 | "{prtscr}": string;
641 | "{scrolllock}": string;
642 | "{pause}": string;
643 | "{insert}": string;
644 | "{home}": string;
645 | "{pageup}": string;
646 | "{delete}": string;
647 | "{forwarddelete}": string;
648 | "{end}": string;
649 | "{pagedown}": string;
650 | "{numpadmultiply}": string;
651 | "{numpadsubtract}": string;
652 | "{numpadadd}": string;
653 | "{numpadenter}": string;
654 | "{period}": string;
655 | "{numpaddecimal}": string;
656 | "{numpad0}": string;
657 | "{numpad1}": string;
658 | "{numpad2}": string;
659 | "{numpad3}": string;
660 | "{numpad4}": string;
661 | "{numpad5}": string;
662 | "{numpad6}": string;
663 | "{numpad7}": string;
664 | "{numpad8}": string;
665 | "{numpad9}": string;
666 | };
667 | /**
668 | * Returns the display (label) name for a given button
669 | *
670 | * @param {string} button The button's layout name
671 | * @param {object} display The provided display option
672 | * @param {boolean} mergeDisplay Whether the provided param value should be merged with the default one.
673 | */
674 | getButtonDisplayName(button: string, display: KeyboardOptions["display"], mergeDisplay?: boolean): string;
675 | /**
676 | * Returns the updated input resulting from clicking a given button
677 | *
678 | * @param {string} button The button's layout name
679 | * @param {string} input The input string
680 | * @param {number} caretPos The cursor's current position
681 | * @param {number} caretPosEnd The cursor's current end position
682 | * @param {boolean} moveCaret Whether to update simple-keyboard's cursor
683 | */
684 | getUpdatedInput(button: string, input: string, caretPos: any, caretPosEnd?: any, moveCaret?: boolean): string;
685 | /**
686 | * Moves the cursor position by a given amount
687 | *
688 | * @param {number} length Represents by how many characters the input should be moved
689 | * @param {boolean} minus Whether the cursor should be moved to the left or not.
690 | */
691 | updateCaretPos(length: number, minus?: boolean): void;
692 | /**
693 | * Action method of updateCaretPos
694 | *
695 | * @param {number} length Represents by how many characters the input should be moved
696 | * @param {boolean} minus Whether the cursor should be moved to the left or not.
697 | */
698 | updateCaretPosAction(length: number, minus?: boolean): number | null;
699 | /**
700 | * Adds a string to the input at a given position
701 | *
702 | * @param {string} source The source input
703 | * @param {string} str The string to add
704 | * @param {number} position The (cursor) position where the string should be added
705 | * @param {boolean} moveCaret Whether to update simple-keyboard's cursor
706 | */
707 | addStringAt(source: string, str: string, position?: number, positionEnd?: number, moveCaret?: boolean): string;
708 | /**
709 | * Check whether the button is a standard button
710 | */
711 | isStandardButton: (button: string) => boolean | "";
712 | /**
713 | * Removes an amount of characters before a given position
714 | *
715 | * @param {string} source The source input
716 | * @param {number} position The (cursor) position from where the characters should be removed
717 | * @param {boolean} moveCaret Whether to update simple-keyboard's cursor
718 | */
719 | removeAt(source: string, position?: number, positionEnd?: number, moveCaret?: boolean): string;
720 | /**
721 | * Removes an amount of characters after a given position
722 | *
723 | * @param {string} source The source input
724 | * @param {number} position The (cursor) position from where the characters should be removed
725 | */
726 | removeForwardsAt(source: string, position?: number, positionEnd?: number, moveCaret?: boolean): string;
727 | /**
728 | * Determines whether the maxLength has been reached. This function is called when the maxLength option it set.
729 | *
730 | * @param {object} inputObj
731 | * @param {string} updatedInput
732 | */
733 | handleMaxLength(inputObj: KeyboardInput, updatedInput: string): boolean | undefined;
734 | /**
735 | * Gets the current value of maxLengthReached
736 | */
737 | isMaxLengthReached(): boolean;
738 | /**
739 | * Determines whether a touch device is being used
740 | */
741 | isTouchDevice(): number | true;
742 | /**
743 | * Determines whether pointer events are supported
744 | */
745 | pointerEventsSupported(): boolean;
746 | /**
747 | * Bind all methods in a given class
748 | */
749 | /**
750 | * Transforms an arbitrary string to camelCase
751 | *
752 | * @param {string} str The string to transform.
753 | */
754 | camelCase(str: string): string;
755 | /**
756 | * Split array into chunks
757 | */
758 | chunkArray(arr: T[], size: number): T[][];
759 | /**
760 | * Escape regex input
761 | */
762 | escapeRegex(str: string): string;
763 | /**
764 | * Calculate caret position offset when using rtl option
765 | */
766 | getRtlOffset(index: number, input: string): number;
767 | /**
768 | * Reusable empty function
769 | */
770 | /**
771 | * Check if a function is a constructor
772 | */
773 | isConstructor(f: any): boolean;
774 | }
775 | export interface CandidateBox {
776 | utilities: Utilities;
777 | options: KeyboardOptions;
778 | candidateBoxElement: HTMLDivElement;
779 | pageIndex: number;
780 | pageSize: number;
781 | constructor: ({ utilities, options }: CandidateBoxParams) => any
782 | destroy(): void;
783 | show({ candidateValue, targetElement, onSelect, }: CandidateBoxShowParams): void;
784 | renderPage({ candidateListPages, targetElement, pageIndex, nbPages, onItemSelected, }: CandidateBoxRenderParams): void;
785 | }
786 |
--------------------------------------------------------------------------------
/src/lib/polyfills.js:
--------------------------------------------------------------------------------
1 | // element.remove() polyfill
2 | if (typeof Element !== "undefined" && !("remove" in Element.prototype)) {
3 | Element.prototype["remove"] = function () {
4 | if (this.parentNode) {
5 | this.parentNode.removeChild(this);
6 | }
7 | };
8 | }
9 |
10 | /*
11 | * classList.js: Cross-browser full element.classList implementation.
12 | * 1.2.20171210
13 | *
14 | * By Eli Grey, http://eligrey.com
15 | * License: Dedicated to the public domain.
16 | * See https://github.com/eligrey/classList.js/blob/master/LICENSE.md
17 | */
18 | if (typeof self !== "undefined" && "document" in self) {
19 | // Full polyfill for browsers with no classList support
20 | // Including IE < Edge missing SVGElement.classList
21 | if (
22 | !("classList" in document.createElement("_")) ||
23 | (document.createElementNS &&
24 | !(
25 | "classList" in
26 | document.createElementNS("http://www.w3.org/2000/svg", "g")
27 | ))
28 | ) {
29 | (function (view) {
30 | "use strict";
31 |
32 | if (!("Element" in view)) return;
33 |
34 | var classListProp = "classList",
35 | protoProp = "prototype",
36 | elemCtrProto = view.Element[protoProp],
37 | objCtr = Object,
38 | strTrim =
39 | String[protoProp].trim ||
40 | function () {
41 | return this.replace(/^\s+|\s+$/g, "");
42 | },
43 | arrIndexOf =
44 | Array[protoProp].indexOf ||
45 | function (item) {
46 | var i = 0,
47 | len = this.length;
48 | for (; i < len; i++) {
49 | if (i in this && this[i] === item) {
50 | return i;
51 | }
52 | }
53 | return -1;
54 | },
55 | // Vendors: please allow content code to instantiate DOMExceptions
56 | DOMEx = function (type, message) {
57 | this.name = type;
58 | this.code = DOMException[type];
59 | this.message = message;
60 | },
61 | checkTokenAndGetIndex = function (classList, token) {
62 | if (token === "") {
63 | throw new DOMEx("SYNTAX_ERR", "The token must not be empty.");
64 | }
65 | if (/\s/.test(token)) {
66 | throw new DOMEx(
67 | "INVALID_CHARACTER_ERR",
68 | "The token must not contain space characters."
69 | );
70 | }
71 | return arrIndexOf.call(classList, token);
72 | },
73 | ClassList = function (elem) {
74 | var trimmedClasses = strTrim.call(elem.getAttribute("class") || ""),
75 | classes = trimmedClasses ? trimmedClasses.split(/\s+/) : [],
76 | i = 0,
77 | len = classes.length;
78 | for (; i < len; i++) {
79 | this.push(classes[i]);
80 | }
81 | this._updateClassName = function () {
82 | elem.setAttribute("class", this.toString());
83 | };
84 | },
85 | classListProto = (ClassList[protoProp] = []),
86 | classListGetter = function () {
87 | return new ClassList(this);
88 | };
89 | // Most DOMException implementations don't allow calling DOMException's toString()
90 | // on non-DOMExceptions. Error's toString() is sufficient here.
91 | DOMEx[protoProp] = Error[protoProp];
92 | classListProto.item = function (i) {
93 | return this[i] || null;
94 | };
95 | classListProto.contains = function (token) {
96 | return ~checkTokenAndGetIndex(this, token + "");
97 | };
98 | classListProto.add = function () {
99 | var tokens = arguments,
100 | i = 0,
101 | l = tokens.length,
102 | token,
103 | updated = false;
104 | do {
105 | token = tokens[i] + "";
106 | if (!~checkTokenAndGetIndex(this, token)) {
107 | this.push(token);
108 | updated = true;
109 | }
110 | } while (++i < l);
111 |
112 | if (updated) {
113 | this._updateClassName();
114 | }
115 | };
116 | classListProto.remove = function () {
117 | var tokens = arguments,
118 | i = 0,
119 | l = tokens.length,
120 | token,
121 | updated = false,
122 | index;
123 | do {
124 | token = tokens[i] + "";
125 | index = checkTokenAndGetIndex(this, token);
126 | while (~index) {
127 | this.splice(index, 1);
128 | updated = true;
129 | index = checkTokenAndGetIndex(this, token);
130 | }
131 | } while (++i < l);
132 |
133 | if (updated) {
134 | this._updateClassName();
135 | }
136 | };
137 | classListProto.toggle = function (token, force) {
138 | var result = this.contains(token),
139 | method = result
140 | ? force !== true && "remove"
141 | : force !== false && "add";
142 | if (method) {
143 | this[method](token);
144 | }
145 |
146 | if (force === true || force === false) {
147 | return force;
148 | } else {
149 | return !result;
150 | }
151 | };
152 | classListProto.replace = function (token, replacement_token) {
153 | var index = checkTokenAndGetIndex(token + "");
154 | if (~index) {
155 | this.splice(index, 1, replacement_token);
156 | this._updateClassName();
157 | }
158 | };
159 | classListProto.toString = function () {
160 | return this.join(" ");
161 | };
162 |
163 | if (objCtr.defineProperty) {
164 | var classListPropDesc = {
165 | get: classListGetter,
166 | enumerable: true,
167 | configurable: true,
168 | };
169 | try {
170 | objCtr.defineProperty(elemCtrProto, classListProp, classListPropDesc);
171 | } catch (ex) {
172 | // IE 8 doesn't support enumerable:true
173 | // adding undefined to fight this issue https://github.com/eligrey/classList.js/issues/36
174 | // modernie IE8-MSW7 machine has IE8 8.0.6001.18702 and is affected
175 | if (ex.number === undefined || ex.number === -0x7ff5ec54) {
176 | classListPropDesc.enumerable = false;
177 | objCtr.defineProperty(
178 | elemCtrProto,
179 | classListProp,
180 | classListPropDesc
181 | );
182 | }
183 | }
184 | } else if (objCtr[protoProp].__defineGetter__) {
185 | elemCtrProto.__defineGetter__(classListProp, classListGetter);
186 | }
187 | })(self);
188 | }
189 |
190 | // There is full or partial native classList support, so just check if we need
191 | // to normalize the add/remove and toggle APIs.
192 |
193 | (function () {
194 | "use strict";
195 |
196 | var testElement = document.createElement("_");
197 |
198 | testElement.classList.add("c1", "c2");
199 |
200 | // Polyfill for IE 10/11 and Firefox <26, where classList.add and
201 | // classList.remove exist but support only one argument at a time.
202 | if (!testElement.classList.contains("c2")) {
203 | var createMethod = function (method) {
204 | var original = DOMTokenList.prototype[method];
205 |
206 | DOMTokenList.prototype[method] = function (token) {
207 | var i,
208 | len = arguments.length;
209 |
210 | for (i = 0; i < len; i++) {
211 | token = arguments[i];
212 | original.call(this, token);
213 | }
214 | };
215 | };
216 | createMethod("add");
217 | createMethod("remove");
218 | }
219 |
220 | testElement.classList.toggle("c3", false);
221 |
222 | // Polyfill for IE 10 and Firefox <24, where classList.toggle does not
223 | // support the second argument.
224 | if (testElement.classList.contains("c3")) {
225 | var _toggle = DOMTokenList.prototype.toggle;
226 |
227 | DOMTokenList.prototype.toggle = function (token, force) {
228 | if (1 in arguments && !this.contains(token) === !force) {
229 | return force;
230 | } else {
231 | return _toggle.call(this, token);
232 | }
233 | };
234 | }
235 |
236 | // replace() polyfill
237 | if (!("replace" in document.createElement("_").classList)) {
238 | DOMTokenList.prototype.replace = function (token, replacement_token) {
239 | var tokens = this.toString().split(" "),
240 | index = tokens.indexOf(token + "");
241 | if (~index) {
242 | tokens = tokens.slice(index);
243 | this.remove.apply(this, tokens);
244 | this.add(replacement_token);
245 | this.add.apply(this, tokens.slice(1));
246 | }
247 | };
248 | }
249 |
250 | testElement = null;
251 | })();
252 | }
253 |
--------------------------------------------------------------------------------
/src/lib/services/Utilities.ts:
--------------------------------------------------------------------------------
1 | import { KeyboardReactInterface } from "../interfaces";
2 |
3 | export const parseProps = (props: KeyboardReactInterface["options"]) => ({
4 | ...props,
5 | theme: `simple-keyboard ${props.theme || "hg-theme-default"}`,
6 | });
7 |
8 | const cleanProps = (sourceObj: KeyboardReactInterface["options"]) => ({
9 | ...sourceObj,
10 | keyboardRef: null,
11 | });
12 |
13 | export const changedProps = (
14 | prevProps: KeyboardReactInterface["options"],
15 | props: KeyboardReactInterface["options"]
16 | ) => {
17 | const cleanedProps = cleanProps(props);
18 | const cleanedPrevProps = cleanProps(prevProps);
19 |
20 | return Object.keys(cleanedProps).filter(
21 | (propName) => cleanedProps[propName] !== cleanedPrevProps[propName]
22 | );
23 | };
24 |
--------------------------------------------------------------------------------
/src/utils/BaseDOM.js:
--------------------------------------------------------------------------------
1 | import { setDOM } from './TestUtility';
2 |
3 | setDOM('container', 'root');
4 |
--------------------------------------------------------------------------------
/src/utils/TestUtility.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Test Utility Functions
3 | */
4 | /**
5 | * Sets a basic DOM structure to test in
6 | */
7 | export const setDOM = (divClass) => {
8 | clearDOM();
9 | const wrapperDOM = document.createElement('div');
10 | wrapperDOM.setAttribute("id", "root");
11 |
12 | const keyboardDOM = document.createElement('div');
13 | keyboardDOM.className = divClass || "simple-keyboard";
14 |
15 | wrapperDOM.appendChild(keyboardDOM);
16 | document.body.appendChild(wrapperDOM);
17 | }
18 |
19 | /**
20 | * Clears DOM structure
21 | */
22 | export const clearDOM = () => {
23 | document.body.innerHTML = "";
24 | }
25 |
26 | /**
27 | * Trigger pointerup
28 | */
29 | export const triggerDocumentPointerUp = (e = {}) => {
30 | document.dispatchEvent(new MouseEvent('mouseup', e));
31 | };
32 |
33 | /**
34 | * Trigger pointerdown
35 | */
36 | export const triggerDocumentPointerDown = (e = {}) => {
37 | document.dispatchEvent(new MouseEvent('mousedown', e));
38 | };
39 |
40 | /**
41 | * Test if standard buttons respect maxLength and do input a value
42 | */
43 | export const testLayoutStdButtons = (keyboard) => {
44 | let stdBtnCount = 0;
45 | let fullInput = '';
46 |
47 | keyboard.recurseButtons((button) => {
48 | const label = button.getAttribute("data-skbtn");
49 |
50 | if(label.includes("{"))
51 | return false;
52 |
53 | // Click all standard buttons, respects maxLength
54 | button.onclick();
55 |
56 | // Recording fullInput, bypasses maxLength
57 | fullInput = keyboard.utilities.getUpdatedInput(label, fullInput);
58 |
59 | stdBtnCount += label.length;
60 | });
61 |
62 | /**
63 | * Check if maxLength is respected
64 | */
65 | if(
66 | (
67 | typeof keyboard.options.maxLength === "object" &&
68 | keyboard.getInput().length !== keyboard.options.maxLength[keyboard.options.layoutName]
69 | ) ||
70 | (
71 | typeof keyboard.options.maxLength !== "object" &&
72 | keyboard.getInput().length !== keyboard.options.maxLength
73 | )
74 | )
75 | throw new Error("MAX_LENGTH_ISSUE");
76 | else
77 | console.log("MAX_LENGTH PASSED:", keyboard.options.layoutName, keyboard.getInput().length, keyboard.options.maxLength);
78 |
79 | /**
80 | * Check if all standard buttons are inputting something
81 | * (Regardless of maxLength)
82 | */
83 | if(stdBtnCount !== fullInput.length)
84 | throw new Error("STANDARD_BUTTONS_ISSUE");
85 | else
86 | console.log("STANDARD_BUTTONS PASSED:", keyboard.options.layoutName, stdBtnCount, fullInput.length);
87 | }
88 |
89 | /**
90 | * Test if function buttons are interactive (have an onclick)
91 | */
92 | export const testLayoutFctButtons = (keyboard, callback) => {
93 | let fctBtnCount = 0;
94 | let fctBtnHasOnclickCount = 0;
95 |
96 | keyboard.recurseButtons((button) => {
97 | const label = button.getAttribute("data-skbtn");
98 |
99 | if(!label.includes("{") && !label.includes("}"))
100 | return false;
101 |
102 | fctBtnCount++;
103 |
104 | if(button.onclick){
105 | button.onclick();
106 | fctBtnHasOnclickCount++;
107 | }
108 |
109 | callback(fctBtnCount, fctBtnHasOnclickCount);
110 | });
111 | }
112 |
113 | /**
114 | * Remove RTL control chars
115 | */
116 | export const removeRTLControls = (input) => {
117 | return input.replace("\u202B", "").replace("\u202C", "");
118 | }
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "noImplicitAny": true,
4 | "outDir": "build",
5 | "module": "esnext",
6 | "target": "es5",
7 | "allowJs": true,
8 | "sourceMap": true,
9 | "declaration": true,
10 | "emitDeclarationOnly": true,
11 | "suppressImplicitAnyIndexErrors": true,
12 | "lib": ["es2020", "dom"],
13 | "moduleResolution": "node",
14 | "downlevelIteration": true,
15 | "jsx": "react",
16 | "strict": true
17 | },
18 | "include": ["src/lib"],
19 | "exclude": ["src/lib/**/tests"],
20 | }
--------------------------------------------------------------------------------
/webpack.config.demo.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const MiniCssExtractPlugin = require("mini-css-extract-plugin");
3 | const HtmlWebpackPlugin = require('html-webpack-plugin');
4 |
5 | module.exports = {
6 | mode: "development",
7 | devtool: 'cheap-module-source-map',
8 | entry: './src/demo/index.tsx',
9 | output: {
10 | path: path.resolve(__dirname, 'demo'),
11 | filename: 'index.js'
12 | },
13 | optimization: {
14 | minimize: false,
15 | },
16 | devServer: {
17 | open: true,
18 | hot: true,
19 | host: "localhost",
20 | port: 9000
21 | },
22 | module: {
23 | rules: [
24 | {
25 | test: /\.(m|j|t)sx?$/,
26 | exclude: /(node_modules|bower_components)/,
27 | use: {
28 | loader: 'babel-loader'
29 | }
30 | },
31 | {
32 | test: /\.(sa|sc|c)ss$/,
33 | use: [
34 | MiniCssExtractPlugin.loader,
35 | { loader: "css-loader", options: { sourceMap: true } },
36 | ],
37 | },
38 | {
39 | test: /\.(png|jpe?g|gif|svg|eot|ttf|woff|woff2)$/,
40 | use: ['url-loader'],
41 | }
42 | ]
43 | },
44 | plugins: [
45 | new MiniCssExtractPlugin({
46 | filename: 'css/index.css'
47 | }),
48 | new HtmlWebpackPlugin(),
49 | ],
50 | resolve: {
51 | extensions: ['.ts', '.tsx', '.js', '.json']
52 | }
53 | };
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const webpack = require('webpack');
3 | const TerserPlugin = require('terser-webpack-plugin');
4 | const getPackageJson = require('./scripts/getPackageJson');
5 | const MiniCssExtractPlugin = require("mini-css-extract-plugin");
6 | const CopyPlugin = require("copy-webpack-plugin");
7 | const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
8 |
9 | const {
10 | version,
11 | name,
12 | license,
13 | repository,
14 | author
15 | } = getPackageJson('version', 'name', 'license', 'repository', 'author');
16 |
17 | const banner = `
18 | ${name} v${version}
19 | ${repository.url}
20 |
21 | Copyright (c) ${author.replace(/ *<[^)]*> */g, " ")} and project contributors.
22 |
23 | This source code is licensed under the ${license} license found in the
24 | LICENSE file in the root directory of this source tree.
25 | `;
26 |
27 | module.exports = {
28 | mode: "production",
29 | entry: './src/lib/index.ts',
30 | target: 'es5',
31 | output: {
32 | filename: 'index.js',
33 | path: path.resolve(__dirname, 'build'),
34 | library: "ReactSimpleKeyboard",
35 | libraryTarget: 'umd',
36 | clean: true,
37 | globalObject: 'this',
38 | hashFunction: 'xxhash64',
39 | chunkFormat: 'module',
40 | environment: {
41 | arrowFunction: false
42 | }
43 | },
44 | optimization: {
45 | minimize: true,
46 | minimizer: [
47 | new TerserPlugin({
48 | terserOptions: {
49 | format: {
50 | comments: /react-simple-keyboard/i,
51 | },
52 | },
53 | extractComments: false
54 | }),
55 | new CssMinimizerPlugin()
56 | ],
57 | },
58 | devServer: {
59 | open: true,
60 | hot: true,
61 | host: "localhost",
62 | static: path.join(__dirname, 'demo'),
63 | port: 9000
64 | },
65 | externals: {
66 | react: {
67 | root: 'React',
68 | commonjs2: 'react',
69 | commonjs: 'react',
70 | amd: 'react'
71 | },
72 | 'react-dom': {
73 | root: 'ReactDOM',
74 | commonjs2: 'react-dom',
75 | commonjs: 'react-dom',
76 | amd: 'react-dom'
77 | }
78 | },
79 | module: {
80 | rules: [
81 | {
82 | test: /\.(js|json|ts|tsx)$/,
83 | exclude: /(node_modules|bower_components)/,
84 | use: {
85 | loader: 'babel-loader'
86 | }
87 | },
88 | {
89 | test: /\.(sa|sc|c)ss$/,
90 | use: [
91 | MiniCssExtractPlugin.loader,
92 | { loader: "css-loader" },
93 | {
94 | loader: "postcss-loader",
95 | options: {
96 | postcssOptions: {
97 | plugins: [
98 | [
99 | "autoprefixer"
100 | ],
101 | ],
102 | },
103 | },
104 | },
105 | ],
106 | },
107 | {
108 | test: /\.(png|jpe?g|gif|svg|eot|ttf|woff|woff2)$/,
109 | use: ['url-loader'],
110 | }
111 | ]
112 | },
113 | plugins: [
114 | new MiniCssExtractPlugin({
115 | filename: 'css/index.css'
116 | }),
117 | new CopyPlugin({
118 | patterns: [ { from: "src/lib/interfaces.d.ts" } ],
119 | }),
120 | new webpack.BannerPlugin(banner)
121 | ],
122 | resolve: {
123 | extensions: ['.ts', '.tsx', '.js', '.json']
124 | }
125 | };
126 |
--------------------------------------------------------------------------------
/webpack.config.modern.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Config to support modern browsers only (build/index.modern.js)
3 | */
4 | const path = require('path');
5 | const webpack = require('webpack');
6 | const TerserPlugin = require('terser-webpack-plugin');
7 | const getPackageJson = require('./scripts/getPackageJson');
8 |
9 | const {
10 | version,
11 | name,
12 | license,
13 | repository,
14 | author
15 | } = getPackageJson('version', 'name', 'license', 'repository', 'author');
16 |
17 | const banner = `
18 | ${name} v${version} (index.modern.js - Modern Browsers bundle)
19 | ${repository.url}
20 |
21 | NOTE: This modern browsers bundle (index.modern.js) removes all polyfills
22 | included in the standard version. Use this if you are supporting
23 | modern browsers only. Otherwise, use the standard version (index.js).
24 |
25 | Copyright (c) ${author.replace(/ *<[^)]*> */g, " ")} and project contributors.
26 |
27 | This source code is licensed under the ${license} license found in the
28 | LICENSE file in the root directory of this source tree.
29 | `;
30 |
31 | module.exports = {
32 | mode: "production",
33 | entry: './src/lib/index.modern.ts',
34 | target: 'es5',
35 | devtool: 'source-map',
36 | output: {
37 | filename: 'index.modern.js',
38 | path: path.resolve(__dirname, 'build'),
39 | library: "ReactSimpleKeyboard",
40 | libraryTarget: 'umd',
41 | globalObject: 'this',
42 | hashFunction: 'xxhash64',
43 | chunkFormat: 'module',
44 | },
45 | optimization: {
46 | minimize: true,
47 | minimizer: [
48 | new TerserPlugin({
49 | terserOptions: {
50 | format: {
51 | comments: /react-simple-keyboard/i,
52 | },
53 | },
54 | extractComments: false
55 | }),
56 | ],
57 | },
58 | externals: {
59 | react: {
60 | root: 'React',
61 | commonjs2: 'react',
62 | commonjs: 'react',
63 | amd: 'react'
64 | },
65 | 'react-dom': {
66 | root: 'ReactDOM',
67 | commonjs2: 'react-dom',
68 | commonjs: 'react-dom',
69 | amd: 'react-dom'
70 | }
71 | },
72 | module: {
73 | rules: [
74 | {
75 | test: /\.(js|json|ts|tsx)$/,
76 | exclude: /(node_modules|bower_components)/,
77 | use: {
78 | loader: 'babel-loader',
79 | options: {
80 | presets: [
81 | "@babel/preset-react",
82 | "@babel/env",
83 | "@babel/preset-typescript"
84 | ],
85 | plugins: [
86 | ["@babel/plugin-proposal-class-properties"],
87 | ["@babel/plugin-transform-typescript"]
88 | ]
89 | }
90 | }
91 | },
92 | {
93 | test: /\.(sa|sc|c)ss$/,
94 | use: path.resolve('scripts/loaderMock.js')
95 | }
96 | ]
97 | },
98 | plugins: [
99 | new webpack.BannerPlugin(banner)
100 | ],
101 | resolve: {
102 | extensions: ['.ts', '.tsx', '.js', '.json']
103 | }
104 | };
105 |
--------------------------------------------------------------------------------
/webpack.config.modern_esm.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Config to support modern browsers only (build/index.modern.js)
3 | */
4 | const path = require('path');
5 | const webpack = require('webpack');
6 | const TerserPlugin = require('terser-webpack-plugin');
7 | const getPackageJson = require('./scripts/getPackageJson');
8 |
9 | const {
10 | version,
11 | name,
12 | license,
13 | repository,
14 | author
15 | } = getPackageJson('version', 'name', 'license', 'repository', 'author');
16 |
17 | const banner = `
18 | ${name} v${version} (index.modern.esm.js - Modern Browsers bundle, ESM output)
19 | ${repository.url}
20 |
21 | NOTE: This modern browsers bundle (index.modern.esm.js) removes all polyfills
22 | included in the standard version. Use this if you are supporting
23 | modern browsers only. Otherwise, use the standard version (index.js).
24 |
25 | Copyright (c) ${author.replace(/ *<[^)]*> */g, " ")} and project contributors.
26 |
27 | This source code is licensed under the ${license} license found in the
28 | LICENSE file in the root directory of this source tree.
29 | `;
30 |
31 | module.exports = {
32 | mode: "production",
33 | entry: './src/lib/index.modern.ts',
34 | devtool: 'source-map',
35 | target: [
36 | 'web',
37 | 'es2020'
38 | ],
39 | output: {
40 | filename: 'index.modern.esm.js',
41 | path: path.resolve(__dirname, 'build'),
42 | globalObject: 'this',
43 | hashFunction: 'xxhash64',
44 | chunkFormat: 'module',
45 | library: {
46 | type: 'module'
47 | }
48 | },
49 | experiments: {
50 | outputModule: true
51 | },
52 | externalsType: 'module',
53 | optimization: {
54 | minimize: true,
55 | minimizer: [
56 | new TerserPlugin({
57 | terserOptions: {
58 | format: {
59 | comments: /react-simple-keyboard/i,
60 | },
61 | },
62 | extractComments: false
63 | }),
64 | ],
65 | },
66 | externals: {
67 | react: {
68 | root: 'React',
69 | commonjs2: 'react',
70 | commonjs: 'react',
71 | amd: 'react',
72 | module: 'react'
73 | },
74 | 'react-dom': {
75 | root: 'ReactDOM',
76 | commonjs2: 'react-dom',
77 | commonjs: 'react-dom',
78 | amd: 'react-dom',
79 | module: 'react-dom'
80 | }
81 | },
82 | module: {
83 | rules: [
84 | {
85 | test: /\.(js|json|ts|tsx)$/,
86 | exclude: /(node_modules|bower_components)/,
87 | use: {
88 | loader: 'babel-loader',
89 | options: {
90 | presets: [
91 | "@babel/preset-react",
92 | "@babel/env",
93 | "@babel/preset-typescript"
94 | ],
95 | plugins: [
96 | ["@babel/plugin-proposal-class-properties"],
97 | ["@babel/plugin-transform-typescript"]
98 | ]
99 | }
100 | }
101 | },
102 | {
103 | test: /\.(sa|sc|c)ss$/,
104 | use: path.resolve('scripts/loaderMock.js')
105 | }
106 | ]
107 | },
108 | plugins: [
109 | new webpack.BannerPlugin(banner)
110 | ],
111 | resolve: {
112 | extensions: ['.ts', '.tsx', '.js', '.json']
113 | }
114 | };
115 |
--------------------------------------------------------------------------------