Port to typescript

This commit is contained in:
Francisco Hodge 2021-03-07 02:52:17 -05:00
parent 60d8d43b3c
commit d1c414b7b5
45 changed files with 5517 additions and 18596 deletions

View File

@ -1,30 +1,27 @@
{ {
"parser": "babel-eslint", "parser": "@typescript-eslint/parser",
"parserOptions": { "parserOptions": {
"project": "./tsconfig.json",
"ecmaVersion": 6, "ecmaVersion": 6,
"sourceType": "module", "sourceType": "module",
"ecmaFeatures": { "ecmaFeatures": {
"jsx": true,
"modules": true, "modules": true,
"experimentalObjectRestSpread": true "experimentalObjectRestSpread": true
} }
}, },
"plugins": [ "plugins": ["@typescript-eslint"],
"react" "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
],
"extends": ["eslint:recommended", "plugin:react/recommended"],
"rules": { "rules": {
"comma-dangle": 0, "comma-dangle": 0,
"no-unused-vars": "warn", "no-unused-vars": "warn",
"no-unexpected-multiline": "warn", "no-unexpected-multiline": "warn",
"prefer-const": "warn" "prefer-const": "warn",
}, "@typescript-eslint/no-empty-function": "off",
"settings": { "@typescript-eslint/explicit-module-boundary-types": "off",
"react": { "@typescript-eslint/no-explicit-any": "off",
"pragma": "React", "@typescript-eslint/no-var-requires": "off"
"version": "15.6.1"
}
}, },
"settings": {},
"env": { "env": {
"browser": true, "browser": true,
"node": true, "node": true,

View File

@ -17,8 +17,6 @@ jobs:
- name: npm install, build, and test - name: npm install, build, and test
run: | run: |
npm install npm install
npm run start -- --testMode
npm run demo
npm run coverage npm run coverage
- uses: codecov/codecov-action@v1.0.2 - uses: codecov/codecov-action@v1.0.2
with: with:
@ -32,7 +30,7 @@ jobs:
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }} DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
uses: Ilshidur/action-discord@master uses: Ilshidur/action-discord@master
with: with:
args: 'simple-keyboard - CI Build Passed' args: "simple-keyboard - CI Build Passed"
- name: Discord notification - name: Discord notification
if: failure() if: failure()
@ -40,5 +38,4 @@ jobs:
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }} DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
uses: Ilshidur/action-discord@master uses: Ilshidur/action-discord@master
with: with:
args: 'simple-keyboard - CI Build Failed' args: "simple-keyboard - CI Build Failed"

3
.gitignore vendored
View File

@ -5,9 +5,6 @@
/.pnp /.pnp
.pnp.js .pnp.js
# production
/demo
# testing # testing
/coverage /coverage

View File

@ -2,6 +2,7 @@
/node_modules /node_modules
# testing # testing
/tests
/coverage /coverage
# docs # docs
@ -13,12 +14,13 @@
.env.development.local .env.development.local
.env.test.local .env.test.local
.env.production.local .env.production.local
/.github
/demo
.esdoc.json
npm-debug.log* npm-debug.log*
yarn-debug.log* yarn-debug.log*
yarn-error.log* yarn-error.log*
/.github
/demo
# Development folders and files # Development folders and files
public public
@ -30,3 +32,5 @@ CHANGELOG.md
README.md README.md
.eslintignore .eslintignore
.eslintrc.json .eslintrc.json
webpack.config.js
babel.config.js

View File

@ -1,19 +0,0 @@
language: node_js
node_js:
- node
install:
- npm install -g codecov
- npm install
script:
- npm run start -- --testMode
- npm run demo
- npm run coverage
- codecov
after_success:
- wget https://raw.githubusercontent.com/DiscordHooks/travis-ci-discord-webhook/master/send.sh
- chmod +x send.sh
- ./send.sh success $WEBHOOK_URL
after_failure:
- wget https://raw.githubusercontent.com/DiscordHooks/travis-ci-discord-webhook/master/send.sh
- chmod +x send.sh
- ./send.sh failure $WEBHOOK_URL

9
babel.config.js Normal file
View File

@ -0,0 +1,9 @@
module.exports = {
presets: [
["@babel/env"]
],
plugins: [
["@babel/plugin-proposal-class-properties"],
["@babel/plugin-transform-typescript"]
]
};

View File

@ -1,12 +1,11 @@
/*! /*!
* *
* simple-keyboard v2.32.119 * simple-keyboard v3.0.0-beta.0
* https://github.com/hodgef/simple-keyboard * https://github.com/hodgef/simple-keyboard
* *
* Copyright (c) Francisco Hodge (https://github.com/hodgef) * Copyright (c) Francisco Hodge (https://github.com/hodgef) and project contributors.
* *
* This source code is licensed under the MIT license found in the * This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree. * LICENSE file in the root directory of this source tree.
* *
*/.hg-theme-default{width:100%;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;box-sizing:border-box;overflow:hidden;touch-action:manipulation}.hg-theme-default .hg-button span{pointer-events:none}.hg-theme-default button.hg-button{border-width:0;outline:0;font-size:inherit}.hg-theme-default{font-family:"HelveticaNeue-Light","Helvetica Neue Light","Helvetica Neue",Helvetica,Arial,"Lucida Grande",sans-serif;background-color:#ececec;padding:5px;border-radius:5px}.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{box-shadow:0 0 3px -1px rgba(0,0,0,.3);height:40px;border-radius:5px;box-sizing:border-box;padding:5px;background:#fff;border-bottom:1px solid #b5b5b5;cursor:pointer;display:flex;align-items:center;justify-content:center;-webkit-tap-highlight-color:rgba(0,0,0,0)}.hg-theme-default .hg-button.hg-activeButton{background:#efefef}.hg-theme-default.hg-layout-numeric .hg-button{width:33.3%;height:60px;align-items:center;display:flex;justify-content:center}.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-theme-default{width:100%;user-select:none;box-sizing:border-box;overflow:hidden;touch-action:manipulation}.hg-theme-default .hg-button span{pointer-events:none}.hg-theme-default button.hg-button{border-width:0;outline:0;font-size:inherit}.hg-theme-default{font-family:HelveticaNeue-Light,Helvetica Neue Light,Helvetica Neue,Helvetica,Arial,Lucida Grande,sans-serif;background-color:#ececec;padding:5px;border-radius:5px}.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{box-shadow:0 0 3px -1px rgba(0,0,0,.3);height:40px;border-radius:5px;box-sizing:border-box;padding:5px;background:#fff;border-bottom:1px solid #b5b5b5;cursor:pointer;display:flex;align-items:center;justify-content:center;-webkit-tap-highlight-color:rgba(0,0,0,0)}.hg-theme-default .hg-button.hg-activeButton{background:#efefef}.hg-theme-default.hg-layout-numeric .hg-button{width:33.3%;height:60px;align-items:center;display:flex;justify-content:center}.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}
/*# sourceMappingURL=index.css.map */

File diff suppressed because one or more lines are too long

595
build/index.d.ts vendored
View File

@ -1,329 +1,618 @@
declare module 'simple-keyboard' { declare module "services/KeyboardLayout" {
export const getDefaultLayout: () => {
default: string[];
shift: string[];
};
}
declare module "interfaces" {
export interface KeyboardLayoutObject { export interface KeyboardLayoutObject {
[key: string]: string[]; [key: string]: string[];
} }
export interface KeyboardButtonTheme { export interface KeyboardButtonTheme {
class: string; class: string;
buttons: string; buttons: string;
} }
export interface KeyboardButtonAttributes { export interface KeyboardButtonAttributes {
attribute: string; attribute: string;
value: string; value: string;
buttons: string; buttons: string;
} }
export interface KeyboardInput {
[key: string]: string;
}
export type KeyboardButton = HTMLDivElement | HTMLButtonElement;
export type KeyboardHandlerEvent = PointerEvent & TouchEvent & KeyboardEvent & {
target: HTMLDivElement | HTMLInputElement;
};
export interface KeyboardButtonElements {
[key: string]: KeyboardButton[];
}
export interface UtilitiesParams {
getOptions: () => KeyboardOptions;
getCaretPosition: () => number;
getCaretPositionEnd: () => number;
dispatch: any;
}
export interface KeyboardOptions { export interface KeyboardOptions {
/** /**
* Modify the keyboard layout. * Modify the keyboard layout.
*/ */
layout?: KeyboardLayoutObject; layout?: KeyboardLayoutObject;
/** /**
* Specifies which layout should be used. * Specifies which layout should be used.
*/ */
layoutName?: string; layoutName?: string;
/** /**
* Replaces variable buttons (such as `{bksp}`) with a human-friendly name (e.g.: `backspace`). * Replaces variable buttons (such as `{bksp}`) with a human-friendly name (e.g.: `backspace`).
*/ */
display?: { [button: string]: string }; display?: {
[button: string]: string;
};
/** /**
* By default, when you set the display property, you replace the default one. This setting merges them instead. * By default, when you set the display property, you replace the default one. This setting merges them instead.
*/ */
mergeDisplay?: boolean; mergeDisplay?: boolean;
/** /**
* A prop to add your own css classes to the keyboard wrapper. You can add multiple classes separated by a space. * A prop to add your own css classes to the keyboard wrapper. You can add multiple classes separated by a space.
*/ */
theme?: string; theme?: string;
/** /**
* A prop to add your own css classes to one or several buttons. * A prop to add your own css classes to one or several buttons.
*/ */
buttonTheme?: KeyboardButtonTheme[]; buttonTheme?: KeyboardButtonTheme[];
/** /**
* A prop to add your own attributes to one or several buttons. * A prop to add your own attributes to one or several buttons.
*/ */
buttonAttributes?: KeyboardButtonAttributes[]; buttonAttributes?: KeyboardButtonAttributes[];
/** /**
* Runs a `console.log` every time a key is pressed. Displays the buttons pressed and the current input. * Runs a `console.log` every time a key is pressed. Displays the buttons pressed and the current input.
*/ */
debug?: boolean; debug?: boolean;
/** /**
* Specifies whether clicking the "ENTER" button will input a newline (`\n`) or not. * Specifies whether clicking the "ENTER" button will input a newline (`\n`) or not.
*/ */
newLineOnEnter?: boolean; newLineOnEnter?: boolean;
/** /**
* Specifies whether clicking the "TAB" button will input a tab character (`\t`) or not. * Specifies whether clicking the "TAB" button will input a tab character (`\t`) or not.
*/ */
tabCharOnTab?: boolean; tabCharOnTab?: boolean;
/** /**
* Allows you to use a single simple-keyboard instance for several inputs. * Allows you to use a single simple-keyboard instance for several inputs.
*/ */
inputName?: string; inputName?: string;
/** /**
* `number`: Restrains all of simple-keyboard inputs to a certain length. This should be used in addition to the input elements maxlengthattribute. * `number`: Restrains all of simple-keyboard inputs to a certain length. This should be used in addition to the input elements maxlengthattribute.
* *
* `{ [inputName: string]: number }`: Restrains simple-keyboards individual inputs to a certain length. This should be used in addition to the input elements maxlengthattribute. * `{ [inputName: string]: number }`: Restrains simple-keyboards individual inputs to a certain length. This should be used in addition to the input elements maxlengthattribute.
*/ */
maxLength?: any; maxLength?: any;
/** /**
* When set to true, this option synchronizes the internal input of every simple-keyboard instance. * When set to true, this option synchronizes the internal input of every simple-keyboard instance.
*/ */
syncInstanceInputs?: boolean; syncInstanceInputs?: boolean;
/** /**
* Enable highlighting of keys pressed on physical keyboard. * Enable highlighting of keys pressed on physical keyboard.
*/ */
physicalKeyboardHighlight?: boolean; physicalKeyboardHighlight?: boolean;
/** /**
* Presses keys highlighted by physicalKeyboardHighlight * Presses keys highlighted by physicalKeyboardHighlight
*/ */
physicalKeyboardHighlightPress?: boolean; physicalKeyboardHighlightPress?: boolean;
/** /**
* Define the text color that the physical keyboard highlighted key should have. * Define the text color that the physical keyboard highlighted key should have.
*/ */
physicalKeyboardHighlightTextColor?: string; physicalKeyboardHighlightTextColor?: string;
/** /**
* Define the background color that the physical keyboard highlighted key should have. * Define the background color that the physical keyboard highlighted key should have.
*/ */
physicalKeyboardHighlightBgColor?: string; physicalKeyboardHighlightBgColor?: string;
/** /**
* Calling preventDefault for the mousedown events keeps the focus on the input. * Calling preventDefault for the mousedown events keeps the focus on the input.
*/ */
preventMouseDownDefault?: boolean; preventMouseDownDefault?: boolean;
/** /**
* Calling preventDefault for the mouseup events. * Calling preventDefault for the mouseup events.
*/ */
preventMouseUpDefault?: boolean; preventMouseUpDefault?: boolean;
/** /**
* Stops pointer down events on simple-keyboard buttons from bubbling to parent elements. * Stops pointer down events on simple-keyboard buttons from bubbling to parent elements.
*/ */
stopMouseDownPropagation?: boolean; stopMouseDownPropagation?: boolean;
/** /**
* Stops pointer up events on simple-keyboard buttons from bubbling to parent elements. * Stops pointer up events on simple-keyboard buttons from bubbling to parent elements.
*/ */
stopMouseUpPropagation?: boolean; stopMouseUpPropagation?: boolean;
/** /**
* Render buttons as a button element instead of a div element. * Render buttons as a button element instead of a div element.
*/ */
useButtonTag?: boolean; useButtonTag?: boolean;
/** /**
* A prop to ensure characters are always be added/removed at the end of the string. * A prop to ensure characters are always be added/removed at the end of the string.
*/ */
disableCaretPositioning?: boolean; disableCaretPositioning?: boolean;
/** /**
* Restrains input(s) change to the defined regular expression pattern. * Restrains input(s) change to the defined regular expression pattern.
*/ */
inputPattern?: any; inputPattern?: any;
/** /**
* Instructs simple-keyboard to use touch events instead of click events. * Instructs simple-keyboard to use touch events instead of click events.
*/ */
useTouchEvents?: boolean; useTouchEvents?: boolean;
/** /**
* Enable useTouchEvents automatically when touch device is detected. * Enable useTouchEvents automatically when touch device is detected.
*/ */
autoUseTouchEvents?: boolean; autoUseTouchEvents?: boolean;
/** /**
* Opt out of PointerEvents handling, falling back to the prior mouse event logic. * Opt out of PointerEvents handling, falling back to the prior mouse event logic.
*/ */
useMouseEvents?: boolean; useMouseEvents?: boolean;
/** /**
* Disable button hold action. * Disable button hold action.
*/ */
disableButtonHold?: boolean; disableButtonHold?: boolean;
/** /**
* Adds unicode right-to-left control characters to input return values. * Adds unicode right-to-left control characters to input return values.
*/ */
rtl?: boolean; rtl?: boolean;
/**
* Executes the callback function on key press. Returns button layout name (i.e.: "{shift}").
*/
onKeyPress?: (button: string) => any;
/**
* Executes the callback function on key release.
*/
onKeyReleased?: (button: string) => any;
/**
* Executes the callback function on input change. Returns the current input's string.
*/
onChange?: (input: string) => any;
/**
* Executes the callback function before the first simple-keyboard render.
*/
beforeFirstRender?: () => void;
/**
* Executes the callback function before a simple-keyboard render.
*/
beforeRender?: () => void;
/**
* Executes the callback function every time simple-keyboard is rendered (e.g: when you change layouts).
*/
onRender?: () => void;
/**
* Executes the callback function once simple-keyboard is rendered for the first time (on initialization).
*/
onInit?: () => void;
/**
* Executes the callback function on input change. Returns the input object with all defined inputs.
*/
onChangeAll?: (inputs: any) => any;
/**
* Module classes to be loaded by simple-keyboard.
*/
modules?: any[];
/** /**
* Module options can have any format * Module options can have any format
*/ */
[name: string]: any; [name: string]: any;
} }
}
class Keyboard { declare module "services/Utilities" {
constructor(selector: string, options: KeyboardOptions); import { KeyboardInput } from "interfaces";
constructor(selector: HTMLDivElement, options: KeyboardOptions); import { KeyboardOptions, UtilitiesParams } from "interfaces";
constructor(options: KeyboardOptions);
/** /**
* Options * Utility Service
*/ */
class Utilities {
getOptions: () => KeyboardOptions;
getCaretPosition: () => number;
getCaretPositionEnd: () => number;
dispatch: any;
maxLengthReached: boolean;
/**
* Creates an instance of the Utility service
*/
constructor({ getOptions, getCaretPosition, getCaretPositionEnd, dispatch, }: UtilitiesParams);
/**
* Adds default classes to a given button
*
* @param {string} button The button's layout name
* @return {string} The classes to be added to the button
*/
getButtonClass(button: string): string;
/**
* Default button display labels
*/
getDefaultDiplay(): {
"{bksp}": string;
"{backspace}": string;
"{enter}": string;
"{shift}": string;
"{shiftleft}": string;
"{shiftright}": string;
"{alt}": string;
"{s}": string;
"{tab}": string;
"{lock}": string;
"{capslock}": string;
"{accept}": string;
"{space}": string;
"{//}": string;
"{esc}": string;
"{escape}": string;
"{f1}": string;
"{f2}": string;
"{f3}": string;
"{f4}": string;
"{f5}": string;
"{f6}": string;
"{f7}": string;
"{f8}": string;
"{f9}": string;
"{f10}": string;
"{f11}": string;
"{f12}": string;
"{numpaddivide}": string;
"{numlock}": string;
"{arrowup}": string;
"{arrowleft}": string;
"{arrowdown}": string;
"{arrowright}": string;
"{prtscr}": string;
"{scrolllock}": string;
"{pause}": string;
"{insert}": string;
"{home}": string;
"{pageup}": string;
"{delete}": string;
"{end}": string;
"{pagedown}": string;
"{numpadmultiply}": string;
"{numpadsubtract}": string;
"{numpadadd}": string;
"{numpadenter}": string;
"{period}": string;
"{numpaddecimal}": string;
"{numpad0}": string;
"{numpad1}": string;
"{numpad2}": string;
"{numpad3}": string;
"{numpad4}": string;
"{numpad5}": string;
"{numpad6}": string;
"{numpad7}": string;
"{numpad8}": string;
"{numpad9}": string;
};
/**
* Returns the display (label) name for a given button
*
* @param {string} button The button's layout name
* @param {object} display The provided display option
* @param {boolean} mergeDisplay Whether the provided param value should be merged with the default one.
*/
getButtonDisplayName(button: string, display: KeyboardOptions["display"], mergeDisplay: boolean): string;
/**
* Returns the updated input resulting from clicking a given button
*
* @param {string} button The button's layout name
* @param {string} input The input string
* @param {number} caretPos The cursor's current position
* @param {number} caretPosEnd The cursor's current end position
* @param {boolean} moveCaret Whether to update simple-keyboard's cursor
*/
getUpdatedInput(button: string, input: string, caretPos: number, caretPosEnd?: number, moveCaret?: boolean): string;
/**
* Moves the cursor position by a given amount
*
* @param {number} length Represents by how many characters the input should be moved
* @param {boolean} minus Whether the cursor should be moved to the left or not.
*/
updateCaretPos(length: number, minus?: boolean): void;
/**
* Action method of updateCaretPos
*
* @param {number} length Represents by how many characters the input should be moved
* @param {boolean} minus Whether the cursor should be moved to the left or not.
*/
updateCaretPosAction(length: number, minus?: boolean): number;
/**
* Adds a string to the input at a given position
*
* @param {string} source The source input
* @param {string} str The string to add
* @param {number} position The (cursor) position where the string should be added
* @param {boolean} moveCaret Whether to update simple-keyboard's cursor
*/
addStringAt(source: string, str: string, position?: number, positionEnd?: number, moveCaret?: boolean): string;
/**
* Removes an amount of characters at a given position
*
* @param {string} source The source input
* @param {number} position The (cursor) position from where the characters should be removed
* @param {boolean} moveCaret Whether to update simple-keyboard's cursor
*/
removeAt(source: string, position?: number, positionEnd?: number, moveCaret?: boolean): string;
/**
* Determines whether the maxLength has been reached. This function is called when the maxLength option it set.
*
* @param {object} inputObj
* @param {string} updatedInput
*/
handleMaxLength(inputObj: KeyboardInput, updatedInput: string): boolean;
/**
* Gets the current value of maxLengthReached
*/
isMaxLengthReached(): boolean;
/**
* Determines whether a touch device is being used
*/
isTouchDevice(): number | true;
/**
* Determines whether pointer events are supported
*/
pointerEventsSupported(): {
new (type: string, eventInitDict?: PointerEventInit): PointerEvent;
prototype: PointerEvent;
};
/**
* Bind all methods in a given class
*/
static bindMethods(myClass: any, instance: any): void;
/**
* Transforms an arbitrary string to camelCase
*
* @param {string} str The string to transform.
*/
camelCase(str: string): string;
static noop: () => void;
}
export default Utilities;
}
declare module "services/PhysicalKeyboard" {
import { KeyboardOptions, UtilitiesParams } from "interfaces";
/**
* Physical Keyboard Service
*/
class PhysicalKeyboard {
getOptions: () => KeyboardOptions;
dispatch: any;
/**
* Creates an instance of the PhysicalKeyboard service
*/
constructor({ dispatch, getOptions }: Partial<UtilitiesParams>);
handleHighlightKeyDown(event: KeyboardEvent): void;
handleHighlightKeyUp(event: KeyboardEvent): void;
/**
* Transforms a KeyboardEvent's "key.code" string into a simple-keyboard layout format
* @param {object} event The KeyboardEvent
*/
getSimpleKeyboardLayoutKey(event: KeyboardEvent): string;
}
export default PhysicalKeyboard;
}
declare module "components/Keyboard" {
import "./Keyboard.css";
import PhysicalKeyboard from "services/PhysicalKeyboard";
import { KeyboardOptions, KeyboardInput, KeyboardButtonElements, KeyboardHandlerEvent, KeyboardButton } from "interfaces";
/**
* Root class for simple-keyboard
* This class:
* - Parses the options
* - Renders the rows and buttons
* - Handles button functionality
*/
class SimpleKeyboard {
input: KeyboardInput;
options: KeyboardOptions; options: KeyboardOptions;
utilities: any;
caretPosition: number;
caretPositionEnd: number;
keyboardDOM: KeyboardButton;
keyboardPluginClasses: string;
keyboardDOMClass: string;
buttonElements: KeyboardButtonElements;
currentInstanceName: string;
allKeyboardInstances: {
[key: string]: SimpleKeyboard;
};
keyboardInstanceNames: string[];
isFirstKeyboardInstance: boolean;
physicalKeyboard: PhysicalKeyboard;
modules: {
[key: string]: any;
};
activeButtonClass: string;
holdInteractionTimeout: number;
holdTimeout: number;
isMouseHold: boolean;
initialized: boolean;
/** /**
* Utilities * Creates an instance of SimpleKeyboard
* @param {Array} params 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.
*/ */
utilities?: any; constructor(...params: []);
/** /**
* caretPosition * parseParams
*/ */
caretPosition?: number; handleParams: (params: any[]) => {
keyboardDOMClass: string;
keyboardDOM: KeyboardButton;
options: Partial<KeyboardOptions>;
};
/** /**
* caretPositionEnd * Getters
*/ */
caretPositionEnd?: number; getOptions: () => KeyboardOptions;
getCaretPosition: () => number;
getCaretPositionEnd: () => number;
/** /**
* Changes the internal caret position * Changes the internal caret position
* @param {number} position The caret's start position * @param {number} position The caret's start position
* @param {number} positionEnd The caret's end position * @param {number} positionEnd The caret's end position
*/ */
setCaretPosition(position: number, positionEnd?: number): void; setCaretPosition(position: number, endPosition?: number): void;
/** /**
* Retrieves the internal caret position * Handles clicks made to keyboard buttons
* @param {string} button The button's layout name.
*/ */
getCaretPosition(): number; handleButtonClicked(button: string): void;
/** /**
* Retrieves the internal end caret position * Handles button mousedown
*/ */
getCaretPositionEnd(): number; handleButtonMouseDown(button: string, e: KeyboardHandlerEvent): void;
/**
* Handles button mouseup
*/
handleButtonMouseUp(button?: string, e?: KeyboardHandlerEvent): void;
/**
* Handles container mousedown
*/
handleKeyboardContainerMouseDown(e: KeyboardHandlerEvent): void;
/**
* Handles button hold
*/
handleButtonHold(button: string): void;
/**
* Send a command to all simple-keyboard instances (if you have several instances).
*/
syncInstanceInputs(): void;
/**
* Clear the keyboards input.
* @param {string} [inputName] optional - the internal input to select
*/
clearInput(inputName: string): void;
/**
* Get the keyboards input (You can also get it from the onChange prop).
* @param {string} [inputName] optional - the internal input to select
*/
getInput(inputName: string, skipSync?: boolean): string;
/**
* Get all simple-keyboard inputs
*/
getAllInputs(): KeyboardInput;
/**
* Set the keyboards input.
* @param {string} input the input value
* @param {string} inputName optional - the internal input to select
*/
setInput(input: string, inputName: string): void;
/**
* Replace the input object (`keyboard.input`)
* @param {object} inputObj The input object
*/
replaceInput(inputObj: KeyboardInput): void;
/**
* Set new option or modify existing ones after initialization.
* @param {object} options The options to set
*/
setOptions(options?: {}): void;
/**
* Detecting changes to non-function options
* This allows us to ascertain whether a button re-render is needed
*/
changedOptions(newOptions: Partial<KeyboardOptions>): string[];
/**
* Executing actions depending on changed options
* @param {object} options The options to set
*/
onSetOptions(options: Partial<KeyboardOptions>): void;
/**
* Remove all keyboard rows and reset keyboard values.
* Used internally between re-renders.
*/
clear(): void;
/**
* Send a command to all simple-keyboard instances at once (if you have multiple instances).
* @param {function(instance: object, key: string)} callback Function to run on every instance
*/
dispatch(callback: (instance: SimpleKeyboard, key?: string) => void): void;
/** /**
* Adds/Modifies an entry to the `buttonTheme`. Basically a way to add a class to a button. * Adds/Modifies an entry to the `buttonTheme`. Basically a way to add a class to a button.
* @param {string} buttons List of buttons to select (separated by a space). * @param {string} buttons List of buttons to select (separated by a space).
* @param {string} className Classes to give to the selected buttons (separated by space). * @param {string} className Classes to give to the selected buttons (separated by space).
*/ */
addButtonTheme(buttons: string, className: string): void; addButtonTheme(buttons: string, className: string): void;
/** /**
* Removes/Amends an entry to the `buttonTheme`. Basically a way to remove a class previously added to a button through buttonTheme or addButtonTheme. * Removes/Amends an entry to the `buttonTheme`. Basically a way to remove a class previously added to a button through buttonTheme or addButtonTheme.
* @param {string} buttons List of buttons to select (separated by a space). * @param {string} buttons List of buttons to select (separated by a space).
* @param {string} className Classes to give to the selected buttons (separated by space). * @param {string} className Classes to give to the selected buttons (separated by space).
*/ */
removeButtonTheme(buttons: string, className: string): void; removeButtonTheme(buttons: string, className: string): void;
/**
* Clear the keyboard's input.
*
* @param {string} [inputName] optional - the internal input to select
*/
clearInput(inputName?: string): void;
/**
* Get the keyboards input (You can also get it from the onChange prop).
* @param {string} [inputName] optional - the internal input to select
*/
getInput(inputName?: string): string;
/**
* Set the keyboards input.
* @param {string} input the input value
* @param {string} inputName optional - the internal input to select
*/
setInput(input: string, inputName?: string): void;
/**
* Replaces the entire internal input object.
* @param {string} input the input object
*/
replaceInput(input: any): void;
/**
* Set new option or modify existing ones after initialization.
* @param {KeyboardOptions} option The option to set
*/
setOptions(options: KeyboardOptions): void;
/**
* Send a command to all simple-keyboard instances at once (if you have multiple instances).
* @param {function(instance: object, key: string)} callback Function to run on every instance
*/
dispatch(callback: (instance: any, key: string) => void): void;
/** /**
* Get the DOM Element of a button. If there are several buttons with the same name, an array of the DOM Elements is returned. * Get the DOM Element of a button. If there are several buttons with the same name, an array of the DOM Elements is returned.
* @param {string} button The button layout name to select * @param {string} button The button layout name to select
*/ */
getButtonElement(button: string): HTMLElement | HTMLElement[]; getButtonElement(button: string): KeyboardButton | KeyboardButton[];
/** /**
* Clears keyboard listeners and DOM elements. * This handles the "inputPattern" option
* by checking if the provided inputPattern passes
*/ */
destroy(): void; inputPatternIsValid(inputVal: string): boolean;
/** /**
* Iterate on each button (ForEach) * Handles simple-keyboard event listeners
*/
setEventListeners(): void;
/**
* Event Handler: KeyUp
*/
handleKeyUp(event: KeyboardHandlerEvent): void;
/**
* Event Handler: KeyDown
*/
handleKeyDown(event: KeyboardHandlerEvent): void;
/**
* Event Handler: MouseUp
*/
handleMouseUp(event: KeyboardHandlerEvent): void;
/**
* Event Handler: TouchEnd
*/
handleTouchEnd(event: KeyboardHandlerEvent): void;
/**
* Called by {@link setEventListeners} when an event that warrants a cursor position update is triggered
*/
caretEventHandler(event: KeyboardHandlerEvent): void;
/**
* Execute an operation on each button
*/ */
recurseButtons(fn: any): void; recurseButtons(fn: any): void;
/**
* Destroy keyboard listeners and DOM elements
*/
destroy(): void;
/**
* Process buttonTheme option
*/
getButtonThemeClasses(button: string): string[];
/**
* Process buttonAttributes option
*/
setDOMButtonAttributes(button: string, callback: any): void;
onTouchDeviceDetected(): void;
/**
* Disabling contextual window for hg-button
*/
disableContextualWindow(): void;
/**
* Process autoTouchEvents option
*/
processAutoTouchEvents(): void;
/**
* Executes the callback function once simple-keyboard is rendered for the first time (on initialization).
*/
onInit(): void;
/**
* Executes the callback function before a simple-keyboard render.
*/
beforeFirstRender(): void;
/**
* Executes the callback function before a simple-keyboard render.
*/
beforeRender(): void;
/**
* Executes the callback function every time simple-keyboard is rendered (e.g: when you change layouts).
*/
onRender(): void;
/**
* Executes the callback function once all modules have been loaded
*/
onModulesLoaded(): void;
/**
* Register module
*/
registerModule: (name: string, initCallback: any) => void;
/**
* Load modules
*/
loadModules(): void;
/**
* Get module prop
*/
getModuleProp(name: string, prop: string): any;
/**
* getModulesList
*/
getModulesList(): string[];
/**
* Parse Row DOM containers
*/
parseRowDOMContainers(rowDOM: HTMLDivElement, rowIndex: number, containerStartIndexes: number[], containerEndIndexes: number[]): HTMLDivElement;
/**
* getKeyboardClassString
*/
getKeyboardClassString: (...baseDOMClasses: any[]) => string;
/**
* Renders rows and buttons as per options
*/
render(): void;
} }
export default SimpleKeyboard;
export default Keyboard; }
declare module "index" {
import SimpleKeyboard from "components/Keyboard";
export default SimpleKeyboard;
} }

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,101 +0,0 @@
'use strict';
const fs = require('fs');
const path = require('path');
const paths = require('./paths');
// Make sure that including paths.js after env.js will read .env variables.
delete require.cache[require.resolve('./paths')];
const NODE_ENV = process.env.NODE_ENV;
if (!NODE_ENV) {
throw new Error(
'The NODE_ENV environment variable is required but was not specified.'
);
}
// https://github.com/bkeepers/dotenv#what-other-env-files-can-i-use
const dotenvFiles = [
`${paths.dotenv}.${NODE_ENV}.local`,
`${paths.dotenv}.${NODE_ENV}`,
// Don't include `.env.local` for `test` environment
// since normally you expect tests to produce the same
// results for everyone
NODE_ENV !== 'test' && `${paths.dotenv}.local`,
paths.dotenv,
].filter(Boolean);
// Load environment variables from .env* files. Suppress warnings using silent
// if this file is missing. dotenv will never modify any environment variables
// that have already been set. Variable expansion is supported in .env files.
// https://github.com/motdotla/dotenv
// https://github.com/motdotla/dotenv-expand
dotenvFiles.forEach(dotenvFile => {
if (fs.existsSync(dotenvFile)) {
require('dotenv-expand')(
require('dotenv').config({
path: dotenvFile,
})
);
}
});
// We support resolving modules according to `NODE_PATH`.
// This lets you use absolute paths in imports inside large monorepos:
// https://github.com/facebook/create-react-app/issues/253.
// It works similar to `NODE_PATH` in Node itself:
// https://nodejs.org/api/modules.html#modules_loading_from_the_global_folders
// Note that unlike in Node, only *relative* paths from `NODE_PATH` are honored.
// Otherwise, we risk importing Node.js core modules into an app instead of webpack shims.
// https://github.com/facebook/create-react-app/issues/1023#issuecomment-265344421
// We also resolve them to make sure all tools using them work consistently.
const appDirectory = fs.realpathSync(process.cwd());
process.env.NODE_PATH = (process.env.NODE_PATH || '')
.split(path.delimiter)
.filter(folder => folder && !path.isAbsolute(folder))
.map(folder => path.resolve(appDirectory, folder))
.join(path.delimiter);
// Grab NODE_ENV and REACT_APP_* environment variables and prepare them to be
// injected into the application via DefinePlugin in webpack configuration.
const REACT_APP = /^REACT_APP_/i;
function getClientEnvironment(publicUrl) {
const raw = Object.keys(process.env)
.filter(key => REACT_APP.test(key))
.reduce(
(env, key) => {
env[key] = process.env[key];
return env;
},
{
// Useful for determining whether were running in production mode.
// Most importantly, it switches React into the correct mode.
NODE_ENV: process.env.NODE_ENV || 'development',
// Useful for resolving the correct path to static assets in `public`.
// For example, <img src={process.env.PUBLIC_URL + '/img/logo.png'} />.
// This should only be used as an escape hatch. Normally you would put
// images into the `src` and `import` them in code to get their paths.
PUBLIC_URL: publicUrl,
// We support configuring the sockjs pathname during development.
// These settings let a developer run multiple simultaneous projects.
// They are used as the connection `hostname`, `pathname` and `port`
// in webpackHotDevClient. They are used as the `sockHost`, `sockPath`
// and `sockPort` options in webpack-dev-server.
WDS_SOCKET_HOST: process.env.WDS_SOCKET_HOST,
WDS_SOCKET_PATH: process.env.WDS_SOCKET_PATH,
WDS_SOCKET_PORT: process.env.WDS_SOCKET_PORT,
}
);
// Stringify all values so we can feed into webpack DefinePlugin
const stringified = {
'process.env': Object.keys(raw).reduce((env, key) => {
env[key] = JSON.stringify(raw[key]);
return env;
}, {}),
};
return { raw, stringified };
}
module.exports = getClientEnvironment;

View File

@ -1,66 +0,0 @@
'use strict';
const fs = require('fs');
const path = require('path');
const crypto = require('crypto');
const chalk = require('react-dev-utils/chalk');
const paths = require('./paths');
// Ensure the certificate and key provided are valid and if not
// throw an easy to debug error
function validateKeyAndCerts({ cert, key, keyFile, crtFile }) {
let encrypted;
try {
// publicEncrypt will throw an error with an invalid cert
encrypted = crypto.publicEncrypt(cert, Buffer.from('test'));
} catch (err) {
throw new Error(
`The certificate "${chalk.yellow(crtFile)}" is invalid.\n${err.message}`
);
}
try {
// privateDecrypt will throw an error with an invalid key
crypto.privateDecrypt(key, encrypted);
} catch (err) {
throw new Error(
`The certificate key "${chalk.yellow(keyFile)}" is invalid.\n${
err.message
}`
);
}
}
// Read file and throw an error if it doesn't exist
function readEnvFile(file, type) {
if (!fs.existsSync(file)) {
throw new Error(
`You specified ${chalk.cyan(
type
)} in your env, but the file "${chalk.yellow(file)}" can't be found.`
);
}
return fs.readFileSync(file);
}
// Get the https config
// Return cert files if provided in env, otherwise just true or false
function getHttpsConfig() {
const { SSL_CRT_FILE, SSL_KEY_FILE, HTTPS } = process.env;
const isHttps = HTTPS === 'true';
if (isHttps && SSL_CRT_FILE && SSL_KEY_FILE) {
const crtFile = path.resolve(paths.appPath, SSL_CRT_FILE);
const keyFile = path.resolve(paths.appPath, SSL_KEY_FILE);
const config = {
cert: readEnvFile(crtFile, 'SSL_CRT_FILE'),
key: readEnvFile(keyFile, 'SSL_KEY_FILE'),
};
validateKeyAndCerts({ ...config, keyFile, crtFile });
return config;
}
return isHttps;
}
module.exports = getHttpsConfig;

View File

@ -1,14 +0,0 @@
'use strict';
// This is a custom Jest transformer turning style imports into empty objects.
// http://facebook.github.io/jest/docs/en/webpack.html
module.exports = {
process() {
return 'module.exports = {};';
},
getCacheKey() {
// The output is always the same.
return 'cssTransform';
},
};

View File

@ -1,40 +0,0 @@
'use strict';
const path = require('path');
const camelcase = require('camelcase');
// This is a custom Jest transformer turning file imports into filenames.
// http://facebook.github.io/jest/docs/en/webpack.html
module.exports = {
process(src, filename) {
const assetFilename = JSON.stringify(path.basename(filename));
if (filename.match(/\.svg$/)) {
// Based on how SVGR generates a component name:
// https://github.com/smooth-code/svgr/blob/01b194cf967347d43d4cbe6b434404731b87cf27/packages/core/src/state.js#L6
const pascalCaseFilename = camelcase(path.parse(filename).name, {
pascalCase: true,
});
const componentName = `Svg${pascalCaseFilename}`;
return `const React = require('react');
module.exports = {
__esModule: true,
default: ${assetFilename},
ReactComponent: React.forwardRef(function ${componentName}(props, ref) {
return {
$$typeof: Symbol.for('react.element'),
type: 'svg',
ref: ref,
key: null,
props: Object.assign({}, props, {
children: ${assetFilename}
})
};
}),
};`;
}
return `module.exports = ${assetFilename};`;
},
};

View File

@ -1,141 +0,0 @@
'use strict';
const fs = require('fs');
const path = require('path');
const paths = require('./paths');
const chalk = require('react-dev-utils/chalk');
const resolve = require('resolve');
/**
* Get additional module paths based on the baseUrl of a compilerOptions object.
*
* @param {Object} options
*/
function getAdditionalModulePaths(options = {}) {
const baseUrl = options.baseUrl;
// We need to explicitly check for null and undefined (and not a falsy value) because
// TypeScript treats an empty string as `.`.
if (baseUrl == null) {
// If there's no baseUrl set we respect NODE_PATH
// Note that NODE_PATH is deprecated and will be removed
// in the next major release of create-react-app.
const nodePath = process.env.NODE_PATH || '';
return nodePath.split(path.delimiter).filter(Boolean);
}
const baseUrlResolved = path.resolve(paths.appPath, baseUrl);
// We don't need to do anything if `baseUrl` is set to `node_modules`. This is
// the default behavior.
if (path.relative(paths.appNodeModules, baseUrlResolved) === '') {
return null;
}
// Allow the user set the `baseUrl` to `appSrc`.
if (path.relative(paths.appSrc, baseUrlResolved) === '') {
return [paths.appSrc];
}
// If the path is equal to the root directory we ignore it here.
// We don't want to allow importing from the root directly as source files are
// not transpiled outside of `src`. We do allow importing them with the
// absolute path (e.g. `src/Components/Button.js`) but we set that up with
// an alias.
if (path.relative(paths.appPath, baseUrlResolved) === '') {
return null;
}
// Otherwise, throw an error.
throw new Error(
chalk.red.bold(
"Your project's `baseUrl` can only be set to `src` or `node_modules`." +
' Create React App does not support other values at this time.'
)
);
}
/**
* Get webpack aliases based on the baseUrl of a compilerOptions object.
*
* @param {*} options
*/
function getWebpackAliases(options = {}) {
const baseUrl = options.baseUrl;
if (!baseUrl) {
return {};
}
const baseUrlResolved = path.resolve(paths.appPath, baseUrl);
if (path.relative(paths.appPath, baseUrlResolved) === '') {
return {
src: paths.appSrc,
};
}
}
/**
* Get jest aliases based on the baseUrl of a compilerOptions object.
*
* @param {*} options
*/
function getJestAliases(options = {}) {
const baseUrl = options.baseUrl;
if (!baseUrl) {
return {};
}
const baseUrlResolved = path.resolve(paths.appPath, baseUrl);
if (path.relative(paths.appPath, baseUrlResolved) === '') {
return {
'^src/(.*)$': '<rootDir>/src/$1',
};
}
}
function getModules() {
// Check if TypeScript is setup
const hasTsConfig = fs.existsSync(paths.appTsConfig);
const hasJsConfig = fs.existsSync(paths.appJsConfig);
if (hasTsConfig && hasJsConfig) {
throw new Error(
'You have both a tsconfig.json and a jsconfig.json. If you are using TypeScript please remove your jsconfig.json file.'
);
}
let config;
// If there's a tsconfig.json we assume it's a
// TypeScript project and set up the config
// based on tsconfig.json
if (hasTsConfig) {
const ts = require(resolve.sync('typescript', {
basedir: paths.appNodeModules,
}));
config = ts.readConfigFile(paths.appTsConfig, ts.sys.readFile).config;
// Otherwise we'll check if there is jsconfig.json
// for non TS projects.
} else if (hasJsConfig) {
config = require(paths.appJsConfig);
}
config = config || {};
const options = config.compilerOptions || {};
const additionalModulePaths = getAdditionalModulePaths(options);
return {
additionalModulePaths: additionalModulePaths,
webpackAliases: getWebpackAliases(options),
jestAliases: getJestAliases(options),
hasTsConfig,
};
}
module.exports = getModules();

View File

@ -1,73 +0,0 @@
const path = require('path');
const fs = require('fs');
const getPublicUrlOrPath = require('react-dev-utils/getPublicUrlOrPath');
// Make sure any symlinks in the project folder are resolved:
// https://github.com/facebook/create-react-app/issues/637
const appDirectory = fs.realpathSync(process.cwd());
const resolveApp = relativePath => path.resolve(appDirectory, relativePath);
// We use `PUBLIC_URL` environment variable or "homepage" field to infer
// "public path" at which the app is served.
// webpack needs to know it to put the right <script> hrefs into HTML even in
// single-page apps that may serve index.html for nested URLs like /todos/42.
// We can't use a relative path in HTML because we don't want to load something
// like /todos/42/static/js/bundle.7289d.js. We have to know the root.
const publicUrlOrPath = '/';
const moduleFileExtensions = [
'web.mjs',
'mjs',
'web.js',
'js',
'web.ts',
'ts',
'web.tsx',
'tsx',
'json',
'web.jsx',
'jsx',
];
// Resolve file paths in the same order as webpack
const resolveModule = (resolveFn, filePath) => {
const extension = moduleFileExtensions.find(extension =>
fs.existsSync(resolveFn(`${filePath}.${extension}`))
);
if (extension) {
return resolveFn(`${filePath}.${extension}`);
}
return resolveFn(`${filePath}.js`);
};
// config after eject: we're in ./config/
module.exports = {
dotenv: resolveApp('.env'),
appPath: resolveApp('.'),
appBuild: resolveApp('build'),
appDemo: resolveApp('demo'),
appPublic: resolveApp('public'),
appHtml: resolveApp('public/index.html'),
appIndexJs: resolveModule(resolveApp, 'src/demo/index'),
appLibIndexJs: resolveModule(resolveApp, 'src/lib/index'),
appDemoIndexJs: resolveModule(resolveApp, 'src/demo/index'),
appPackageJson: resolveApp('package.json'),
appSrc: resolveApp('src'),
appSrcLib: resolveApp('src/lib'),
appSrcLibTypes: resolveApp('src/lib/@types'),
appSrcDemo: resolveApp('src/demo'),
appTsConfig: resolveApp('tsconfig.json'),
appJsConfig: resolveApp('jsconfig.json'),
yarnLockFile: resolveApp('yarn.lock'),
testsSetup: resolveModule(resolveApp, 'src/setupTests'),
proxySetup: resolveApp('src/setupProxy.js'),
appNodeModules: resolveApp('node_modules'),
publicUrlOrPath
};
module.exports.moduleFileExtensions = moduleFileExtensions;

View File

@ -1,35 +0,0 @@
'use strict';
const { resolveModuleName } = require('ts-pnp');
exports.resolveModuleName = (
typescript,
moduleName,
containingFile,
compilerOptions,
resolutionHost
) => {
return resolveModuleName(
moduleName,
containingFile,
compilerOptions,
resolutionHost,
typescript.resolveModuleName
);
};
exports.resolveTypeReferenceDirective = (
typescript,
moduleName,
containingFile,
compilerOptions,
resolutionHost
) => {
return resolveModuleName(
moduleName,
containingFile,
compilerOptions,
resolutionHost,
typescript.resolveTypeReferenceDirective
);
};

View File

@ -1,674 +0,0 @@
const fs = require('fs');
const path = require('path');
const webpack = require('webpack');
const resolve = require('resolve');
const PnpWebpackPlugin = require('pnp-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin');
const InlineChunkHtmlPlugin = require('react-dev-utils/InlineChunkHtmlPlugin');
const TerserPlugin = require('terser-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const safePostCssParser = require('postcss-safe-parser');
const ManifestPlugin = require('webpack-manifest-plugin');
const InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin');
const WorkboxWebpackPlugin = require('workbox-webpack-plugin');
const WatchMissingNodeModulesPlugin = require('react-dev-utils/WatchMissingNodeModulesPlugin');
const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin');
const getCSSModuleLocalIdent = require('react-dev-utils/getCSSModuleLocalIdent');
const paths = require('./paths');
const modules = require('./modules');
const getClientEnvironment = require('./env');
const ModuleNotFoundPlugin = require('react-dev-utils/ModuleNotFoundPlugin');
const ForkTsCheckerWebpackPlugin = require('react-dev-utils/ForkTsCheckerWebpackPlugin');
const typescriptFormatter = require('react-dev-utils/typescriptFormatter');
const getPackageJson = require('./getPackageJson');
const eslint = require('eslint');
const postcssNormalize = require('postcss-normalize');
const appPackageJson = require(paths.appPackageJson);
// Source maps are resource heavy and can cause out of memory issue for large source files.
const shouldUseSourceMap = process.env.GENERATE_SOURCEMAP !== 'false';
// Some apps do not need the benefits of saving a web request, so not inlining the chunk
// makes for a smoother build process.
const shouldInlineRuntimeChunk = process.env.INLINE_RUNTIME_CHUNK !== 'false';
const isExtendingEslintConfig = process.env.EXTEND_ESLINT === 'true';
const imageInlineSizeLimit = parseInt(
process.env.IMAGE_INLINE_SIZE_LIMIT || '10000'
);
// Check if TypeScript is setup
const useTypeScript = fs.existsSync(paths.appTsConfig);
// style files regexes
const cssRegex = /\.css$/;
const cssModuleRegex = /\.module\.css$/;
const sassRegex = /\.(scss|sass)$/;
const sassModuleRegex = /\.module\.(scss|sass)$/;
// This is the production and development configuration.
// It is focused on developer experience, fast rebuilds, and a minimal bundle.
module.exports = function(webpackEnv) {
const isEnvDevelopment = webpackEnv === 'development';
const isEnvProduction = webpackEnv === 'production';
// Variable used for enabling profiling in Production
// passed into alias object. Uses a flag if passed into the build command
const isEnvProductionProfile =
isEnvProduction && process.argv.includes('--profile');
// We will provide `paths.publicUrlOrPath` to our app
// as %PUBLIC_URL% in `index.html` and `process.env.PUBLIC_URL` in JavaScript.
// Omit trailing slash as %PUBLIC_URL%/xyz looks better than %PUBLIC_URL%xyz.
// Get environment variables to inject into our app.
const env = getClientEnvironment(paths.publicUrlOrPath.slice(0, -1));
// common function to get style loaders
const getStyleLoaders = (cssOptions, preProcessor) => {
const loaders = [
isEnvDevelopment && require.resolve('style-loader'),
isEnvProduction && {
loader: MiniCssExtractPlugin.loader,
// css is located in `static/css`, use '../../' to locate index.html folder
// in production `paths.publicUrlOrPath` can be a relative path
options: paths.publicUrlOrPath.startsWith('.')
? { publicPath: '../../' }
: {},
},
{
loader: require.resolve('css-loader'),
options: cssOptions,
},
{
// Options for PostCSS as we reference these options twice
// Adds vendor prefixing based on your specified browser support in
// package.json
loader: require.resolve('postcss-loader'),
options: {
// Necessary for external CSS imports to work
// https://github.com/facebook/create-react-app/issues/2677
ident: 'postcss',
plugins: () => [
require('postcss-flexbugs-fixes'),
require('postcss-preset-env')({
autoprefixer: {
flexbox: 'no-2009',
},
stage: 3,
}),
// Adds PostCSS Normalize as the reset css with default options,
// so that it honors browserslist config in package.json
// which in turn let's users customize the target behavior as per their needs.
postcssNormalize(),
],
sourceMap: isEnvProduction && shouldUseSourceMap,
},
},
].filter(Boolean);
if (preProcessor) {
loaders.push(
{
loader: require.resolve('resolve-url-loader'),
options: {
sourceMap: isEnvProduction && shouldUseSourceMap,
},
},
{
loader: require.resolve(preProcessor),
options: {
sourceMap: true,
},
}
);
}
return loaders;
};
return {
mode: 'production',
// Stop compilation early in production
bail: isEnvProduction,
devtool: isEnvProduction
? shouldUseSourceMap
? 'source-map'
: false
: isEnvDevelopment && 'cheap-module-source-map',
// These are the "entry points" to our application.
// This means they will be the "root" imports that are included in JS bundle.
entry: isEnvProduction ? [paths.appDemoIndexJs] : [
// Include an alternative client for WebpackDevServer. A client's job is to
// connect to WebpackDevServer by a socket and get notified about changes.
// When you save a file, the client will either apply hot updates (in case
// of CSS changes), or refresh the page (in case of JS changes). When you
// make a syntax error, this client will display a syntax error overlay.
// Note: instead of the default WebpackDevServer client, we use a custom one
// to bring better experience for Create React App users. You can replace
// the line below with these two lines if you prefer the stock client:
// require.resolve('webpack-dev-server/client') + '?/',
// require.resolve('webpack/hot/dev-server'),
isEnvDevelopment &&
require.resolve('react-dev-utils/webpackHotDevClient'),
// Finally, this is your app's code:
paths.appDemoIndexJs,
// We include the app code last so that if there is a runtime error during
// initialization, it doesn't blow up the WebpackDevServer client, and
// changing JS code would still trigger a refresh.
].filter(Boolean),
output: {
// The build folder.
path: isEnvProduction ? paths.appDemo : undefined,
// Add /* filename */ comments to generated require()s in the output.
pathinfo: isEnvDevelopment,
// There will be one main bundle, and one file per asynchronous chunk.
// In development, it does not produce real files.
filename: 'index.js',
// TODO: remove this when upgrading to webpack 5
futureEmitAssets: true,
// There are also additional JS chunk files if you use code splitting.
chunkFilename: isEnvProduction
? 'static/js/[name].[contenthash:8].chunk.js'
: isEnvDevelopment && 'static/js/[name].chunk.js',
// webpack uses `publicPath` to determine where the app is being served from.
// It requires a trailing slash, or the file assets will get an incorrect path.
// We inferred the "public path" (such as / or /my-project) from homepage.
publicPath: paths.publicUrlOrPath,
// Point sourcemap entries to original disk location (format as URL on Windows)
devtoolModuleFilenameTemplate: isEnvProduction
? info =>
path
.relative(paths.appSrcDemo, info.absoluteResourcePath)
.replace(/\\/g, '/')
: isEnvDevelopment &&
(info => path.resolve(info.absoluteResourcePath).replace(/\\/g, '/')),
// Prevents conflicts when multiple webpack runtimes (from different apps)
// are used on the same page.
jsonpFunction: `webpackJsonp${appPackageJson.name}`,
// this defaults to 'window', but by setting it to 'this' then
// module chunks which are built will work in web workers as well.
globalObject: 'this',
},
optimization: {
minimize: isEnvProduction,
minimizer: [
// This is only used in production mode
new TerserPlugin({
terserOptions: {
parse: {
// We want terser to parse ecma 8 code. However, we don't want it
// to apply any minification steps that turns valid ecma 5 code
// into invalid ecma 5 code. This is why the 'compress' and 'output'
// sections only apply transformations that are ecma 5 safe
// https://github.com/facebook/create-react-app/pull/4234
ecma: 8,
},
compress: {
ecma: 5,
warnings: false,
// Disabled because of an issue with Uglify breaking seemingly valid code:
// https://github.com/facebook/create-react-app/issues/2376
// Pending further investigation:
// https://github.com/mishoo/UglifyJS2/issues/2011
comparisons: false,
// Disabled because of an issue with Terser breaking valid code:
// https://github.com/facebook/create-react-app/issues/5250
// Pending further investigation:
// https://github.com/terser-js/terser/issues/120
inline: 2,
},
mangle: {
safari10: true,
keep_classnames: true,
keep_fnames: true,
module: true
},
output: {
ecma: 5,
comments: false,
// Turned on because emoji and regex is not minified properly using default
// https://github.com/facebook/create-react-app/issues/2488
ascii_only: true,
},
},
cache: true,
sourceMap: shouldUseSourceMap,
}),
new OptimizeCSSAssetsPlugin({
cssProcessorOptions: {
parser: safePostCssParser,
map: shouldUseSourceMap
? {
// `inline: false` forces the sourcemap to be output into a
// separate file
inline: false,
// `annotation: true` appends the sourceMappingURL to the end of
// the css file, helping the browser find the sourcemap
annotation: true,
}
: false,
},
cssProcessorPluginOptions: {
preset: ['default', { minifyFontValues: { removeQuotes: false } }],
},
}),
],
// Automatically split vendor and commons
// https://twitter.com/wSokra/status/969633336732905474
// https://medium.com/webpack/webpack-4-code-splitting-chunk-graph-and-the-splitchunks-optimization-be739a861366
splitChunks: {
cacheGroups: {
default: false,
}
},
// Keep the runtime chunk separated to enable long term caching
// https://twitter.com/wSokra/status/969679223278505985
// https://github.com/facebook/create-react-app/issues/5358
runtimeChunk: false,
},
resolve: {
// This allows you to set a fallback for where webpack should look for modules.
// We placed these paths second because we want `node_modules` to "win"
// if there are any conflicts. This matches Node resolution mechanism.
// https://github.com/facebook/create-react-app/issues/253
modules: ['node_modules', paths.appNodeModules].concat(
modules.additionalModulePaths || []
),
// These are the reasonable defaults supported by the Node ecosystem.
// We also include JSX as a common component filename extension to support
// some tools, although we do not recommend using it, see:
// https://github.com/facebook/create-react-app/issues/290
// `web` extension prefixes have been added for better support
// for React Native Web.
extensions: paths.moduleFileExtensions
.map(ext => `.${ext}`)
.filter(ext => useTypeScript || !ext.includes('ts')),
alias: {
// Support React Native Web
// https://www.smashingmagazine.com/2016/08/a-glimpse-into-the-future-with-react-native-for-web/
'react-native': 'react-native-web',
// Allows for better profiling with ReactDevTools
...(isEnvProductionProfile && {
'react-dom$': 'react-dom/profiling',
'scheduler/tracing': 'scheduler/tracing-profiling',
}),
...(modules.webpackAliases || {}),
},
plugins: [
// Adds support for installing with Plug'n'Play, leading to faster installs and adding
// guards against forgotten dependencies and such.
PnpWebpackPlugin,
// Prevents users from importing files from outside of src/ (or node_modules/).
// This often causes confusion because we only process files within src/ with babel.
// To fix this, we prevent you from importing files out of src/ -- if you'd like to,
// please link the files into your node_modules/ and let module-resolution kick in.
// Make sure your source files are compiled, as they will not be processed in any way.
new ModuleScopePlugin(paths.appSrc, [paths.appPackageJson]),
],
},
resolveLoader: {
plugins: [
// Also related to Plug'n'Play, but this time it tells webpack to load its loaders
// from the current package.
PnpWebpackPlugin.moduleLoader(module),
],
},
module: {
strictExportPresence: true,
rules: [
// Disable require.ensure as it's not a standard language feature.
{ parser: { requireEnsure: false } },
// First, run the linter.
// It's important to do this before Babel processes the JS.
{
test: /\.(js|mjs|jsx|ts|tsx)$/,
enforce: 'pre',
use: [
{
options: {
cache: true,
formatter: require.resolve('react-dev-utils/eslintFormatter'),
eslintPath: require.resolve('eslint'),
resolvePluginsRelativeTo: __dirname,
},
loader: require.resolve('eslint-loader'),
},
],
include: paths.appSrcDemo,
},
{
// "oneOf" will traverse all following loaders until one will
// match the requirements. When no loader matches it will fall
// back to the "file" loader at the end of the loader list.
oneOf: [
// "url" loader works like "file" loader except that it embeds assets
// smaller than specified limit in bytes as data URLs to avoid requests.
// A missing `test` is equivalent to a match.
{
test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/],
loader: require.resolve('url-loader'),
options: {
limit: imageInlineSizeLimit,
name: 'static/media/[name].[hash:8].[ext]',
},
},
// Process application JS with Babel.
// The preset includes JSX, Flow, TypeScript, and some ESnext features.
{
test: /\.(js|mjs|jsx|ts|tsx)$/,
include: paths.appSrcDemo,
loader: require.resolve('babel-loader'),
options: {
customize: require.resolve(
'babel-preset-react-app/webpack-overrides'
),
plugins: [
[
require.resolve('babel-plugin-named-asset-import'),
{
loaderMap: {
svg: {
ReactComponent:
'@svgr/webpack?-svgo,+titleProp,+ref![path]',
},
},
},
],
],
// This is a feature of `babel-loader` for webpack (not Babel itself).
// It enables caching results in ./node_modules/.cache/babel-loader/
// directory for faster rebuilds.
cacheDirectory: true,
// See #6846 for context on why cacheCompression is disabled
cacheCompression: false,
compact: isEnvProduction,
},
},
// Process any JS outside of the app with Babel.
// Unlike the application JS, we only compile the standard ES features.
{
test: /\.(js|mjs)$/,
exclude: /@babel(?:\/|\\{1,2})runtime/,
loader: require.resolve('babel-loader'),
options: {
babelrc: false,
configFile: false,
compact: false,
presets: [
[
require.resolve('babel-preset-react-app/dependencies'),
{ helpers: true },
],
"@babel/preset-env",
"@babel/preset-react"
],
plugins: [
[
"@babel/plugin-proposal-class-properties"
]
],
cacheDirectory: true,
// See #6846 for context on why cacheCompression is disabled
cacheCompression: false,
// Babel sourcemaps are needed for debugging into node_modules
// code. Without the options below, debuggers like VSCode
// show incorrect code and set breakpoints on the wrong lines.
sourceMaps: shouldUseSourceMap,
inputSourceMap: shouldUseSourceMap,
},
},
// "postcss" loader applies autoprefixer to our CSS.
// "css" loader resolves paths in CSS and adds assets as dependencies.
// "style" loader turns CSS into JS modules that inject <style> tags.
// In production, we use MiniCSSExtractPlugin to extract that CSS
// to a file, but in development "style" loader enables hot editing
// of CSS.
// By default we support CSS Modules with the extension .module.css
{
test: cssRegex,
exclude: cssModuleRegex,
use: getStyleLoaders({
importLoaders: 1,
sourceMap: isEnvProduction && shouldUseSourceMap,
}),
// Don't consider CSS imports dead code even if the
// containing package claims to have no side effects.
// Remove this when webpack adds a warning or an error for this.
// See https://github.com/webpack/webpack/issues/6571
sideEffects: true,
},
// Adds support for CSS Modules (https://github.com/css-modules/css-modules)
// using the extension .module.css
{
test: cssModuleRegex,
use: getStyleLoaders({
importLoaders: 1,
sourceMap: isEnvProduction && shouldUseSourceMap,
modules: {
getLocalIdent: getCSSModuleLocalIdent,
},
}),
},
// Opt-in support for SASS (using .scss or .sass extensions).
// By default we support SASS Modules with the
// extensions .module.scss or .module.sass
{
test: sassRegex,
exclude: sassModuleRegex,
use: getStyleLoaders(
{
importLoaders: 3,
sourceMap: isEnvProduction && shouldUseSourceMap,
},
'sass-loader'
),
// Don't consider CSS imports dead code even if the
// containing package claims to have no side effects.
// Remove this when webpack adds a warning or an error for this.
// See https://github.com/webpack/webpack/issues/6571
sideEffects: true,
},
// Adds support for CSS Modules, but using SASS
// using the extension .module.scss or .module.sass
{
test: sassModuleRegex,
use: getStyleLoaders(
{
importLoaders: 3,
sourceMap: isEnvProduction && shouldUseSourceMap,
modules: {
getLocalIdent: getCSSModuleLocalIdent,
},
},
'sass-loader'
),
},
// "file" loader makes sure those assets get served by WebpackDevServer.
// When you `import` an asset, you get its (virtual) filename.
// In production, they would get copied to the `build` folder.
// This loader doesn't use a "test" so it will catch all modules
// that fall through the other loaders.
{
loader: require.resolve('file-loader'),
// Exclude `js` files to keep "css" loader working as it injects
// its runtime that would otherwise be processed through "file" loader.
// Also exclude `html` and `json` extensions so they get processed
// by webpacks internal loaders.
exclude: [/\.(js|mjs|jsx|ts|tsx)$/, /\.html$/, /\.json$/],
options: {
name: 'static/media/[name].[hash:8].[ext]',
},
},
// ** STOP ** Are you adding a new loader?
// Make sure to add the new loader(s) before the "file" loader.
],
},
],
},
plugins: [
// Generates an `index.html` file with the <script> injected.
new HtmlWebpackPlugin(
Object.assign(
{},
{
inject: true,
template: paths.appHtml,
},
isEnvProduction
? {
minify: {
removeComments: true,
collapseWhitespace: true,
removeRedundantAttributes: true,
useShortDoctype: true,
removeEmptyAttributes: true,
removeStyleLinkTypeAttributes: true,
keepClosingSlash: true,
minifyJS: true,
minifyCSS: true,
minifyURLs: true,
},
}
: undefined
)
),
// Inlines the webpack runtime script. This script is too small to warrant
// a network request.
// https://github.com/facebook/create-react-app/issues/5358
isEnvProduction &&
shouldInlineRuntimeChunk &&
new InlineChunkHtmlPlugin(HtmlWebpackPlugin, [/runtime-.+[.]js/]),
// Makes some environment variables available in index.html.
// The public URL is available as %PUBLIC_URL% in index.html, e.g.:
// <link rel="icon" href="%PUBLIC_URL%/favicon.ico">
// It will be an empty string unless you specify "homepage"
// in `package.json`, in which case it will be the pathname of that URL.
new InterpolateHtmlPlugin(HtmlWebpackPlugin, env.raw),
// This gives some necessary context to module not found errors, such as
// the requesting resource.
new ModuleNotFoundPlugin(paths.appPath),
// Makes some environment variables available to the JS code, for example:
// if (process.env.NODE_ENV === 'production') { ... }. See `./env.js`.
// It is absolutely essential that NODE_ENV is set to production
// during a production build.
// Otherwise React will be compiled in the very slow development mode.
new webpack.DefinePlugin(env.stringified),
// This is necessary to emit hot updates (currently CSS only):
isEnvDevelopment && new webpack.HotModuleReplacementPlugin(),
// Watcher doesn't work well if you mistype casing in a path so we use
// a plugin that prints an error when you attempt to do this.
// See https://github.com/facebook/create-react-app/issues/240
isEnvDevelopment && new CaseSensitivePathsPlugin(),
// If you require a missing module and then `npm install` it, you still have
// to restart the development server for webpack to discover it. This plugin
// makes the discovery automatic so you don't have to restart.
// See https://github.com/facebook/create-react-app/issues/186
isEnvDevelopment &&
new WatchMissingNodeModulesPlugin(paths.appNodeModules),
isEnvProduction &&
new MiniCssExtractPlugin({
// Options similar to the same options in webpackOptions.output
// both options are optional
filename: 'index.css',
chunkFilename: 'index.[contenthash:8].chunk.css',
}),
// Generate an asset manifest file with the following content:
// - "files" key: Mapping of all asset filenames to their corresponding
// output file so that tools can pick it up without having to parse
// `index.html`
// - "entrypoints" key: Array of files which are included in `index.html`,
// can be used to reconstruct the HTML if necessary
/*new ManifestPlugin({
fileName: 'asset-manifest.json',
publicPath: paths.publicUrlOrPath,
generate: (seed, files, entrypoints) => {
const manifestFiles = files.reduce((manifest, file) => {
manifest[file.name] = file.path;
return manifest;
}, seed);
const entrypointFiles = entrypoints.main.filter(
fileName => !fileName.endsWith('.map')
);
return {
files: manifestFiles,
entrypoints: entrypointFiles,
};
},
}),*/
// Moment.js is an extremely popular library that bundles large locale files
// by default due to how webpack interprets its code. This is a practical
// solution that requires the user to opt into importing specific locales.
// https://github.com/jmblog/how-to-optimize-momentjs-with-webpack
// You can remove this if you don't use Moment.js:
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
// Generate a service worker script that will precache, and keep up to date,
// the HTML & assets that are part of the webpack build.
/*isEnvProduction &&
new WorkboxWebpackPlugin.GenerateSW({
clientsClaim: true,
exclude: [/\.map$/, /asset-manifest\.json$/],
importWorkboxFrom: 'cdn',
navigateFallback: paths.publicUrlOrPath + 'index.html',
navigateFallbackBlacklist: [
// Exclude URLs starting with /_, as they're likely an API call
new RegExp('^/_'),
// Exclude any URLs whose last part seems to be a file extension
// as they're likely a resource and not a SPA route.
// URLs containing a "?" character won't be blacklisted as they're likely
// a route with query params (e.g. auth callbacks).
new RegExp('/[^/?]+\\.[^/]+$'),
],
}),*/
// TypeScript type checking
useTypeScript &&
new ForkTsCheckerWebpackPlugin({
typescript: resolve.sync('typescript', {
basedir: paths.appNodeModules,
}),
async: isEnvDevelopment,
useTypescriptIncrementalApi: true,
checkSyntacticErrors: true,
resolveModuleNameModule: process.versions.pnp
? `${__dirname}/pnpTs.js`
: undefined,
resolveTypeReferenceDirectiveModule: process.versions.pnp
? `${__dirname}/pnpTs.js`
: undefined,
tsconfig: paths.appTsConfig,
reportFiles: [
'**',
'!**/__tests__/**',
'!**/?(*.)(spec|test).*',
'!**/src/setupProxy.*',
'!**/src/setupTests.*',
],
watch: paths.appSrcDemo,
silent: true,
// The formatter is invoked directly in WebpackDevServerUtils during development
formatter: isEnvProduction ? typescriptFormatter : undefined,
}),
].filter(Boolean),
// Some libraries import Node modules but don't use them in the browser.
// Tell webpack to provide empty mocks for them so importing them works.
node: {
module: 'empty',
dgram: 'empty',
dns: 'mock',
fs: 'empty',
http2: 'empty',
net: 'empty',
tls: 'empty',
child_process: 'empty',
},
// Turn off performance processing because we utilize
// our own hints via the FileSizeReporter
performance: false,
};
};

View File

@ -1,712 +0,0 @@
const fs = require('fs');
const path = require('path');
const webpack = require('webpack');
const resolve = require('resolve');
const PnpWebpackPlugin = require('pnp-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin');
const InlineChunkHtmlPlugin = require('react-dev-utils/InlineChunkHtmlPlugin');
const TerserPlugin = require('terser-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const safePostCssParser = require('postcss-safe-parser');
const ManifestPlugin = require('webpack-manifest-plugin');
const InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin');
const WorkboxWebpackPlugin = require('workbox-webpack-plugin');
const WatchMissingNodeModulesPlugin = require('react-dev-utils/WatchMissingNodeModulesPlugin');
const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin');
const getCSSModuleLocalIdent = require('react-dev-utils/getCSSModuleLocalIdent');
const paths = require('./paths');
const modules = require('./modules');
const getClientEnvironment = require('./env');
const ModuleNotFoundPlugin = require('react-dev-utils/ModuleNotFoundPlugin');
const ForkTsCheckerWebpackPlugin = require('react-dev-utils/ForkTsCheckerWebpackPlugin');
const typescriptFormatter = require('react-dev-utils/typescriptFormatter');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const getPackageJson = require('./getPackageJson');
const PrettierPlugin = require("prettier-webpack-plugin");
const eslint = require('eslint');
const postcssNormalize = require('postcss-normalize');
const appPackageJson = require(paths.appPackageJson);
// Source maps are resource heavy and can cause out of memory issue for large source files.
const shouldUseSourceMap = process.env.GENERATE_SOURCEMAP !== 'false';
// Some apps do not need the benefits of saving a web request, so not inlining the chunk
// makes for a smoother build process.
const shouldInlineRuntimeChunk = process.env.INLINE_RUNTIME_CHUNK !== 'false';
const isExtendingEslintConfig = process.env.EXTEND_ESLINT === 'true';
const imageInlineSizeLimit = parseInt(
process.env.IMAGE_INLINE_SIZE_LIMIT || '10000'
);
// Check if TypeScript is setup
const useTypeScript = fs.existsSync(paths.appTsConfig);
// style files regexes
const cssRegex = /\.css$/;
const cssModuleRegex = /\.module\.css$/;
const sassRegex = /\.(scss|sass)$/;
const sassModuleRegex = /\.module\.(scss|sass)$/;
const {
version,
name,
license,
repository,
author,
} = getPackageJson('version', 'name', 'license', 'repository', 'author');
const banner = `
${name} v${version}
${repository.url}
Copyright (c) ${author.replace(/ *<[^)]*> */g, " ")}
This source code is licensed under the ${license} license found in the
LICENSE file in the root directory of this source tree.
`;
// This is the production and development configuration.
// It is focused on developer experience, fast rebuilds, and a minimal bundle.
module.exports = function(webpackEnv) {
const isEnvDevelopment = webpackEnv === 'development';
const isEnvProduction = webpackEnv === 'production';
// Variable used for enabling profiling in Production
// passed into alias object. Uses a flag if passed into the build command
const isEnvProductionProfile =
isEnvProduction && process.argv.includes('--profile');
// We will provide `paths.publicUrlOrPath` to our app
// as %PUBLIC_URL% in `index.html` and `process.env.PUBLIC_URL` in JavaScript.
// Omit trailing slash as %PUBLIC_URL%/xyz looks better than %PUBLIC_URL%xyz.
// Get environment variables to inject into our app.
const env = getClientEnvironment(paths.publicUrlOrPath.slice(0, -1));
// common function to get style loaders
const getStyleLoaders = (cssOptions, preProcessor) => {
const loaders = [
isEnvDevelopment && require.resolve('style-loader'),
isEnvProduction && {
loader: MiniCssExtractPlugin.loader,
// css is located in `static/css`, use '../../' to locate index.html folder
// in production `paths.publicUrlOrPath` can be a relative path
options: paths.publicUrlOrPath.startsWith('.')
? { publicPath: '../../' }
: {},
},
{
loader: require.resolve('css-loader'),
options: cssOptions,
},
{
// Options for PostCSS as we reference these options twice
// Adds vendor prefixing based on your specified browser support in
// package.json
loader: require.resolve('postcss-loader'),
options: {
// Necessary for external CSS imports to work
// https://github.com/facebook/create-react-app/issues/2677
ident: 'postcss',
plugins: () => [
require('postcss-flexbugs-fixes'),
require('postcss-preset-env')({
autoprefixer: {
flexbox: 'no-2009',
},
stage: 3,
}),
// Adds PostCSS Normalize as the reset css with default options,
// so that it honors browserslist config in package.json
// which in turn let's users customize the target behavior as per their needs.
postcssNormalize(),
],
sourceMap: isEnvProduction && shouldUseSourceMap,
},
},
].filter(Boolean);
if (preProcessor) {
loaders.push(
{
loader: require.resolve('resolve-url-loader'),
options: {
sourceMap: isEnvProduction && shouldUseSourceMap,
},
},
{
loader: require.resolve(preProcessor),
options: {
sourceMap: true,
},
}
);
}
return loaders;
};
return {
mode: isEnvProduction ? 'production' : isEnvDevelopment && 'development',
// Stop compilation early in production
bail: isEnvProduction,
devtool: isEnvProduction
? shouldUseSourceMap
? 'source-map'
: false
: isEnvDevelopment && 'cheap-module-source-map',
// These are the "entry points" to our application.
// This means they will be the "root" imports that are included in JS bundle.
entry: isEnvProduction ? [paths.appLibIndexJs] : [
// Include an alternative client for WebpackDevServer. A client's job is to
// connect to WebpackDevServer by a socket and get notified about changes.
// When you save a file, the client will either apply hot updates (in case
// of CSS changes), or refresh the page (in case of JS changes). When you
// make a syntax error, this client will display a syntax error overlay.
// Note: instead of the default WebpackDevServer client, we use a custom one
// to bring better experience for Create React App users. You can replace
// the line below with these two lines if you prefer the stock client:
// require.resolve('webpack-dev-server/client') + '?/',
// require.resolve('webpack/hot/dev-server'),
isEnvDevelopment &&
require.resolve('react-dev-utils/webpackHotDevClient'),
// Finally, this is your app's code:
paths.appIndexJs,
// We include the app code last so that if there is a runtime error during
// initialization, it doesn't blow up the WebpackDevServer client, and
// changing JS code would still trigger a refresh.
].filter(Boolean),
output: {
// The build folder.
path: isEnvProduction ? paths.appBuild : undefined,
// Add /* filename */ comments to generated require()s in the output.
pathinfo: isEnvDevelopment,
// There will be one main bundle, and one file per asynchronous chunk.
// In development, it does not produce real files.
filename: isEnvProduction
? 'index.js'
: isEnvDevelopment && 'static/js/bundle.js',
// TODO: remove this when upgrading to webpack 5
futureEmitAssets: true,
// There are also additional JS chunk files if you use code splitting.
chunkFilename: isEnvProduction
? 'static/js/[name].[contenthash:8].chunk.js'
: isEnvDevelopment && 'static/js/[name].chunk.js',
// webpack uses `publicPath` to determine where the app is being served from.
// It requires a trailing slash, or the file assets will get an incorrect path.
// We inferred the "public path" (such as / or /my-project) from homepage.
publicPath: paths.publicUrlOrPath,
library: "SimpleKeyboard",
libraryTarget: 'umd',
umdNamedDefine: true,
// Point sourcemap entries to original disk location (format as URL on Windows)
devtoolModuleFilenameTemplate: isEnvProduction
? info =>
path
.relative(paths.appSrcLib, info.absoluteResourcePath)
.replace(/\\/g, '/')
: isEnvDevelopment &&
(info => path.resolve(info.absoluteResourcePath).replace(/\\/g, '/')),
// Prevents conflicts when multiple webpack runtimes (from different apps)
// are used on the same page.
jsonpFunction: `webpackJsonp${appPackageJson.name}`,
// this defaults to 'window', but by setting it to 'this' then
// module chunks which are built will work in web workers as well.
globalObject: 'this',
},
optimization: {
minimize: isEnvProduction,
minimizer: [
// This is only used in production mode
new TerserPlugin({
terserOptions: {
parse: {
// We want terser to parse ecma 8 code. However, we don't want it
// to apply any minification steps that turns valid ecma 5 code
// into invalid ecma 5 code. This is why the 'compress' and 'output'
// sections only apply transformations that are ecma 5 safe
// https://github.com/facebook/create-react-app/pull/4234
ecma: 8,
},
compress: {
ecma: 5,
warnings: false,
// Disabled because of an issue with Uglify breaking seemingly valid code:
// https://github.com/facebook/create-react-app/issues/2376
// Pending further investigation:
// https://github.com/mishoo/UglifyJS2/issues/2011
comparisons: false,
// Disabled because of an issue with Terser breaking valid code:
// https://github.com/facebook/create-react-app/issues/5250
// Pending further investigation:
// https://github.com/terser-js/terser/issues/120
inline: 2,
},
mangle: {
safari10: true,
keep_classnames: true,
keep_fnames: true,
module: true
},
// Added for profiling in devtools
keep_classnames: isEnvProductionProfile,
keep_fnames: isEnvProductionProfile,
output: {
ecma: 5,
comments: false,
// Turned on because emoji and regex is not minified properly using default
// https://github.com/facebook/create-react-app/issues/2488
ascii_only: true,
},
},
cache: true,
sourceMap: shouldUseSourceMap,
}),
// This is only used in production mode
new webpack.BannerPlugin({
banner: banner,
entryOnly: true
}),
new OptimizeCSSAssetsPlugin({
cssProcessorOptions: {
parser: safePostCssParser,
map: shouldUseSourceMap
? {
// `inline: false` forces the sourcemap to be output into a
// separate file
inline: false,
// `annotation: true` appends the sourceMappingURL to the end of
// the css file, helping the browser find the sourcemap
annotation: true,
}
: false,
},
cssProcessorPluginOptions: {
preset: ['default', { minifyFontValues: { removeQuotes: false } }],
},
}),
],
// Automatically split vendor and commons
// https://twitter.com/wSokra/status/969633336732905474
// https://medium.com/webpack/webpack-4-code-splitting-chunk-graph-and-the-splitchunks-optimization-be739a861366
splitChunks: {
cacheGroups: {
default: false,
}
},
// Keep the runtime chunk separated to enable long term caching
// https://twitter.com/wSokra/status/969679223278505985
// https://github.com/facebook/create-react-app/issues/5358
runtimeChunk: false,
},
resolve: {
// This allows you to set a fallback for where webpack should look for modules.
// We placed these paths second because we want `node_modules` to "win"
// if there are any conflicts. This matches Node resolution mechanism.
// https://github.com/facebook/create-react-app/issues/253
modules: ['node_modules', paths.appNodeModules].concat(
modules.additionalModulePaths || []
),
// These are the reasonable defaults supported by the Node ecosystem.
// We also include JSX as a common component filename extension to support
// some tools, although we do not recommend using it, see:
// https://github.com/facebook/create-react-app/issues/290
// `web` extension prefixes have been added for better support
// for React Native Web.
extensions: paths.moduleFileExtensions
.map(ext => `.${ext}`)
.filter(ext => useTypeScript || !ext.includes('ts')),
alias: {
// Support React Native Web
// https://www.smashingmagazine.com/2016/08/a-glimpse-into-the-future-with-react-native-for-web/
'react-native': 'react-native-web',
// Allows for better profiling with ReactDevTools
...(isEnvProductionProfile && {
'react-dom$': 'react-dom/profiling',
'scheduler/tracing': 'scheduler/tracing-profiling',
}),
...(modules.webpackAliases || {}),
},
plugins: [
// Adds support for installing with Plug'n'Play, leading to faster installs and adding
// guards against forgotten dependencies and such.
PnpWebpackPlugin,
// Prevents users from importing files from outside of src/ (or node_modules/).
// This often causes confusion because we only process files within src/ with babel.
// To fix this, we prevent you from importing files out of src/ -- if you'd like to,
// please link the files into your node_modules/ and let module-resolution kick in.
// Make sure your source files are compiled, as they will not be processed in any way.
new ModuleScopePlugin(paths.appSrc, [paths.appPackageJson]),
],
},
resolveLoader: {
plugins: [
// Also related to Plug'n'Play, but this time it tells webpack to load its loaders
// from the current package.
PnpWebpackPlugin.moduleLoader(module),
],
},
module: {
strictExportPresence: true,
rules: [
// Disable require.ensure as it's not a standard language feature.
{ parser: { requireEnsure: false } },
// First, run the linter.
// It's important to do this before Babel processes the JS.
{
test: /\.(js|mjs|jsx|ts|tsx)$/,
enforce: 'pre',
use: [
{
options: {
cache: true,
formatter: require.resolve('react-dev-utils/eslintFormatter'),
eslintPath: require.resolve('eslint'),
resolvePluginsRelativeTo: __dirname,
},
loader: require.resolve('eslint-loader'),
},
],
include: paths.appSrcLib,
},
{
// "oneOf" will traverse all following loaders until one will
// match the requirements. When no loader matches it will fall
// back to the "file" loader at the end of the loader list.
oneOf: [
// "url" loader works like "file" loader except that it embeds assets
// smaller than specified limit in bytes as data URLs to avoid requests.
// A missing `test` is equivalent to a match.
{
test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/],
loader: require.resolve('url-loader'),
options: {
limit: imageInlineSizeLimit,
name: 'static/media/[name].[hash:8].[ext]',
},
},
// Process application JS with Babel.
// The preset includes JSX, Flow, TypeScript, and some ESnext features.
{
test: /\.(js|mjs|jsx|ts|tsx)$/,
include: paths.appSrcLib,
loader: require.resolve('babel-loader'),
options: {
customize: require.resolve(
'babel-preset-react-app/webpack-overrides'
),
plugins: [
[
require.resolve('babel-plugin-named-asset-import'),
{
loaderMap: {
svg: {
ReactComponent:
'@svgr/webpack?-svgo,+titleProp,+ref![path]',
},
},
},
],
],
// This is a feature of `babel-loader` for webpack (not Babel itself).
// It enables caching results in ./node_modules/.cache/babel-loader/
// directory for faster rebuilds.
cacheDirectory: true,
// See #6846 for context on why cacheCompression is disabled
cacheCompression: false,
compact: isEnvProduction,
},
},
// Process any JS outside of the app with Babel.
// Unlike the application JS, we only compile the standard ES features.
{
test: /\.(js|mjs)$/,
exclude: /@babel(?:\/|\\{1,2})runtime/,
loader: require.resolve('babel-loader'),
options: {
babelrc: false,
configFile: false,
compact: false,
presets: [
[
require.resolve('babel-preset-react-app/dependencies'),
{ helpers: true },
],
"@babel/preset-env",
"@babel/preset-react"
],
plugins: [
[
"@babel/plugin-proposal-class-properties"
]
],
cacheDirectory: true,
// See #6846 for context on why cacheCompression is disabled
cacheCompression: false,
// Babel sourcemaps are needed for debugging into node_modules
// code. Without the options below, debuggers like VSCode
// show incorrect code and set breakpoints on the wrong lines.
sourceMaps: shouldUseSourceMap,
inputSourceMap: shouldUseSourceMap,
},
},
// "postcss" loader applies autoprefixer to our CSS.
// "css" loader resolves paths in CSS and adds assets as dependencies.
// "style" loader turns CSS into JS modules that inject <style> tags.
// In production, we use MiniCSSExtractPlugin to extract that CSS
// to a file, but in development "style" loader enables hot editing
// of CSS.
// By default we support CSS Modules with the extension .module.css
{
test: cssRegex,
exclude: cssModuleRegex,
use: getStyleLoaders({
importLoaders: 1,
sourceMap: isEnvProduction && shouldUseSourceMap,
}),
// Don't consider CSS imports dead code even if the
// containing package claims to have no side effects.
// Remove this when webpack adds a warning or an error for this.
// See https://github.com/webpack/webpack/issues/6571
sideEffects: true,
},
// Adds support for CSS Modules (https://github.com/css-modules/css-modules)
// using the extension .module.css
{
test: cssModuleRegex,
use: getStyleLoaders({
importLoaders: 1,
sourceMap: isEnvProduction && shouldUseSourceMap,
modules: {
getLocalIdent: getCSSModuleLocalIdent,
},
}),
},
// Opt-in support for SASS (using .scss or .sass extensions).
// By default we support SASS Modules with the
// extensions .module.scss or .module.sass
{
test: sassRegex,
exclude: sassModuleRegex,
use: getStyleLoaders(
{
importLoaders: 3,
sourceMap: isEnvProduction && shouldUseSourceMap,
},
'sass-loader'
),
// Don't consider CSS imports dead code even if the
// containing package claims to have no side effects.
// Remove this when webpack adds a warning or an error for this.
// See https://github.com/webpack/webpack/issues/6571
sideEffects: true,
},
// Adds support for CSS Modules, but using SASS
// using the extension .module.scss or .module.sass
{
test: sassModuleRegex,
use: getStyleLoaders(
{
importLoaders: 3,
sourceMap: isEnvProduction && shouldUseSourceMap,
modules: {
getLocalIdent: getCSSModuleLocalIdent,
},
},
'sass-loader'
),
},
// "file" loader makes sure those assets get served by WebpackDevServer.
// When you `import` an asset, you get its (virtual) filename.
// In production, they would get copied to the `build` folder.
// This loader doesn't use a "test" so it will catch all modules
// that fall through the other loaders.
{
loader: require.resolve('file-loader'),
// Exclude `js` files to keep "css" loader working as it injects
// its runtime that would otherwise be processed through "file" loader.
// Also exclude `html` and `json` extensions so they get processed
// by webpacks internal loaders.
exclude: [/\.(js|mjs|jsx|ts|tsx)$/, /\.html$/, /\.json$/],
options: {
name: 'static/media/[name].[hash:8].[ext]',
},
},
// ** STOP ** Are you adding a new loader?
// Make sure to add the new loader(s) before the "file" loader.
],
},
],
},
plugins: [
isEnvDevelopment &&
// Generates an `index.html` file with the <script> injected.
new HtmlWebpackPlugin(
Object.assign(
{},
{
inject: true,
template: paths.appHtml,
},
isEnvProduction
? {
minify: {
removeComments: true,
collapseWhitespace: true,
removeRedundantAttributes: true,
useShortDoctype: true,
removeEmptyAttributes: true,
removeStyleLinkTypeAttributes: true,
keepClosingSlash: true,
minifyJS: true,
minifyCSS: true,
minifyURLs: true,
},
}
: undefined
)
),
// Inlines the webpack runtime script. This script is too small to warrant
// a network request.
// https://github.com/facebook/create-react-app/issues/5358
isEnvProduction &&
shouldInlineRuntimeChunk &&
new InlineChunkHtmlPlugin(HtmlWebpackPlugin, [/runtime-.+[.]js/]),
// Makes some environment variables available in index.html.
// The public URL is available as %PUBLIC_URL% in index.html, e.g.:
// <link rel="icon" href="%PUBLIC_URL%/favicon.ico">
// It will be an empty string unless you specify "homepage"
// in `package.json`, in which case it will be the pathname of that URL.
new InterpolateHtmlPlugin(HtmlWebpackPlugin, env.raw),
// This gives some necessary context to module not found errors, such as
// the requesting resource.
new ModuleNotFoundPlugin(paths.appPath),
// Makes some environment variables available to the JS code, for example:
// if (process.env.NODE_ENV === 'production') { ... }. See `./env.js`.
// It is absolutely essential that NODE_ENV is set to production
// during a production build.
// Otherwise React will be compiled in the very slow development mode.
new webpack.DefinePlugin(env.stringified),
// This is necessary to emit hot updates (currently CSS only):
isEnvDevelopment && new webpack.HotModuleReplacementPlugin(),
// Watcher doesn't work well if you mistype casing in a path so we use
// a plugin that prints an error when you attempt to do this.
// See https://github.com/facebook/create-react-app/issues/240
isEnvDevelopment && new CaseSensitivePathsPlugin(),
// If you require a missing module and then `npm install` it, you still have
// to restart the development server for webpack to discover it. This plugin
// makes the discovery automatic so you don't have to restart.
// See https://github.com/facebook/create-react-app/issues/186
isEnvDevelopment &&
new WatchMissingNodeModulesPlugin(paths.appNodeModules),
isEnvProduction &&
new MiniCssExtractPlugin({
// Options similar to the same options in webpackOptions.output
// both options are optional
filename: 'css/index.css',
chunkFilename: 'css/index.[contenthash:8].chunk.css',
}),
// Generate an asset manifest file with the following content:
// - "files" key: Mapping of all asset filenames to their corresponding
// output file so that tools can pick it up without having to parse
// `index.html`
// - "entrypoints" key: Array of files which are included in `index.html`,
// can be used to reconstruct the HTML if necessary
/*new ManifestPlugin({
fileName: 'asset-manifest.json',
publicPath: paths.publicUrlOrPath,
generate: (seed, files, entrypoints) => {
const manifestFiles = files.reduce((manifest, file) => {
manifest[file.name] = file.path;
return manifest;
}, seed);
const entrypointFiles = entrypoints.main.filter(
fileName => !fileName.endsWith('.map')
);
return {
files: manifestFiles,
entrypoints: entrypointFiles,
};
},
}),*/
// Moment.js is an extremely popular library that bundles large locale files
// by default due to how webpack interprets its code. This is a practical
// solution that requires the user to opt into importing specific locales.
// https://github.com/jmblog/how-to-optimize-momentjs-with-webpack
// You can remove this if you don't use Moment.js:
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
new CopyWebpackPlugin([
{
from: `${paths.appSrcLibTypes}`,
to: paths.appBuild
}
]),
new PrettierPlugin(),
// Generate a service worker script that will precache, and keep up to date,
// the HTML & assets that are part of the webpack build.
/*isEnvProduction &&
new WorkboxWebpackPlugin.GenerateSW({
clientsClaim: true,
exclude: [/\.map$/, /asset-manifest\.json$/],
importWorkboxFrom: 'cdn',
navigateFallback: paths.publicUrlOrPath + 'index.html',
navigateFallbackBlacklist: [
// Exclude URLs starting with /_, as they're likely an API call
new RegExp('^/_'),
// Exclude any URLs whose last part seems to be a file extension
// as they're likely a resource and not a SPA route.
// URLs containing a "?" character won't be blacklisted as they're likely
// a route with query params (e.g. auth callbacks).
new RegExp('/[^/?]+\\.[^/]+$'),
],
}),*/
// TypeScript type checking
useTypeScript &&
new ForkTsCheckerWebpackPlugin({
typescript: resolve.sync('typescript', {
basedir: paths.appNodeModules,
}),
async: isEnvDevelopment,
useTypescriptIncrementalApi: true,
checkSyntacticErrors: true,
resolveModuleNameModule: process.versions.pnp
? `${__dirname}/pnpTs.js`
: undefined,
resolveTypeReferenceDirectiveModule: process.versions.pnp
? `${__dirname}/pnpTs.js`
: undefined,
tsconfig: paths.appTsConfig,
reportFiles: [
'**',
'!**/__tests__/**',
'!**/?(*.)(spec|test).*',
'!**/src/setupProxy.*',
'!**/src/setupTests.*',
],
silent: true,
// The formatter is invoked directly in WebpackDevServerUtils during development
formatter: isEnvProduction ? typescriptFormatter : undefined,
}),
].filter(Boolean),
// Some libraries import Node modules but don't use them in the browser.
// Tell webpack to provide empty mocks for them so importing them works.
node: {
module: 'empty',
dgram: 'empty',
dns: 'mock',
fs: 'empty',
http2: 'empty',
net: 'empty',
tls: 'empty',
child_process: 'empty',
},
// Turn off performance processing because we utilize
// our own hints via the FileSizeReporter
performance: false,
};
};

View File

@ -1,130 +0,0 @@
'use strict';
const fs = require('fs');
const errorOverlayMiddleware = require('react-dev-utils/errorOverlayMiddleware');
const evalSourceMapMiddleware = require('react-dev-utils/evalSourceMapMiddleware');
const noopServiceWorkerMiddleware = require('react-dev-utils/noopServiceWorkerMiddleware');
const ignoredFiles = require('react-dev-utils/ignoredFiles');
const redirectServedPath = require('react-dev-utils/redirectServedPathMiddleware');
const paths = require('./paths');
const getHttpsConfig = require('./getHttpsConfig');
const host = process.env.HOST || '0.0.0.0';
const sockHost = process.env.WDS_SOCKET_HOST;
const sockPath = process.env.WDS_SOCKET_PATH; // default: '/sockjs-node'
const sockPort = process.env.WDS_SOCKET_PORT;
module.exports = function(proxy, allowedHost) {
return {
// WebpackDevServer 2.4.3 introduced a security fix that prevents remote
// websites from potentially accessing local content through DNS rebinding:
// https://github.com/webpack/webpack-dev-server/issues/887
// https://medium.com/webpack/webpack-dev-server-middleware-security-issues-1489d950874a
// However, it made several existing use cases such as development in cloud
// environment or subdomains in development significantly more complicated:
// https://github.com/facebook/create-react-app/issues/2271
// https://github.com/facebook/create-react-app/issues/2233
// While we're investigating better solutions, for now we will take a
// compromise. Since our WDS configuration only serves files in the `public`
// folder we won't consider accessing them a vulnerability. However, if you
// use the `proxy` feature, it gets more dangerous because it can expose
// remote code execution vulnerabilities in backends like Django and Rails.
// So we will disable the host check normally, but enable it if you have
// specified the `proxy` setting. Finally, we let you override it if you
// really know what you're doing with a special environment variable.
disableHostCheck:
!proxy || process.env.DANGEROUSLY_DISABLE_HOST_CHECK === 'true',
// Enable gzip compression of generated files.
compress: true,
// Silence WebpackDevServer's own logs since they're generally not useful.
// It will still show compile warnings and errors with this setting.
clientLogLevel: 'none',
// By default WebpackDevServer serves physical files from current directory
// in addition to all the virtual build products that it serves from memory.
// This is confusing because those files wont automatically be available in
// production build folder unless we copy them. However, copying the whole
// project directory is dangerous because we may expose sensitive files.
// Instead, we establish a convention that only files in `public` directory
// get served. Our build script will copy `public` into the `build` folder.
// In `index.html`, you can get URL of `public` folder with %PUBLIC_URL%:
// <link rel="icon" href="%PUBLIC_URL%/favicon.ico">
// In JavaScript code, you can access it with `process.env.PUBLIC_URL`.
// Note that we only recommend to use `public` folder as an escape hatch
// for files like `favicon.ico`, `manifest.json`, and libraries that are
// for some reason broken when imported through webpack. If you just want to
// use an image, put it in `src` and `import` it from JavaScript instead.
contentBase: paths.appPublic,
contentBasePublicPath: paths.publicUrlOrPath,
// By default files from `contentBase` will not trigger a page reload.
watchContentBase: true,
// Enable hot reloading server. It will provide WDS_SOCKET_PATH endpoint
// for the WebpackDevServer client so it can learn when the files were
// updated. The WebpackDevServer client is included as an entry point
// in the webpack development configuration. Note that only changes
// to CSS are currently hot reloaded. JS changes will refresh the browser.
hot: true,
// Use 'ws' instead of 'sockjs-node' on server since we're using native
// websockets in `webpackHotDevClient`.
transportMode: 'ws',
// Prevent a WS client from getting injected as we're already including
// `webpackHotDevClient`.
injectClient: false,
// Enable custom sockjs pathname for websocket connection to hot reloading server.
// Enable custom sockjs hostname, pathname and port for websocket connection
// to hot reloading server.
sockHost,
sockPath,
sockPort,
// It is important to tell WebpackDevServer to use the same "publicPath" path as
// we specified in the webpack config. When homepage is '.', default to serving
// from the root.
// remove last slash so user can land on `/test` instead of `/test/`
publicPath: paths.publicUrlOrPath.slice(0, -1),
// WebpackDevServer is noisy by default so we emit custom message instead
// by listening to the compiler events with `compiler.hooks[...].tap` calls above.
quiet: true,
// Reportedly, this avoids CPU overload on some systems.
// https://github.com/facebook/create-react-app/issues/293
// src/node_modules is not ignored to support absolute imports
// https://github.com/facebook/create-react-app/issues/1065
watchOptions: {
ignored: ignoredFiles(paths.appSrc),
},
https: getHttpsConfig(),
host,
overlay: false,
historyApiFallback: {
// Paths with dots should still use the history fallback.
// See https://github.com/facebook/create-react-app/issues/387.
disableDotRule: true,
index: paths.publicUrlOrPath,
},
public: allowedHost,
// `proxy` is run between `before` and `after` `webpack-dev-server` hooks
proxy,
before(app, server) {
// Keep `evalSourceMapMiddleware` and `errorOverlayMiddleware`
// middlewares before `redirectServedPath` otherwise will not have any effect
// This lets us fetch source contents from webpack for the error overlay
app.use(evalSourceMapMiddleware(server));
// This lets us open files from the runtime error overlay.
app.use(errorOverlayMiddleware());
if (fs.existsSync(paths.proxySetup)) {
// This registers user provided middleware for proxy reasons
require(paths.proxySetup)(app);
}
},
after(app) {
// Redirect to `PUBLIC_URL` or `homepage` from `package.json` if url not match
app.use(redirectServedPath(paths.publicUrlOrPath));
// This service worker file is effectively a 'no-op' that will reset any
// previous service worker registered for the same host:port combination.
// We do this in development to avoid hitting the production cache if
// it used the same host and port.
// https://github.com/facebook/create-react-app/issues/2272#issuecomment-302832432
app.use(noopServiceWorkerMiddleware(paths.publicUrlOrPath));
},
};
};

12
demo/index.html Normal file
View File

@ -0,0 +1,12 @@
<html>
<head>
<link rel="stylesheet" href="css/index.css">
<link rel="stylesheet" href="main.css">
</head>
<body>
<input class="input" placeholder="Tap on the virtual keyboard to start" />
<div class="simple-keyboard"></div>
<script src="index.js"></script>
<script src="main.js"></script>
</body>
</html>

12
demo/main.css Normal file
View File

@ -0,0 +1,12 @@
input {
width: 100%;
height: 100px;
padding: 20px;
font-size: 20px;
border: none;
box-sizing: border-box;
}
.simple-keyboard {
max-width: 850px;
}

38
demo/main.js Normal file
View File

@ -0,0 +1,38 @@
const Keyboard = window.SimpleKeyboard.default;
const keyboard = new Keyboard({
onChange: input => onChange(input),
onKeyPress: button => onKeyPress(button)
});
/**
* Update simple-keyboard when input is changed directly
*/
document.querySelector(".input").addEventListener("input", event => {
keyboard.setInput(event.target.value);
});
console.log(keyboard);
function onChange(input) {
document.querySelector(".input").value = input;
console.log("Input changed", input);
}
function onKeyPress(button) {
console.log("Button pressed", button);
/**
* If you want to handle the shift and caps lock buttons
*/
if (button === "{shift}" || button === "{lock}") handleShift();
}
function handleShift() {
const currentLayout = keyboard.options.layoutName;
const shiftToggle = currentLayout === "default" ? "shift" : "default";
keyboard.setOptions({
layoutName: shiftToggle
});
}

19019
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,15 +1,14 @@
{ {
"name": "simple-keyboard", "name": "simple-keyboard",
"version": "2.32.119", "version": "3.0.0-beta.0",
"description": "On-screen Javascript Virtual Keyboard", "description": "On-screen Javascript Virtual Keyboard",
"main": "build/index.js", "main": "build/index.js",
"types": "build/index.d.ts", "types": "build/index.d.ts",
"scripts": { "scripts": {
"start": "node scripts/start.js", "start": "webpack serve",
"build": "node scripts/build.js", "build": "webpack && tsc",
"demo": "node scripts/demo.js", "test": "jest --silent",
"test": "node scripts/test.js", "coverage": "npm run test -- --coverage",
"coverage": "node scripts/test.js --coverage --watchAll=false",
"prepare": "npm run build", "prepare": "npm run build",
"trypublish": "npm publish || true" "trypublish": "npm publish || true"
}, },
@ -40,85 +39,32 @@
], ],
"license": "MIT", "license": "MIT",
"devDependencies": { "devDependencies": {
"@babel/core": "7.13.8", "@babel/cli": "^7.13.0",
"@babel/core": "^7.13.8",
"@babel/plugin-proposal-class-properties": "^7.13.0", "@babel/plugin-proposal-class-properties": "^7.13.0",
"@babel/plugin-transform-typescript": "^7.13.0",
"@babel/polyfill": "^7.12.1",
"@babel/preset-env": "^7.13.9", "@babel/preset-env": "^7.13.9",
"@babel/preset-react": "^7.12.13", "@typescript-eslint/eslint-plugin": "^4.16.1",
"@svgr/webpack": "5.5.0", "@typescript-eslint/parser": "^4.16.1",
"@testing-library/jest-dom": "^5.11.9",
"@testing-library/react": "^11.2.5",
"@testing-library/user-event": "^12.8.1",
"@typescript-eslint/eslint-plugin": "^2.10.0",
"@typescript-eslint/parser": "^2.10.0",
"babel-eslint": "^10.1.0", "babel-eslint": "^10.1.0",
"babel-jest": "^26.6.3", "babel-loader": "^8.2.2",
"babel-loader": "8.2.2", "babel-preset-minify": "^0.5.0",
"babel-plugin-named-asset-import": "^0.3.7", "css-loader": "^5.1.1",
"babel-preset-react-app": "^10.0.0", "eslint": "^7.21.0",
"camelcase": "^6.2.0", "file-loader": "^6.2.0",
"case-sensitive-paths-webpack-plugin": "2.4.0", "jest": "^26.6.3",
"copy-webpack-plugin": "^5.1.2", "mini-css-extract-plugin": "^1.3.9",
"css-loader": "5.1.1", "optimize-css-assets-webpack-plugin": "^5.0.4",
"dotenv": "8.2.0", "prettier": "^2.2.1",
"dotenv-expand": "5.1.0",
"eslint": "^6.8.0",
"eslint-config-react-app": "^5.0.1",
"eslint-loader": "4.0.2",
"eslint-plugin-flowtype": "4.7.0",
"eslint-plugin-import": "2.22.1",
"eslint-plugin-jsx-a11y": "6.4.1",
"eslint-plugin-react": "7.22.0",
"eslint-plugin-react-hooks": "^4.2.0",
"file-loader": "6.2.0",
"fs-extra": "9.1.0",
"html-webpack-plugin": "4.5.2",
"identity-obj-proxy": "3.0.0",
"jest": "26.6.3",
"jest-environment-jsdom-fourteen": "1.0.1",
"jest-resolve": "26.6.2",
"jest-watch-typeahead": "0.6.1",
"mini-css-extract-plugin": "1.3.9",
"optimize-css-assets-webpack-plugin": "5.0.4",
"pnp-webpack-plugin": "1.6.4",
"postcss-flexbugs-fixes": "4.2.1",
"postcss-loader": "3.0.0",
"postcss-normalize": "9.0.0",
"postcss-preset-env": "6.7.0",
"postcss-safe-parser": "5.0.2",
"prettier": "^1.19.1",
"prettier-webpack-plugin": "^1.2.0", "prettier-webpack-plugin": "^1.2.0",
"react": "^17.0.1", "style-loader": "^2.0.0",
"react-app-polyfill": "^2.0.0", "terser-webpack-plugin": "^5.1.1",
"react-dev-utils": "^11.0.3", "typescript": "^4.2.3",
"react-dom": "^17.0.1", "url-loader": "^4.1.1",
"resolve": "1.20.0", "webpack": "^5.24.3",
"resolve-url-loader": "3.1.2", "webpack-cli": "^4.5.0",
"sass-loader": "10.1.1", "webpack-dev-server": "4.0.0-beta.0"
"semver": "7.3.4",
"style-loader": "2.0.0",
"terser-webpack-plugin": "4.2.3",
"ts-pnp": "1.2.0",
"uglifyjs-webpack-plugin": "^2.2.0",
"url-loader": "4.1.1",
"webpack": "4.46.0",
"webpack-dev-server": "3.11.2",
"webpack-manifest-plugin": "3.0.0",
"workbox-webpack-plugin": "6.1.1"
},
"eslintConfig": {
"extends": "react-app"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}, },
"jest": { "jest": {
"roots": [ "roots": [
@ -133,30 +79,18 @@
"!src/**/*.d.ts", "!src/**/*.d.ts",
"!**/tests/**" "!**/tests/**"
], ],
"setupFiles": [
"react-app-polyfill/jsdom"
],
"setupFilesAfterEnv": [
"<rootDir>/src/setupTests.js"
],
"testMatch": [ "testMatch": [
"<rootDir>/src/**/__tests__/**/*.{js,jsx,ts,tsx}", "<rootDir>/src/**/__tests__/**/*.{js,jsx,ts,tsx}",
"<rootDir>/src/**/*.{spec,test}.{js,jsx,ts,tsx}" "<rootDir>/src/**/*.{spec,test}.{js,jsx,ts,tsx}"
], ],
"testEnvironment": "jest-environment-jsdom-fourteen",
"transform": {
"^.+\\.(js|jsx|ts|tsx)$": "<rootDir>/node_modules/babel-jest",
"^.+\\.css$": "<rootDir>/config/jest/cssTransform.js",
"^(?!.*\\.(js|jsx|ts|tsx|css|json)$)": "<rootDir>/config/jest/fileTransform.js"
},
"transformIgnorePatterns": [ "transformIgnorePatterns": [
"[/\\\\]node_modules[/\\\\].+\\.(js|jsx|ts|tsx)$", "[/\\\\]node_modules[/\\\\].+\\.(js|jsx|ts|tsx)$",
"^.+\\.module\\.(css|sass|scss)$" "^.+\\.module\\.(css|sass|scss)$"
], ],
"modulePaths": [], "modulePaths": [],
"moduleNameMapper": { "moduleNameMapper": {
"^react-native$": "react-native-web", "\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "<rootDir>/scripts/testMock.js",
"^.+\\.module\\.(css|sass|scss)$": "identity-obj-proxy" "\\.(css|less)$": "<rootDir>/scripts/testMock.js"
}, },
"moduleFileExtensions": [ "moduleFileExtensions": [
"web.js", "web.js",
@ -169,21 +103,6 @@
"web.jsx", "web.jsx",
"jsx", "jsx",
"node" "node"
],
"watchPlugins": [
"jest-watch-typeahead/filename",
"jest-watch-typeahead/testname"
]
},
"babel": {
"presets": [
"@babel/preset-env",
"@babel/preset-react"
],
"plugins": [
[
"@babel/plugin-proposal-class-properties"
]
] ]
} }
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

View File

@ -1,28 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"
/>
<meta name="theme-color" content="#000000" />
<title>simple-keyboard</title>
<style>
/**
* Disabling double-tap to zoom in iOS 10+
* as it interferes with simple-keyboard
*/
body,
html {
touch-action: manipulation;
}
</style>
</head>
<body>
<noscript>
You need to enable JavaScript to run this app.
</noscript>
<div id="root"></div>
</body>
</html>

View File

@ -1,209 +0,0 @@
// Do this as the first thing so that any code reading it knows the right env.
process.env.BABEL_ENV = 'production';
process.env.NODE_ENV = 'production';
// Makes the script crash on unhandled rejections instead of silently
// ignoring them. In the future, promise rejections that are not handled will
// terminate the Node.js process with a non-zero exit code.
process.on('unhandledRejection', err => {
throw err;
});
// Ensure environment variables are read.
require('../config/env');
const path = require('path');
const chalk = require('react-dev-utils/chalk');
const fs = require('fs-extra');
const webpack = require('webpack');
const configFactory = require('../config/webpack.config');
const paths = require('../config/paths');
const checkRequiredFiles = require('react-dev-utils/checkRequiredFiles');
const formatWebpackMessages = require('react-dev-utils/formatWebpackMessages');
const printHostingInstructions = require('react-dev-utils/printHostingInstructions');
const FileSizeReporter = require('react-dev-utils/FileSizeReporter');
const printBuildError = require('react-dev-utils/printBuildError');
const measureFileSizesBeforeBuild =
FileSizeReporter.measureFileSizesBeforeBuild;
const printFileSizesAfterBuild = FileSizeReporter.printFileSizesAfterBuild;
const useYarn = fs.existsSync(paths.yarnLockFile);
// These sizes are pretty large. We'll warn for bundles exceeding them.
const WARN_AFTER_BUNDLE_GZIP_SIZE = 512 * 1024;
const WARN_AFTER_CHUNK_GZIP_SIZE = 1024 * 1024;
const isInteractive = process.stdout.isTTY;
// Warn and crash if required files are missing
if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) {
process.exit(1);
}
// Generate configuration
const config = configFactory('production');
// We require that you explicitly set browsers and do not fall back to
// browserslist defaults.
const { checkBrowsers } = require('react-dev-utils/browsersHelper');
checkBrowsers(paths.appPath, isInteractive)
.then(() => {
// First, read the current file sizes in build directory.
// This lets us display how much they changed later.
return measureFileSizesBeforeBuild(paths.appBuild);
})
.then(previousFileSizes => {
// Remove all content but keep the directory so that
// if you're in it, you don't end up in Trash
fs.emptyDirSync(paths.appBuild);
// Merge with the public folder
//copyPublicFolder();
// Start the webpack build
return build(previousFileSizes);
})
.then(
({ stats, previousFileSizes, warnings }) => {
if (warnings.length) {
console.log(chalk.yellow('Compiled with warnings.\n'));
console.log(warnings.join('\n\n'));
console.log(
'\nSearch for the ' +
chalk.underline(chalk.yellow('keywords')) +
' to learn more about each warning.'
);
console.log(
'To ignore, add ' +
chalk.cyan('// eslint-disable-next-line') +
' to the line before.\n'
);
} else {
console.log(chalk.green('Compiled successfully.\n'));
}
console.log('File sizes after gzip:\n');
printFileSizesAfterBuild(
stats,
previousFileSizes,
paths.appBuild,
WARN_AFTER_BUNDLE_GZIP_SIZE,
WARN_AFTER_CHUNK_GZIP_SIZE
);
console.log();
const appPackage = require(paths.appPackageJson);
const publicUrl = paths.publicUrlOrPath;
const publicPath = config.output.publicPath;
const buildFolder = path.relative(process.cwd(), paths.appBuild);
printHostingInstructions(
appPackage,
publicUrl,
publicPath,
buildFolder,
useYarn
);
},
err => {
const tscCompileOnError = process.env.TSC_COMPILE_ON_ERROR === 'true';
if (tscCompileOnError) {
console.log(
chalk.yellow(
'Compiled with the following type errors (you may want to check these before deploying your app):\n'
)
);
printBuildError(err);
} else {
console.log(chalk.red('Failed to compile.\n'));
printBuildError(err);
process.exit(1);
}
}
)
.catch(err => {
if (err && err.message) {
console.log(err.message);
}
process.exit(1);
});
// Create the production build and print the deployment instructions.
function build(previousFileSizes) {
// We used to support resolving modules according to `NODE_PATH`.
// This now has been deprecated in favor of jsconfig/tsconfig.json
// This lets you use absolute paths in imports inside large monorepos:
if (process.env.NODE_PATH) {
console.log(
chalk.yellow(
'Setting NODE_PATH to resolve modules absolutely has been deprecated in favor of setting baseUrl in jsconfig.json (or tsconfig.json if you are using TypeScript) and will be removed in a future major release of create-react-app.'
)
);
console.log();
}
console.log('Creating an optimized production build...');
const compiler = webpack(config);
return new Promise((resolve, reject) => {
compiler.run((err, stats) => {
let messages;
if (err) {
if (!err.message) {
return reject(err);
}
let errMessage = err.message;
// Add additional information for postcss errors
if (Object.prototype.hasOwnProperty.call(err, 'postcssNode')) {
errMessage +=
'\nCompileError: Begins at CSS selector ' +
err['postcssNode'].selector;
}
messages = formatWebpackMessages({
errors: [errMessage],
warnings: [],
});
} else {
messages = formatWebpackMessages(
stats.toJson({ all: false, warnings: true, errors: true })
);
}
if (messages.errors.length) {
// Only keep the first error. Others are often indicative
// of the same problem, but confuse the reader with noise.
if (messages.errors.length > 1) {
messages.errors.length = 1;
}
return reject(new Error(messages.errors.join('\n\n')));
}
if (
process.env.CI &&
(typeof process.env.CI !== 'string' ||
process.env.CI.toLowerCase() !== 'false') &&
messages.warnings.length
) {
console.log(
chalk.yellow(
'\nTreating warnings as errors because process.env.CI = true.\n' +
'Most CI servers set it automatically.\n'
)
);
return reject(new Error(messages.warnings.join('\n\n')));
}
return resolve({
stats,
previousFileSizes,
warnings: messages.warnings,
});
});
});
}
function copyPublicFolder() {
fs.copySync(paths.appPublic, paths.appBuild, {
dereference: true,
filter: file => file !== paths.appHtml,
});
}

View File

@ -1,209 +0,0 @@
// Do this as the first thing so that any code reading it knows the right env.
process.env.BABEL_ENV = 'production';
process.env.NODE_ENV = 'production';
// Makes the script crash on unhandled rejections instead of silently
// ignoring them. In the future, promise rejections that are not handled will
// terminate the Node.js process with a non-zero exit code.
process.on('unhandledRejection', err => {
throw err;
});
// Ensure environment variables are read.
require('../config/env');
const path = require('path');
const chalk = require('react-dev-utils/chalk');
const fs = require('fs-extra');
const webpack = require('webpack');
const configFactory = require('../config/webpack.config.demo');
const paths = require('../config/paths');
const checkRequiredFiles = require('react-dev-utils/checkRequiredFiles');
const formatWebpackMessages = require('react-dev-utils/formatWebpackMessages');
const printHostingInstructions = require('react-dev-utils/printHostingInstructions');
const FileSizeReporter = require('react-dev-utils/FileSizeReporter');
const printBuildError = require('react-dev-utils/printBuildError');
const measureFileSizesBeforeBuild =
FileSizeReporter.measureFileSizesBeforeBuild;
const printFileSizesAfterBuild = FileSizeReporter.printFileSizesAfterBuild;
const useYarn = fs.existsSync(paths.yarnLockFile);
// These sizes are pretty large. We'll warn for bundles exceeding them.
const WARN_AFTER_BUNDLE_GZIP_SIZE = 512 * 1024;
const WARN_AFTER_CHUNK_GZIP_SIZE = 1024 * 1024;
const isInteractive = process.stdout.isTTY;
// Warn and crash if required files are missing
if (!checkRequiredFiles([paths.appHtml, paths.appDemoIndexJs])) {
process.exit(1);
}
// Generate configuration
const config = configFactory('production');
// We require that you explicitly set browsers and do not fall back to
// browserslist defaults.
const { checkBrowsers } = require('react-dev-utils/browsersHelper');
checkBrowsers(paths.appPath, isInteractive)
.then(() => {
// First, read the current file sizes in build directory.
// This lets us display how much they changed later.
return measureFileSizesBeforeBuild(paths.appDemo);
})
.then(previousFileSizes => {
// Remove all content but keep the directory so that
// if you're in it, you don't end up in Trash
fs.emptyDirSync(paths.appDemo);
// Merge with the public folder
copyPublicFolder();
// Start the webpack build
return build(previousFileSizes);
})
.then(
({ stats, previousFileSizes, warnings }) => {
if (warnings.length) {
console.log(chalk.yellow('Compiled with warnings.\n'));
console.log(warnings.join('\n\n'));
console.log(
'\nSearch for the ' +
chalk.underline(chalk.yellow('keywords')) +
' to learn more about each warning.'
);
console.log(
'To ignore, add ' +
chalk.cyan('// eslint-disable-next-line') +
' to the line before.\n'
);
} else {
console.log(chalk.green('Compiled successfully.\n'));
}
console.log('File sizes after gzip:\n');
printFileSizesAfterBuild(
stats,
previousFileSizes,
paths.appDemo,
WARN_AFTER_BUNDLE_GZIP_SIZE,
WARN_AFTER_CHUNK_GZIP_SIZE
);
console.log();
const appPackage = require(paths.appPackageJson);
const publicUrl = paths.publicUrlOrPath;
const publicPath = config.output.publicPath;
const buildFolder = path.relative(process.cwd(), paths.appDemo);
printHostingInstructions(
appPackage,
publicUrl,
publicPath,
buildFolder,
useYarn
);
},
err => {
const tscCompileOnError = process.env.TSC_COMPILE_ON_ERROR === 'true';
if (tscCompileOnError) {
console.log(
chalk.yellow(
'Compiled with the following type errors (you may want to check these before deploying your app):\n'
)
);
printBuildError(err);
} else {
console.log(chalk.red('Failed to compile.\n'));
printBuildError(err);
process.exit(1);
}
}
)
.catch(err => {
if (err && err.message) {
console.log(err.message);
}
process.exit(1);
});
// Create the production build and print the deployment instructions.
function build(previousFileSizes) {
// We used to support resolving modules according to `NODE_PATH`.
// This now has been deprecated in favor of jsconfig/tsconfig.json
// This lets you use absolute paths in imports inside large monorepos:
if (process.env.NODE_PATH) {
console.log(
chalk.yellow(
'Setting NODE_PATH to resolve modules absolutely has been deprecated in favor of setting baseUrl in jsconfig.json (or tsconfig.json if you are using TypeScript) and will be removed in a future major release of create-react-app.'
)
);
console.log();
}
console.log('Creating an optimized production build...');
const compiler = webpack(config);
return new Promise((resolve, reject) => {
compiler.run((err, stats) => {
let messages;
if (err) {
if (!err.message) {
return reject(err);
}
let errMessage = err.message;
// Add additional information for postcss errors
if (Object.prototype.hasOwnProperty.call(err, 'postcssNode')) {
errMessage +=
'\nCompileError: Begins at CSS selector ' +
err['postcssNode'].selector;
}
messages = formatWebpackMessages({
errors: [errMessage],
warnings: [],
});
} else {
messages = formatWebpackMessages(
stats.toJson({ all: false, warnings: true, errors: true })
);
}
if (messages.errors.length) {
// Only keep the first error. Others are often indicative
// of the same problem, but confuse the reader with noise.
if (messages.errors.length > 1) {
messages.errors.length = 1;
}
return reject(new Error(messages.errors.join('\n\n')));
}
if (
process.env.CI &&
(typeof process.env.CI !== 'string' ||
process.env.CI.toLowerCase() !== 'false') &&
messages.warnings.length
) {
console.log(
chalk.yellow(
'\nTreating warnings as errors because process.env.CI = true.\n' +
'Most CI servers set it automatically.\n'
)
);
return reject(new Error(messages.warnings.join('\n\n')));
}
return resolve({
stats,
previousFileSizes,
warnings: messages.warnings,
});
});
});
}
function copyPublicFolder() {
fs.copySync(paths.appPublic, paths.appDemo, {
dereference: true,
filter: file => file !== paths.appHtml,
});
}

View File

@ -1,187 +0,0 @@
// Do this as the first thing so that any code reading it knows the right env.
process.env.BABEL_ENV = 'development';
process.env.NODE_ENV = 'development';
// Makes the script crash on unhandled rejections instead of silently
// ignoring them. In the future, promise rejections that are not handled will
// terminate the Node.js process with a non-zero exit code.
process.on('unhandledRejection', err => {
throw err;
});
// Ensure environment variables are read.
require('../config/env');
const fs = require('fs');
const chalk = require('react-dev-utils/chalk');
const webpack = require('webpack');
const WebpackDevServer = require('webpack-dev-server');
const clearConsole = require('react-dev-utils/clearConsole');
const checkRequiredFiles = require('react-dev-utils/checkRequiredFiles');
const {
choosePort,
createCompiler,
prepareProxy,
prepareUrls,
} = require('react-dev-utils/WebpackDevServerUtils');
const openBrowser = require('react-dev-utils/openBrowser');
const paths = require('../config/paths');
const configFactory = require('../config/webpack.config');
const createDevServerConfig = require('../config/webpackDevServer.config');
const useYarn = fs.existsSync(paths.yarnLockFile);
const isInteractive = process.stdout.isTTY;
// Warn and crash if required files are missing
if (!checkRequiredFiles([paths.appHtml, paths.appDemoIndexJs])) {
process.exit(1);
}
// Tools like Cloud9 rely on this.
const DEFAULT_PORT = parseInt(process.env.PORT, 10) || 3000;
const HOST = process.env.HOST || '0.0.0.0';
if (process.env.HOST) {
console.log(
chalk.cyan(
`Attempting to bind to HOST environment variable: ${chalk.yellow(
chalk.bold(process.env.HOST)
)}`
)
);
console.log(
`If this was unintentional, check that you haven't mistakenly set it in your shell.`
);
console.log(
`Learn more here: ${chalk.yellow('https://bit.ly/CRA-advanced-config')}`
);
console.log();
}
// We require that you explicitly set browsers and do not fall back to
// browserslist defaults.
const { checkBrowsers } = require('react-dev-utils/browsersHelper');
checkBrowsers(paths.appPath, isInteractive)
.then(() => {
// We attempt to use the default port but if it is busy, we offer the user to
// run on a different port. `choosePort()` Promise resolves to the next free port.
return choosePort(HOST, DEFAULT_PORT);
})
.then(port => {
if (port == null) {
// We have not found a port.
return;
}
const args = process.argv;
const testMode = args[2] === "--testMode";
const config = configFactory('development');
const protocol = process.env.HTTPS === 'true' ? 'https' : 'http';
const appName = require(paths.appPackageJson).name;
const useTypeScript = fs.existsSync(paths.appTsConfig);
const tscCompileOnError = process.env.TSC_COMPILE_ON_ERROR === 'true';
const urls = prepareUrls(
protocol,
HOST,
port,
paths.publicUrlOrPath.slice(0, -1)
);
const devSocket = {
warnings: warnings =>
devServer.sockWrite(devServer.sockets, 'warnings', warnings),
errors: errors =>
devServer.sockWrite(devServer.sockets, 'errors', errors),
};
// Create a webpack compiler that is configured with custom messages.
const compiler = createCompiler({
appName,
config,
devSocket,
urls,
useYarn,
useTypeScript,
tscCompileOnError,
webpack,
});
// Load proxy config
const proxySetting = require(paths.appPackageJson).proxy;
const proxyConfig = prepareProxy(
proxySetting,
paths.appPublic
);
// Serve webpack assets generated by the compiler over a web server.
const serverConfig = createDevServerConfig(
proxyConfig,
urls.lanUrlForConfig
);
const devServer = new WebpackDevServer(compiler, serverConfig);
// Launch WebpackDevServer.
devServer.listen(port, HOST, err => {
if (err) {
return console.log(err);
}
if (isInteractive) {
clearConsole();
}
// We used to support resolving modules according to `NODE_PATH`.
// This now has been deprecated in favor of jsconfig/tsconfig.json
// This lets you use absolute paths in imports inside large monorepos:
if (process.env.NODE_PATH) {
console.log(
chalk.yellow(
'Setting NODE_PATH to resolve modules absolutely has been deprecated in favor of setting baseUrl in jsconfig.json (or tsconfig.json if you are using TypeScript) and will be removed in a future major release of create-react-app.'
)
);
console.log();
}
console.log(chalk.cyan('Starting the development server...\n'));
openBrowser(urls.localUrlForBrowser);
/**
* Handle testMode
*/
if(testMode){
compiler.hooks.done.tap('done', (stats) => {
stats = stats.toJson();
if (stats.errors && stats.errors.length > 0) {
devServer.close();
process.exit(1);
}
console.warn("App started in test mode. Closing in 5 seconds.");
const closeTimeout = setTimeout(() => {
clearTimeout(closeTimeout);
devServer.close();
process.exit();
}, 5000);
});
}
});
['SIGINT', 'SIGTERM'].forEach(function(sig) {
process.on(sig, function() {
devServer.close();
process.exit();
});
});
if (isInteractive || process.env.CI !== 'true') {
// Gracefully exit when stdin ends
process.stdin.on('end', function() {
devServer.close();
process.exit();
});
process.stdin.resume();
}
})
.catch(err => {
if (err && err.message) {
console.log(err.message);
}
process.exit(1);
});

View File

@ -1,53 +0,0 @@
'use strict';
// Do this as the first thing so that any code reading it knows the right env.
process.env.BABEL_ENV = 'test';
process.env.NODE_ENV = 'test';
process.env.PUBLIC_URL = '';
// Makes the script crash on unhandled rejections instead of silently
// ignoring them. In the future, promise rejections that are not handled will
// terminate the Node.js process with a non-zero exit code.
process.on('unhandledRejection', err => {
throw err;
});
// Ensure environment variables are read.
require('../config/env');
const jest = require('jest');
const execSync = require('child_process').execSync;
let argv = process.argv.slice(2);
function isInGitRepository() {
try {
execSync('git rev-parse --is-inside-work-tree', { stdio: 'ignore' });
return true;
} catch (e) {
return false;
}
}
function isInMercurialRepository() {
try {
execSync('hg --cwd . root', { stdio: 'ignore' });
return true;
} catch (e) {
return false;
}
}
// Watch unless on CI or explicitly running all tests
if (
!process.env.CI &&
argv.indexOf('--watchAll') === -1 &&
argv.indexOf('--watchAll=false') === -1
) {
// https://github.com/facebook/create-react-app/issues/5210
const hasSourceControl = isInGitRepository() || isInMercurialRepository();
argv.push(hasSourceControl ? '--watch' : '--watchAll');
}
jest.run(argv);

1
scripts/testMock.js Normal file
View File

@ -0,0 +1 @@
module.exports = {};

View File

@ -1,329 +0,0 @@
declare module 'simple-keyboard' {
export interface KeyboardLayoutObject {
[key: string]: string[];
}
export interface KeyboardButtonTheme {
class: string;
buttons: string;
}
export interface KeyboardButtonAttributes {
attribute: string;
value: string;
buttons: string;
}
export interface KeyboardOptions {
/**
* Modify the keyboard layout.
*/
layout?: KeyboardLayoutObject;
/**
* Specifies which layout should be used.
*/
layoutName?: string;
/**
* Replaces variable buttons (such as `{bksp}`) with a human-friendly name (e.g.: `backspace`).
*/
display?: { [button: string]: string };
/**
* By default, when you set the display property, you replace the default one. This setting merges them instead.
*/
mergeDisplay?: boolean;
/**
* A prop to add your own css classes to the keyboard wrapper. You can add multiple classes separated by a space.
*/
theme?: string;
/**
* A prop to add your own css classes to one or several buttons.
*/
buttonTheme?: KeyboardButtonTheme[];
/**
* A prop to add your own attributes to one or several buttons.
*/
buttonAttributes?: KeyboardButtonAttributes[];
/**
* Runs a `console.log` every time a key is pressed. Displays the buttons pressed and the current input.
*/
debug?: boolean;
/**
* Specifies whether clicking the "ENTER" button will input a newline (`\n`) or not.
*/
newLineOnEnter?: boolean;
/**
* Specifies whether clicking the "TAB" button will input a tab character (`\t`) or not.
*/
tabCharOnTab?: boolean;
/**
* Allows you to use a single simple-keyboard instance for several inputs.
*/
inputName?: string;
/**
* `number`: Restrains all of simple-keyboard inputs to a certain length. This should be used in addition to the input elements maxlengthattribute.
*
* `{ [inputName: string]: number }`: Restrains simple-keyboards individual inputs to a certain length. This should be used in addition to the input elements maxlengthattribute.
*/
maxLength?: any;
/**
* When set to true, this option synchronizes the internal input of every simple-keyboard instance.
*/
syncInstanceInputs?: boolean;
/**
* Enable highlighting of keys pressed on physical keyboard.
*/
physicalKeyboardHighlight?: boolean;
/**
* Presses keys highlighted by physicalKeyboardHighlight
*/
physicalKeyboardHighlightPress?: boolean;
/**
* Define the text color that the physical keyboard highlighted key should have.
*/
physicalKeyboardHighlightTextColor?: string;
/**
* Define the background color that the physical keyboard highlighted key should have.
*/
physicalKeyboardHighlightBgColor?: string;
/**
* Calling preventDefault for the mousedown events keeps the focus on the input.
*/
preventMouseDownDefault?: boolean;
/**
* Calling preventDefault for the mouseup events.
*/
preventMouseUpDefault?: boolean;
/**
* Stops pointer down events on simple-keyboard buttons from bubbling to parent elements.
*/
stopMouseDownPropagation?: boolean;
/**
* Stops pointer up events on simple-keyboard buttons from bubbling to parent elements.
*/
stopMouseUpPropagation?: boolean;
/**
* Render buttons as a button element instead of a div element.
*/
useButtonTag?: boolean;
/**
* A prop to ensure characters are always be added/removed at the end of the string.
*/
disableCaretPositioning?: boolean;
/**
* Restrains input(s) change to the defined regular expression pattern.
*/
inputPattern?: any;
/**
* Instructs simple-keyboard to use touch events instead of click events.
*/
useTouchEvents?: boolean;
/**
* Enable useTouchEvents automatically when touch device is detected.
*/
autoUseTouchEvents?: boolean;
/**
* Opt out of PointerEvents handling, falling back to the prior mouse event logic.
*/
useMouseEvents?: boolean;
/**
* Disable button hold action.
*/
disableButtonHold?: boolean;
/**
* Adds unicode right-to-left control characters to input return values.
*/
rtl?: boolean;
/**
* Executes the callback function on key press. Returns button layout name (i.e.: "{shift}").
*/
onKeyPress?: (button: string) => any;
/**
* Executes the callback function on key release.
*/
onKeyReleased?: (button: string) => any;
/**
* Executes the callback function on input change. Returns the current input's string.
*/
onChange?: (input: string) => any;
/**
* Executes the callback function before the first simple-keyboard render.
*/
beforeFirstRender?: () => void;
/**
* Executes the callback function before a simple-keyboard render.
*/
beforeRender?: () => void;
/**
* Executes the callback function every time simple-keyboard is rendered (e.g: when you change layouts).
*/
onRender?: () => void;
/**
* Executes the callback function once simple-keyboard is rendered for the first time (on initialization).
*/
onInit?: () => void;
/**
* Executes the callback function on input change. Returns the input object with all defined inputs.
*/
onChangeAll?: (inputs: any) => any;
/**
* Module classes to be loaded by simple-keyboard.
*/
modules?: any[];
/**
* Module options can have any format
*/
[name: string]: any;
}
class Keyboard {
constructor(selector: string, options: KeyboardOptions);
constructor(selector: HTMLDivElement, options: KeyboardOptions);
constructor(options: KeyboardOptions);
/**
* Options
*/
options: KeyboardOptions;
/**
* Utilities
*/
utilities?: any;
/**
* caretPosition
*/
caretPosition?: number;
/**
* caretPositionEnd
*/
caretPositionEnd?: number;
/**
* Changes the internal caret position
* @param {number} position The caret's start position
* @param {number} positionEnd The caret's end position
*/
setCaretPosition(position: number, positionEnd?: number): void;
/**
* Retrieves the internal caret position
*/
getCaretPosition(): number;
/**
* Retrieves the internal end caret position
*/
getCaretPositionEnd(): number;
/**
* Adds/Modifies an entry to the `buttonTheme`. Basically a way to add a class to a button.
* @param {string} buttons List of buttons to select (separated by a space).
* @param {string} className Classes to give to the selected buttons (separated by space).
*/
addButtonTheme(buttons: string, className: string): void;
/**
* Removes/Amends an entry to the `buttonTheme`. Basically a way to remove a class previously added to a button through buttonTheme or addButtonTheme.
* @param {string} buttons List of buttons to select (separated by a space).
* @param {string} className Classes to give to the selected buttons (separated by space).
*/
removeButtonTheme(buttons: string, className: string): void;
/**
* Clear the keyboard's input.
*
* @param {string} [inputName] optional - the internal input to select
*/
clearInput(inputName?: string): void;
/**
* Get the keyboards input (You can also get it from the onChange prop).
* @param {string} [inputName] optional - the internal input to select
*/
getInput(inputName?: string): string;
/**
* Set the keyboards input.
* @param {string} input the input value
* @param {string} inputName optional - the internal input to select
*/
setInput(input: string, inputName?: string): void;
/**
* Replaces the entire internal input object.
* @param {string} input the input object
*/
replaceInput(input: any): void;
/**
* Set new option or modify existing ones after initialization.
* @param {KeyboardOptions} option The option to set
*/
setOptions(options: KeyboardOptions): void;
/**
* Send a command to all simple-keyboard instances at once (if you have multiple instances).
* @param {function(instance: object, key: string)} callback Function to run on every instance
*/
dispatch(callback: (instance: any, key: string) => void): void;
/**
* Get the DOM Element of a button. If there are several buttons with the same name, an array of the DOM Elements is returned.
* @param {string} button The button layout name to select
*/
getButtonElement(button: string): HTMLElement | HTMLElement[];
/**
* Clears keyboard listeners and DOM elements.
*/
destroy(): void;
/**
* Iterate on each button (ForEach)
*/
recurseButtons(fn: any): void;
}
export default Keyboard;
}

View File

@ -4,6 +4,13 @@ import "./Keyboard.css";
import { getDefaultLayout } from "../services/KeyboardLayout"; import { getDefaultLayout } from "../services/KeyboardLayout";
import PhysicalKeyboard from "../services/PhysicalKeyboard"; import PhysicalKeyboard from "../services/PhysicalKeyboard";
import Utilities from "../services/Utilities"; import Utilities from "../services/Utilities";
import {
KeyboardOptions,
KeyboardInput,
KeyboardButtonElements,
KeyboardHandlerEvent,
KeyboardButton,
} from "../interfaces";
/** /**
* Root class for simple-keyboard * Root class for simple-keyboard
@ -13,11 +20,32 @@ import Utilities from "../services/Utilities";
* - Handles button functionality * - Handles button functionality
*/ */
class SimpleKeyboard { class SimpleKeyboard {
input: KeyboardInput;
options: KeyboardOptions;
utilities: any;
caretPosition: number;
caretPositionEnd: number;
keyboardDOM: KeyboardButton;
keyboardPluginClasses: string;
keyboardDOMClass: string;
buttonElements: KeyboardButtonElements;
currentInstanceName: string;
allKeyboardInstances: { [key: string]: SimpleKeyboard };
keyboardInstanceNames: string[];
isFirstKeyboardInstance: boolean;
physicalKeyboard: PhysicalKeyboard;
modules: { [key: string]: any };
activeButtonClass: string;
holdInteractionTimeout: number;
holdTimeout: number;
isMouseHold: boolean;
initialized: boolean;
/** /**
* Creates an instance of SimpleKeyboard * Creates an instance of SimpleKeyboard
* @param {Array} params 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. * @param {Array} params 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.
*/ */
constructor(...params) { constructor(...params: []) {
const { keyboardDOMClass, keyboardDOM, options = {} } = this.handleParams( const { keyboardDOMClass, keyboardDOM, options = {} } = this.handleParams(
params params
); );
@ -29,7 +57,7 @@ class SimpleKeyboard {
getOptions: this.getOptions, getOptions: this.getOptions,
getCaretPosition: this.getCaretPosition, getCaretPosition: this.getCaretPosition,
getCaretPositionEnd: this.getCaretPositionEnd, getCaretPositionEnd: this.getCaretPositionEnd,
dispatch: this.dispatch dispatch: this.dispatch,
}); });
/** /**
@ -155,7 +183,7 @@ class SimpleKeyboard {
*/ */
this.physicalKeyboard = new PhysicalKeyboard({ this.physicalKeyboard = new PhysicalKeyboard({
dispatch: this.dispatch, dispatch: this.dispatch,
getOptions: this.getOptions getOptions: this.getOptions,
}); });
/** /**
@ -177,7 +205,13 @@ class SimpleKeyboard {
/** /**
* parseParams * parseParams
*/ */
handleParams = params => { handleParams = (
params: any[]
): {
keyboardDOMClass: string;
keyboardDOM: KeyboardButton;
options: Partial<KeyboardOptions>;
} => {
let keyboardDOMClass; let keyboardDOMClass;
let keyboardDOM; let keyboardDOM;
let options; let options;
@ -188,11 +222,13 @@ class SimpleKeyboard {
*/ */
if (typeof params[0] === "string") { if (typeof params[0] === "string") {
keyboardDOMClass = params[0].split(".").join(""); keyboardDOMClass = params[0].split(".").join("");
keyboardDOM = document.querySelector(`.${keyboardDOMClass}`); keyboardDOM = document.querySelector(
`.${keyboardDOMClass}`
) as KeyboardButton;
options = params[1]; options = params[1];
/** /**
* If first parameter is an HTMLDivElement * If first parameter is an KeyboardButton
* Consider it as the keyboard DOM element * Consider it as the keyboard DOM element
*/ */
} else if (params[0] instanceof HTMLDivElement) { } else if (params[0] instanceof HTMLDivElement) {
@ -213,43 +249,47 @@ class SimpleKeyboard {
*/ */
} else { } else {
keyboardDOMClass = "simple-keyboard"; keyboardDOMClass = "simple-keyboard";
keyboardDOM = document.querySelector(`.${keyboardDOMClass}`); keyboardDOM = document.querySelector(
`.${keyboardDOMClass}`
) as KeyboardButton;
options = params[0]; options = params[0];
} }
return { return {
keyboardDOMClass, keyboardDOMClass,
keyboardDOM, keyboardDOM,
options options,
}; };
}; };
/** /**
* Getters * Getters
*/ */
getOptions = () => this.options; getOptions = (): KeyboardOptions => this.options;
getCaretPosition = () => this.caretPosition; getCaretPosition = (): number => this.caretPosition;
getCaretPositionEnd = () => this.caretPositionEnd; getCaretPositionEnd = (): number => this.caretPositionEnd;
/** /**
* Setters * Changes the internal caret position
* @param {number} position The caret's start position
* @param {number} positionEnd The caret's end position
*/ */
setCaretPosition(position, endPosition) { setCaretPosition(position: number, endPosition = position): void {
this.caretPosition = position; this.caretPosition = position;
this.caretPositionEnd = endPosition || position; this.caretPositionEnd = endPosition;
} }
/** /**
* Handles clicks made to keyboard buttons * Handles clicks made to keyboard buttons
* @param {string} button The button's layout name. * @param {string} button The button's layout name.
*/ */
handleButtonClicked(button) { handleButtonClicked(button: string): void {
const debug = this.options.debug; const debug = this.options.debug;
/** /**
* Ignoring placeholder buttons * Ignoring placeholder buttons
*/ */
if (button === "{//}") return false; if (button === "{//}") return;
/** /**
* Calling onKeyPress * Calling onKeyPress
@ -283,7 +323,7 @@ class SimpleKeyboard {
this.options.maxLength && this.options.maxLength &&
this.utilities.handleMaxLength(this.input, updatedInput) this.utilities.handleMaxLength(this.input, updatedInput)
) { ) {
return false; return;
} }
this.input[this.options.inputName] = this.utilities.getUpdatedInput( this.input[this.options.inputName] = this.utilities.getUpdatedInput(
@ -332,7 +372,7 @@ class SimpleKeyboard {
* Handles button mousedown * Handles button mousedown
*/ */
/* istanbul ignore next */ /* istanbul ignore next */
handleButtonMouseDown(button, e) { handleButtonMouseDown(button: string, e: KeyboardHandlerEvent): void {
if (e) { if (e) {
/** /**
* Handle event options * Handle event options
@ -358,7 +398,7 @@ class SimpleKeyboard {
* @type {object} Time to wait until a key hold is detected * @type {object} Time to wait until a key hold is detected
*/ */
if (!this.options.disableButtonHold) { if (!this.options.disableButtonHold) {
this.holdTimeout = setTimeout(() => { this.holdTimeout = window.setTimeout(() => {
if ( if (
(this.isMouseHold && (this.isMouseHold &&
// TODO: This needs to be configurable through options // TODO: This needs to be configurable through options
@ -375,7 +415,7 @@ class SimpleKeyboard {
) { ) {
if (this.options.debug) console.log("Button held:", button); if (this.options.debug) console.log("Button held:", button);
this.handleButtonHold(button, e); this.handleButtonHold(button);
} }
clearTimeout(this.holdTimeout); clearTimeout(this.holdTimeout);
}, 500); }, 500);
@ -385,7 +425,7 @@ class SimpleKeyboard {
/** /**
* Handles button mouseup * Handles button mouseup
*/ */
handleButtonMouseUp(button = null, e = null) { handleButtonMouseUp(button?: string, e?: KeyboardHandlerEvent): void {
if (e) { if (e) {
/** /**
* Handle event options * Handle event options
@ -397,7 +437,7 @@ class SimpleKeyboard {
/** /**
* Remove active class * Remove active class
*/ */
this.recurseButtons(buttonElement => { this.recurseButtons((buttonElement: Element) => {
buttonElement.classList.remove(this.activeButtonClass); buttonElement.classList.remove(this.activeButtonClass);
}); });
@ -414,7 +454,7 @@ class SimpleKeyboard {
/** /**
* Handles container mousedown * Handles container mousedown
*/ */
handleKeyboardContainerMouseDown(e) { handleKeyboardContainerMouseDown(e: KeyboardHandlerEvent): void {
/** /**
* Handle event options * Handle event options
*/ */
@ -425,13 +465,13 @@ class SimpleKeyboard {
* Handles button hold * Handles button hold
*/ */
/* istanbul ignore next */ /* istanbul ignore next */
handleButtonHold(button) { handleButtonHold(button: string): void {
if (this.holdInteractionTimeout) clearTimeout(this.holdInteractionTimeout); if (this.holdInteractionTimeout) clearTimeout(this.holdInteractionTimeout);
/** /**
* @type {object} Timeout dictating the speed of key hold iterations * @type {object} Timeout dictating the speed of key hold iterations
*/ */
this.holdInteractionTimeout = setTimeout(() => { this.holdInteractionTimeout = window.setTimeout(() => {
if (this.isMouseHold) { if (this.isMouseHold) {
this.handleButtonClicked(button); this.handleButtonClicked(button);
this.handleButtonHold(button); this.handleButtonHold(button);
@ -444,8 +484,8 @@ class SimpleKeyboard {
/** /**
* Send a command to all simple-keyboard instances (if you have several instances). * Send a command to all simple-keyboard instances (if you have several instances).
*/ */
syncInstanceInputs() { syncInstanceInputs(): void {
this.dispatch(instance => { this.dispatch((instance: SimpleKeyboard) => {
instance.replaceInput(this.input); instance.replaceInput(this.input);
instance.setCaretPosition(this.caretPosition, this.caretPositionEnd); instance.setCaretPosition(this.caretPosition, this.caretPositionEnd);
}); });
@ -455,7 +495,7 @@ class SimpleKeyboard {
* Clear the keyboards input. * Clear the keyboards input.
* @param {string} [inputName] optional - the internal input to select * @param {string} [inputName] optional - the internal input to select
*/ */
clearInput(inputName) { clearInput(inputName: string): void {
inputName = inputName || this.options.inputName; inputName = inputName || this.options.inputName;
this.input[inputName] = ""; this.input[inputName] = "";
@ -474,7 +514,7 @@ class SimpleKeyboard {
* Get the keyboards input (You can also get it from the onChange prop). * Get the keyboards input (You can also get it from the onChange prop).
* @param {string} [inputName] optional - the internal input to select * @param {string} [inputName] optional - the internal input to select
*/ */
getInput(inputName, skipSync = false) { getInput(inputName: string, skipSync = false): string {
inputName = inputName || this.options.inputName; inputName = inputName || this.options.inputName;
/** /**
@ -497,11 +537,11 @@ class SimpleKeyboard {
/** /**
* Get all simple-keyboard inputs * Get all simple-keyboard inputs
*/ */
getAllInputs() { getAllInputs(): KeyboardInput {
const output = {}; const output = {};
const inputNames = Object.keys(this.input); const inputNames = Object.keys(this.input);
inputNames.forEach(inputName => { inputNames.forEach((inputName) => {
output[inputName] = this.getInput(inputName, true); output[inputName] = this.getInput(inputName, true);
}); });
@ -513,7 +553,7 @@ class SimpleKeyboard {
* @param {string} input the input value * @param {string} input the input value
* @param {string} inputName optional - the internal input to select * @param {string} inputName optional - the internal input to select
*/ */
setInput(input, inputName) { setInput(input: string, inputName: string): void {
inputName = inputName || this.options.inputName; inputName = inputName || this.options.inputName;
this.input[inputName] = input; this.input[inputName] = input;
@ -527,7 +567,7 @@ class SimpleKeyboard {
* Replace the input object (`keyboard.input`) * Replace the input object (`keyboard.input`)
* @param {object} inputObj The input object * @param {object} inputObj The input object
*/ */
replaceInput(inputObj) { replaceInput(inputObj: KeyboardInput): void {
this.input = inputObj; this.input = inputObj;
} }
@ -535,7 +575,7 @@ class SimpleKeyboard {
* Set new option or modify existing ones after initialization. * Set new option or modify existing ones after initialization.
* @param {object} options The options to set * @param {object} options The options to set
*/ */
setOptions(options = {}) { setOptions(options = {}): void {
const changedOptions = this.changedOptions(options); const changedOptions = this.changedOptions(options);
this.options = Object.assign(this.options, options); this.options = Object.assign(this.options, options);
@ -560,9 +600,9 @@ class SimpleKeyboard {
* Detecting changes to non-function options * Detecting changes to non-function options
* This allows us to ascertain whether a button re-render is needed * This allows us to ascertain whether a button re-render is needed
*/ */
changedOptions(newOptions) { changedOptions(newOptions: Partial<KeyboardOptions>): string[] {
return Object.keys(newOptions).filter( return Object.keys(newOptions).filter(
optionName => (optionName) =>
JSON.stringify(newOptions[optionName]) !== JSON.stringify(newOptions[optionName]) !==
JSON.stringify(this.options[optionName]) JSON.stringify(this.options[optionName])
); );
@ -572,7 +612,7 @@ class SimpleKeyboard {
* Executing actions depending on changed options * Executing actions depending on changed options
* @param {object} options The options to set * @param {object} options The options to set
*/ */
onSetOptions(options) { onSetOptions(options: Partial<KeyboardOptions>): void {
if (options.inputName) { if (options.inputName) {
/** /**
* inputName changed. This requires a caretPosition reset * inputName changed. This requires a caretPosition reset
@ -588,7 +628,7 @@ class SimpleKeyboard {
* Remove all keyboard rows and reset keyboard values. * Remove all keyboard rows and reset keyboard values.
* Used internally between re-renders. * Used internally between re-renders.
*/ */
clear() { clear(): void {
this.keyboardDOM.innerHTML = ""; this.keyboardDOM.innerHTML = "";
this.keyboardDOM.className = this.keyboardDOMClass; this.keyboardDOM.className = this.keyboardDOMClass;
this.buttonElements = {}; this.buttonElements = {};
@ -598,7 +638,8 @@ class SimpleKeyboard {
* Send a command to all simple-keyboard instances at once (if you have multiple instances). * Send a command to all simple-keyboard instances at once (if you have multiple instances).
* @param {function(instance: object, key: string)} callback Function to run on every instance * @param {function(instance: object, key: string)} callback Function to run on every instance
*/ */
dispatch(callback) { // eslint-disable-next-line no-unused-vars
dispatch(callback: (instance: SimpleKeyboard, key?: string) => void): void {
if (!window["SimpleKeyboardInstances"]) { if (!window["SimpleKeyboardInstances"]) {
console.warn( console.warn(
`SimpleKeyboardInstances is not defined. Dispatch cannot be called.` `SimpleKeyboardInstances is not defined. Dispatch cannot be called.`
@ -606,7 +647,7 @@ class SimpleKeyboard {
throw new Error("INSTANCES_VAR_ERROR"); throw new Error("INSTANCES_VAR_ERROR");
} }
return Object.keys(window["SimpleKeyboardInstances"]).forEach(key => { return Object.keys(window["SimpleKeyboardInstances"]).forEach((key) => {
callback(window["SimpleKeyboardInstances"][key], key); callback(window["SimpleKeyboardInstances"][key], key);
}); });
} }
@ -616,11 +657,11 @@ class SimpleKeyboard {
* @param {string} buttons List of buttons to select (separated by a space). * @param {string} buttons List of buttons to select (separated by a space).
* @param {string} className Classes to give to the selected buttons (separated by space). * @param {string} className Classes to give to the selected buttons (separated by space).
*/ */
addButtonTheme(buttons, className) { addButtonTheme(buttons: string, className: string): void {
if (!className || !buttons) return false; if (!className || !buttons) return;
buttons.split(" ").forEach(button => { buttons.split(" ").forEach((button) => {
className.split(" ").forEach(classNameItem => { className.split(" ").forEach((classNameItem) => {
if (!this.options.buttonTheme) this.options.buttonTheme = []; if (!this.options.buttonTheme) this.options.buttonTheme = [];
let classNameFound = false; let classNameFound = false;
@ -628,7 +669,7 @@ class SimpleKeyboard {
/** /**
* If class is already defined, we add button to class definition * If class is already defined, we add button to class definition
*/ */
this.options.buttonTheme.map(buttonTheme => { this.options.buttonTheme.map((buttonTheme) => {
if (buttonTheme.class.split(" ").includes(classNameItem)) { if (buttonTheme.class.split(" ").includes(classNameItem)) {
classNameFound = true; classNameFound = true;
@ -648,7 +689,7 @@ class SimpleKeyboard {
if (!classNameFound) { if (!classNameFound) {
this.options.buttonTheme.push({ this.options.buttonTheme.push({
class: classNameItem, class: classNameItem,
buttons: buttons buttons: buttons,
}); });
} }
}); });
@ -662,14 +703,14 @@ class SimpleKeyboard {
* @param {string} buttons List of buttons to select (separated by a space). * @param {string} buttons List of buttons to select (separated by a space).
* @param {string} className Classes to give to the selected buttons (separated by space). * @param {string} className Classes to give to the selected buttons (separated by space).
*/ */
removeButtonTheme(buttons, className) { removeButtonTheme(buttons: string, className: string): void {
/** /**
* When called with empty parameters, remove all button themes * When called with empty parameters, remove all button themes
*/ */
if (!buttons && !className) { if (!buttons && !className) {
this.options.buttonTheme = []; this.options.buttonTheme = [];
this.render(); this.render();
return false; return;
} }
/** /**
@ -681,7 +722,7 @@ class SimpleKeyboard {
this.options.buttonTheme.length this.options.buttonTheme.length
) { ) {
const buttonArray = buttons.split(" "); const buttonArray = buttons.split(" ");
buttonArray.forEach(button => { buttonArray.forEach((button) => {
this.options.buttonTheme.map((buttonTheme, index) => { this.options.buttonTheme.map((buttonTheme, index) => {
/** /**
* If className is set, we affect the buttons only for that class * If className is set, we affect the buttons only for that class
@ -693,7 +734,7 @@ class SimpleKeyboard {
) { ) {
const filteredButtonArray = buttonTheme.buttons const filteredButtonArray = buttonTheme.buttons
.split(" ") .split(" ")
.filter(item => item !== button); .filter((item) => item !== button);
/** /**
* If buttons left, return them, otherwise, remove button Theme * If buttons left, return them, otherwise, remove button Theme
@ -718,7 +759,7 @@ class SimpleKeyboard {
* Get the DOM Element of a button. If there are several buttons with the same name, an array of the DOM Elements is returned. * Get the DOM Element of a button. If there are several buttons with the same name, an array of the DOM Elements is returned.
* @param {string} button The button layout name to select * @param {string} button The button layout name to select
*/ */
getButtonElement(button) { getButtonElement(button: string): KeyboardButton | KeyboardButton[] {
let output; let output;
const buttonArr = this.buttonElements[button]; const buttonArr = this.buttonElements[button];
@ -737,7 +778,7 @@ class SimpleKeyboard {
* This handles the "inputPattern" option * This handles the "inputPattern" option
* by checking if the provided inputPattern passes * by checking if the provided inputPattern passes
*/ */
inputPatternIsValid(inputVal) { inputPatternIsValid(inputVal: string): boolean {
const inputPatternRaw = this.options.inputPattern; const inputPatternRaw = this.options.inputPattern;
let inputPattern; let inputPattern;
@ -773,7 +814,7 @@ class SimpleKeyboard {
/** /**
* Handles simple-keyboard event listeners * Handles simple-keyboard event listeners
*/ */
setEventListeners() { setEventListeners(): void {
/** /**
* Only first instance should set the event listeners * Only first instance should set the event listeners
*/ */
@ -795,7 +836,7 @@ class SimpleKeyboard {
/** /**
* Event Handler: KeyUp * Event Handler: KeyUp
*/ */
handleKeyUp(event) { handleKeyUp(event: KeyboardHandlerEvent): void {
this.caretEventHandler(event); this.caretEventHandler(event);
if (this.options.physicalKeyboardHighlight) { if (this.options.physicalKeyboardHighlight) {
@ -806,7 +847,7 @@ class SimpleKeyboard {
/** /**
* Event Handler: KeyDown * Event Handler: KeyDown
*/ */
handleKeyDown(event) { handleKeyDown(event: KeyboardHandlerEvent): void {
if (this.options.physicalKeyboardHighlight) { if (this.options.physicalKeyboardHighlight) {
this.physicalKeyboard.handleHighlightKeyDown(event); this.physicalKeyboard.handleHighlightKeyDown(event);
} }
@ -815,7 +856,7 @@ class SimpleKeyboard {
/** /**
* Event Handler: MouseUp * Event Handler: MouseUp
*/ */
handleMouseUp(event) { handleMouseUp(event: KeyboardHandlerEvent): void {
this.caretEventHandler(event); this.caretEventHandler(event);
} }
@ -823,20 +864,20 @@ class SimpleKeyboard {
* Event Handler: TouchEnd * Event Handler: TouchEnd
*/ */
/* istanbul ignore next */ /* istanbul ignore next */
handleTouchEnd(event) { handleTouchEnd(event: KeyboardHandlerEvent): void {
this.caretEventHandler(event); this.caretEventHandler(event);
} }
/** /**
* Called by {@link setEventListeners} when an event that warrants a cursor position update is triggered * Called by {@link setEventListeners} when an event that warrants a cursor position update is triggered
*/ */
caretEventHandler(event) { caretEventHandler(event: KeyboardHandlerEvent): void {
let targetTagName; let targetTagName: string;
if (event.target.tagName) { if (event.target.tagName) {
targetTagName = event.target.tagName.toLowerCase(); targetTagName = event.target.tagName.toLowerCase();
} }
this.dispatch(instance => { this.dispatch((instance) => {
const isKeyboard = const isKeyboard =
event.target === instance.keyboardDOM || event.target === instance.keyboardDOM ||
(event.target && instance.keyboardDOM.contains(event.target)); (event.target && instance.keyboardDOM.contains(event.target));
@ -881,10 +922,10 @@ class SimpleKeyboard {
/** /**
* Execute an operation on each button * Execute an operation on each button
*/ */
recurseButtons(fn) { recurseButtons(fn: any): void {
if (!fn) return; if (!fn) return;
Object.keys(this.buttonElements).forEach(buttonName => Object.keys(this.buttonElements).forEach((buttonName) =>
this.buttonElements[buttonName].forEach(fn) this.buttonElements[buttonName].forEach(fn)
); );
} }
@ -892,7 +933,7 @@ class SimpleKeyboard {
/** /**
* Destroy keyboard listeners and DOM elements * Destroy keyboard listeners and DOM elements
*/ */
destroy() { destroy(): void {
if (this.options.debug) if (this.options.debug)
console.log( console.log(
`Destroying simple-keyboard instance: ${this.currentInstanceName}` `Destroying simple-keyboard instance: ${this.currentInstanceName}`
@ -913,7 +954,7 @@ class SimpleKeyboard {
/** /**
* Remove buttons * Remove buttons
*/ */
let deleteButton = buttonElement => { let deleteButton = (buttonElement: KeyboardButton) => {
buttonElement.onpointerdown = null; buttonElement.onpointerdown = null;
buttonElement.onpointerup = null; buttonElement.onpointerup = null;
buttonElement.onpointercancel = null; buttonElement.onpointercancel = null;
@ -960,12 +1001,12 @@ class SimpleKeyboard {
/** /**
* Process buttonTheme option * Process buttonTheme option
*/ */
getButtonThemeClasses(button) { getButtonThemeClasses(button: string): string[] {
const buttonTheme = this.options.buttonTheme; const buttonTheme = this.options.buttonTheme;
let buttonClasses = []; let buttonClasses: string[] = [];
if (Array.isArray(buttonTheme)) { if (Array.isArray(buttonTheme)) {
buttonTheme.forEach(themeObj => { buttonTheme.forEach((themeObj) => {
if ( if (
themeObj.class && themeObj.class &&
typeof themeObj.class === "string" && typeof themeObj.class === "string" &&
@ -993,11 +1034,11 @@ class SimpleKeyboard {
/** /**
* Process buttonAttributes option * Process buttonAttributes option
*/ */
setDOMButtonAttributes(button, callback) { setDOMButtonAttributes(button: string, callback: any): void {
const buttonAttributes = this.options.buttonAttributes; const buttonAttributes = this.options.buttonAttributes;
if (Array.isArray(buttonAttributes)) { if (Array.isArray(buttonAttributes)) {
buttonAttributes.forEach(attrObj => { buttonAttributes.forEach((attrObj) => {
if ( if (
attrObj.attribute && attrObj.attribute &&
typeof attrObj.attribute === "string" && typeof attrObj.attribute === "string" &&
@ -1038,7 +1079,7 @@ class SimpleKeyboard {
*/ */
/* istanbul ignore next */ /* istanbul ignore next */
disableContextualWindow() { disableContextualWindow() {
window.oncontextmenu = event => { window.oncontextmenu = (event: KeyboardHandlerEvent) => {
if (event.target.classList.contains("hg-button")) { if (event.target.classList.contains("hg-button")) {
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
@ -1144,7 +1185,7 @@ class SimpleKeyboard {
/** /**
* Register module * Register module
*/ */
registerModule = (name, initCallback) => { registerModule = (name: string, initCallback: any) => {
if (!this.modules[name]) this.modules[name] = {}; if (!this.modules[name]) this.modules[name] = {};
initCallback(this.modules[name]); initCallback(this.modules[name]);
@ -1155,7 +1196,7 @@ class SimpleKeyboard {
*/ */
loadModules() { loadModules() {
if (Array.isArray(this.options.modules)) { if (Array.isArray(this.options.modules)) {
this.options.modules.forEach(KeyboardModule => { this.options.modules.forEach((KeyboardModule) => {
const keyboardModule = new KeyboardModule(); const keyboardModule = new KeyboardModule();
keyboardModule.init(this); keyboardModule.init(this);
}); });
@ -1170,7 +1211,7 @@ class SimpleKeyboard {
/** /**
* Get module prop * Get module prop
*/ */
getModuleProp(name, prop) { getModuleProp(name: string, prop: string) {
if (!this.modules[name]) return false; if (!this.modules[name]) return false;
return this.modules[name][prop]; return this.modules[name][prop];
@ -1187,10 +1228,10 @@ class SimpleKeyboard {
* Parse Row DOM containers * Parse Row DOM containers
*/ */
parseRowDOMContainers( parseRowDOMContainers(
rowDOM, rowDOM: HTMLDivElement,
rowIndex, rowIndex: number,
containerStartIndexes, containerStartIndexes: number[],
containerEndIndexes containerEndIndexes: number[]
) { ) {
const rowDOMArray = Array.from(rowDOM.children); const rowDOMArray = Array.from(rowDOM.children);
let removedElements = 0; let removedElements = 0;
@ -1235,7 +1276,9 @@ class SimpleKeyboard {
/** /**
* Inserting elements to container * Inserting elements to container
*/ */
containedElements.forEach(element => containerDOM.appendChild(element)); containedElements.forEach((element) =>
containerDOM.appendChild(element)
);
/** /**
* Adding container at correct position within rowDOMArray * Adding container at correct position within rowDOMArray
@ -1250,7 +1293,7 @@ class SimpleKeyboard {
/** /**
* Appending rowDOM new children list * Appending rowDOM new children list
*/ */
rowDOMArray.forEach(element => rowDOM.appendChild(element)); rowDOMArray.forEach((element) => rowDOM.appendChild(element));
if (this.options.debug) { if (this.options.debug) {
console.log( console.log(
@ -1270,9 +1313,9 @@ class SimpleKeyboard {
/** /**
* getKeyboardClassString * getKeyboardClassString
*/ */
getKeyboardClassString = (...baseDOMClasses) => { getKeyboardClassString = (...baseDOMClasses: any[]) => {
const keyboardClasses = [this.keyboardDOMClass, ...baseDOMClasses].filter( const keyboardClasses = [this.keyboardDOMClass, ...baseDOMClasses].filter(
DOMClass => !!DOMClass (DOMClass) => !!DOMClass
); );
return keyboardClasses.join(" "); return keyboardClasses.join(" ");
@ -1331,8 +1374,8 @@ class SimpleKeyboard {
/** /**
* Tracking container indicators in rows * Tracking container indicators in rows
*/ */
const containerStartIndexes = []; const containerStartIndexes: number[] = [];
const containerEndIndexes = []; const containerEndIndexes: number[] = [];
/** /**
* Iterating through each button in row * Iterating through each button in row
@ -1399,9 +1442,12 @@ class SimpleKeyboard {
/** /**
* Adding buttonAttributes * Adding buttonAttributes
*/ */
this.setDOMButtonAttributes(button, (attribute, value) => { this.setDOMButtonAttributes(
button,
(attribute: string, value: string) => {
buttonDOM.setAttribute(attribute, value); buttonDOM.setAttribute(attribute, value);
}); }
);
this.activeButtonClass = "hg-activeButton"; this.activeButtonClass = "hg-activeButton";
@ -1417,14 +1463,14 @@ class SimpleKeyboard {
/** /**
* Handle PointerEvents * Handle PointerEvents
*/ */
buttonDOM.onpointerdown = e => { buttonDOM.onpointerdown = (e: KeyboardHandlerEvent) => {
this.handleButtonClicked(button); this.handleButtonClicked(button);
this.handleButtonMouseDown(button, e); this.handleButtonMouseDown(button, e);
}; };
buttonDOM.onpointerup = e => { buttonDOM.onpointerup = (e: KeyboardHandlerEvent) => {
this.handleButtonMouseUp(button, e); this.handleButtonMouseUp(button, e);
}; };
buttonDOM.onpointercancel = e => { buttonDOM.onpointercancel = (e: KeyboardHandlerEvent) => {
this.handleButtonMouseUp(button, e); this.handleButtonMouseUp(button, e);
}; };
} else { } else {
@ -1435,14 +1481,14 @@ class SimpleKeyboard {
/** /**
* Handle touch events * Handle touch events
*/ */
buttonDOM.ontouchstart = e => { buttonDOM.ontouchstart = (e: KeyboardHandlerEvent) => {
this.handleButtonClicked(button); this.handleButtonClicked(button);
this.handleButtonMouseDown(button, e); this.handleButtonMouseDown(button, e);
}; };
buttonDOM.ontouchend = e => { buttonDOM.ontouchend = (e: KeyboardHandlerEvent) => {
this.handleButtonMouseUp(button, e); this.handleButtonMouseUp(button, e);
}; };
buttonDOM.ontouchcancel = e => { buttonDOM.ontouchcancel = (e: KeyboardHandlerEvent) => {
this.handleButtonMouseUp(button, e); this.handleButtonMouseUp(button, e);
}; };
} else { } else {
@ -1453,10 +1499,10 @@ class SimpleKeyboard {
this.isMouseHold = false; this.isMouseHold = false;
this.handleButtonClicked(button); this.handleButtonClicked(button);
}; };
buttonDOM.onmousedown = e => { buttonDOM.onmousedown = (e: KeyboardHandlerEvent) => {
this.handleButtonMouseDown(button, e); this.handleButtonMouseDown(button, e);
}; };
buttonDOM.onmouseup = e => { buttonDOM.onmouseup = (e: KeyboardHandlerEvent) => {
this.handleButtonMouseUp(button, e); this.handleButtonMouseUp(button, e);
}; };
} }
@ -1531,7 +1577,7 @@ class SimpleKeyboard {
!useMouseEvents !useMouseEvents
) { ) {
document.onpointerup = () => this.handleButtonMouseUp(); document.onpointerup = () => this.handleButtonMouseUp();
this.keyboardDOM.onpointerdown = e => this.keyboardDOM.onpointerdown = (e: KeyboardHandlerEvent) =>
this.handleKeyboardContainerMouseDown(e); this.handleKeyboardContainerMouseDown(e);
} else if (useTouchEvents) { } else if (useTouchEvents) {
/** /**
@ -1540,14 +1586,14 @@ class SimpleKeyboard {
document.ontouchend = () => this.handleButtonMouseUp(); document.ontouchend = () => this.handleButtonMouseUp();
document.ontouchcancel = () => this.handleButtonMouseUp(); document.ontouchcancel = () => this.handleButtonMouseUp();
this.keyboardDOM.ontouchstart = e => this.keyboardDOM.ontouchstart = (e: KeyboardHandlerEvent) =>
this.handleKeyboardContainerMouseDown(e); this.handleKeyboardContainerMouseDown(e);
} else if (!useTouchEvents) { } else if (!useTouchEvents) {
/** /**
* Handling mouseup * Handling mouseup
*/ */
document.onmouseup = () => this.handleButtonMouseUp(); document.onmouseup = () => this.handleButtonMouseUp();
this.keyboardDOM.onmousedown = e => this.keyboardDOM.onmousedown = (e: KeyboardHandlerEvent) =>
this.handleKeyboardContainerMouseDown(e); this.handleKeyboardContainerMouseDown(e);
} }

186
src/lib/interfaces.ts Normal file
View File

@ -0,0 +1,186 @@
export interface KeyboardLayoutObject {
[key: string]: string[];
}
export interface KeyboardButtonTheme {
class: string;
buttons: string;
}
export interface KeyboardButtonAttributes {
attribute: string;
value: string;
buttons: string;
}
export interface KeyboardInput {
[key: string]: string
}
export type KeyboardButton = HTMLDivElement | HTMLButtonElement;
export type KeyboardHandlerEvent = PointerEvent & TouchEvent & KeyboardEvent & { target: HTMLDivElement | HTMLInputElement };
export interface KeyboardButtonElements {
[key: string]: KeyboardButton[]
}
export interface UtilitiesParams {
getOptions: () => KeyboardOptions;
getCaretPosition: () => number;
getCaretPositionEnd: () => number;
dispatch: any;
}
export interface KeyboardOptions {
/**
* Modify the keyboard layout.
*/
layout?: KeyboardLayoutObject;
/**
* Specifies which layout should be used.
*/
layoutName?: string;
/**
* Replaces variable buttons (such as `{bksp}`) with a human-friendly name (e.g.: `backspace`).
*/
display?: { [button: string]: string };
/**
* By default, when you set the display property, you replace the default one. This setting merges them instead.
*/
mergeDisplay?: boolean;
/**
* A prop to add your own css classes to the keyboard wrapper. You can add multiple classes separated by a space.
*/
theme?: string;
/**
* A prop to add your own css classes to one or several buttons.
*/
buttonTheme?: KeyboardButtonTheme[];
/**
* A prop to add your own attributes to one or several buttons.
*/
buttonAttributes?: KeyboardButtonAttributes[];
/**
* Runs a `console.log` every time a key is pressed. Displays the buttons pressed and the current input.
*/
debug?: boolean;
/**
* Specifies whether clicking the "ENTER" button will input a newline (`\n`) or not.
*/
newLineOnEnter?: boolean;
/**
* Specifies whether clicking the "TAB" button will input a tab character (`\t`) or not.
*/
tabCharOnTab?: boolean;
/**
* Allows you to use a single simple-keyboard instance for several inputs.
*/
inputName?: string;
/**
* `number`: Restrains all of simple-keyboard inputs to a certain length. This should be used in addition to the input elements maxlengthattribute.
*
* `{ [inputName: string]: number }`: Restrains simple-keyboards individual inputs to a certain length. This should be used in addition to the input elements maxlengthattribute.
*/
maxLength?: any;
/**
* When set to true, this option synchronizes the internal input of every simple-keyboard instance.
*/
syncInstanceInputs?: boolean;
/**
* Enable highlighting of keys pressed on physical keyboard.
*/
physicalKeyboardHighlight?: boolean;
/**
* Presses keys highlighted by physicalKeyboardHighlight
*/
physicalKeyboardHighlightPress?: boolean;
/**
* Define the text color that the physical keyboard highlighted key should have.
*/
physicalKeyboardHighlightTextColor?: string;
/**
* Define the background color that the physical keyboard highlighted key should have.
*/
physicalKeyboardHighlightBgColor?: string;
/**
* Calling preventDefault for the mousedown events keeps the focus on the input.
*/
preventMouseDownDefault?: boolean;
/**
* Calling preventDefault for the mouseup events.
*/
preventMouseUpDefault?: boolean;
/**
* Stops pointer down events on simple-keyboard buttons from bubbling to parent elements.
*/
stopMouseDownPropagation?: boolean;
/**
* Stops pointer up events on simple-keyboard buttons from bubbling to parent elements.
*/
stopMouseUpPropagation?: boolean;
/**
* Render buttons as a button element instead of a div element.
*/
useButtonTag?: boolean;
/**
* A prop to ensure characters are always be added/removed at the end of the string.
*/
disableCaretPositioning?: boolean;
/**
* Restrains input(s) change to the defined regular expression pattern.
*/
inputPattern?: any;
/**
* Instructs simple-keyboard to use touch events instead of click events.
*/
useTouchEvents?: boolean;
/**
* Enable useTouchEvents automatically when touch device is detected.
*/
autoUseTouchEvents?: boolean;
/**
* Opt out of PointerEvents handling, falling back to the prior mouse event logic.
*/
useMouseEvents?: boolean;
/**
* Disable button hold action.
*/
disableButtonHold?: boolean;
/**
* Adds unicode right-to-left control characters to input return values.
*/
rtl?: boolean;
/**
* Module options can have any format
*/
[name: string]: any;
}

View File

@ -5,14 +5,14 @@ export const getDefaultLayout = () => {
"{tab} q w e r t y u i o p [ ] \\", "{tab} q w e r t y u i o p [ ] \\",
"{lock} a s d f g h j k l ; ' {enter}", "{lock} a s d f g h j k l ; ' {enter}",
"{shift} z x c v b n m , . / {shift}", "{shift} z x c v b n m , . / {shift}",
".com @ {space}" ".com @ {space}",
], ],
shift: [ shift: [
"~ ! @ # $ % ^ & * ( ) _ + {bksp}", "~ ! @ # $ % ^ & * ( ) _ + {bksp}",
"{tab} Q W E R T Y U I O P { } |", "{tab} Q W E R T Y U I O P { } |",
'{lock} A S D F G H J K L : " {enter}', '{lock} A S D F G H J K L : " {enter}',
"{shift} Z X C V B N M < > ? {shift}", "{shift} Z X C V B N M < > ? {shift}",
".com @ {space}" ".com @ {space}",
] ],
}; };
}; };

View File

@ -1,13 +1,17 @@
import { KeyboardOptions, UtilitiesParams } from "../interfaces";
import Utilities from "../services/Utilities"; import Utilities from "../services/Utilities";
/** /**
* Physical Keyboard Service * Physical Keyboard Service
*/ */
class PhysicalKeyboard { class PhysicalKeyboard {
getOptions: () => KeyboardOptions;
dispatch: any;
/** /**
* Creates an instance of the PhysicalKeyboard service * Creates an instance of the PhysicalKeyboard service
*/ */
constructor({ dispatch, getOptions }) { constructor({ dispatch, getOptions }: Partial<UtilitiesParams>) {
/** /**
* @type {object} A simple-keyboard instance * @type {object} A simple-keyboard instance
*/ */
@ -20,11 +24,11 @@ class PhysicalKeyboard {
Utilities.bindMethods(PhysicalKeyboard, this); Utilities.bindMethods(PhysicalKeyboard, this);
} }
handleHighlightKeyDown(event) { handleHighlightKeyDown(event: KeyboardEvent) {
const options = this.getOptions(); const options = this.getOptions();
const buttonPressed = this.getSimpleKeyboardLayoutKey(event); const buttonPressed = this.getSimpleKeyboardLayoutKey(event);
this.dispatch(instance => { this.dispatch((instance: any) => {
const buttonDOM = const buttonDOM =
instance.getButtonElement(buttonPressed) || instance.getButtonElement(buttonPressed) ||
instance.getButtonElement(`{${buttonPressed}}`); instance.getButtonElement(`{${buttonPressed}}`);
@ -50,11 +54,11 @@ class PhysicalKeyboard {
}); });
} }
handleHighlightKeyUp(event) { handleHighlightKeyUp(event: KeyboardEvent) {
const options = this.getOptions(); const options = this.getOptions();
const buttonPressed = this.getSimpleKeyboardLayoutKey(event); const buttonPressed = this.getSimpleKeyboardLayoutKey(event);
this.dispatch(instance => { this.dispatch((instance: any) => {
const buttonDOM = const buttonDOM =
instance.getButtonElement(buttonPressed) || instance.getButtonElement(buttonPressed) ||
instance.getButtonElement(`{${buttonPressed}}`); instance.getButtonElement(`{${buttonPressed}}`);
@ -81,7 +85,7 @@ class PhysicalKeyboard {
* Transforms a KeyboardEvent's "key.code" string into a simple-keyboard layout format * Transforms a KeyboardEvent's "key.code" string into a simple-keyboard layout format
* @param {object} event The KeyboardEvent * @param {object} event The KeyboardEvent
*/ */
getSimpleKeyboardLayoutKey(event) { getSimpleKeyboardLayoutKey(event: KeyboardEvent) {
let output; let output;
if ( if (

View File

@ -1,11 +1,25 @@
import { KeyboardInput } from "./../interfaces";
import { KeyboardOptions, UtilitiesParams } from "../interfaces";
/** /**
* Utility Service * Utility Service
*/ */
class Utilities { class Utilities {
getOptions: () => KeyboardOptions;
getCaretPosition: () => number;
getCaretPositionEnd: () => number;
dispatch: any;
maxLengthReached: boolean;
/** /**
* Creates an instance of the Utility service * Creates an instance of the Utility service
*/ */
constructor({ getOptions, getCaretPosition, getCaretPositionEnd, dispatch }) { constructor({
getOptions,
getCaretPosition,
getCaretPositionEnd,
dispatch,
}: UtilitiesParams) {
this.getOptions = getOptions; this.getOptions = getOptions;
this.getCaretPosition = getCaretPosition; this.getCaretPosition = getCaretPosition;
this.getCaretPositionEnd = getCaretPositionEnd; this.getCaretPositionEnd = getCaretPositionEnd;
@ -23,7 +37,7 @@ class Utilities {
* @param {string} button The button's layout name * @param {string} button The button's layout name
* @return {string} The classes to be added to the button * @return {string} The classes to be added to the button
*/ */
getButtonClass(button) { getButtonClass(button: string) {
const buttonTypeClass = const buttonTypeClass =
button.includes("{") && button.includes("}") && button !== "{//}" button.includes("{") && button.includes("}") && button !== "{//}"
? "functionBtn" ? "functionBtn"
@ -100,7 +114,7 @@ class Utilities {
"{numpad6}": "6", "{numpad6}": "6",
"{numpad7}": "7", "{numpad7}": "7",
"{numpad8}": "8", "{numpad8}": "8",
"{numpad9}": "9" "{numpad9}": "9",
}; };
} }
/** /**
@ -110,7 +124,11 @@ class Utilities {
* @param {object} display The provided display option * @param {object} display The provided display option
* @param {boolean} mergeDisplay Whether the provided param value should be merged with the default one. * @param {boolean} mergeDisplay Whether the provided param value should be merged with the default one.
*/ */
getButtonDisplayName(button, display, mergeDisplay) { getButtonDisplayName(
button: string,
display: KeyboardOptions["display"],
mergeDisplay: boolean
) {
if (mergeDisplay) { if (mergeDisplay) {
display = Object.assign({}, this.getDefaultDiplay(), display); display = Object.assign({}, this.getDefaultDiplay(), display);
} else { } else {
@ -130,14 +148,18 @@ class Utilities {
* @param {boolean} moveCaret Whether to update simple-keyboard's cursor * @param {boolean} moveCaret Whether to update simple-keyboard's cursor
*/ */
getUpdatedInput( getUpdatedInput(
button, button: string,
input, input: string,
caretPos, caretPos: number,
caretPosEnd = caretPos, caretPosEnd = caretPos,
moveCaret = false moveCaret = false
) { ) {
const options = this.getOptions(); const options = this.getOptions();
const commonParams = [caretPos, caretPosEnd, moveCaret]; const commonParams: [number, number, boolean] = [
caretPos,
caretPosEnd,
moveCaret,
];
let output = input; let output = input;
@ -194,10 +216,10 @@ class Utilities {
* @param {number} length Represents by how many characters the input should be moved * @param {number} length Represents by how many characters the input should be moved
* @param {boolean} minus Whether the cursor should be moved to the left or not. * @param {boolean} minus Whether the cursor should be moved to the left or not.
*/ */
updateCaretPos(length, minus) { updateCaretPos(length: number, minus = false) {
const newCaretPos = this.updateCaretPosAction(length, minus); const newCaretPos = this.updateCaretPosAction(length, minus);
this.dispatch(instance => { this.dispatch((instance: any) => {
instance.setCaretPosition(newCaretPos); instance.setCaretPosition(newCaretPos);
}); });
} }
@ -208,7 +230,7 @@ class Utilities {
* @param {number} length Represents by how many characters the input should be moved * @param {number} length Represents by how many characters the input should be moved
* @param {boolean} minus Whether the cursor should be moved to the left or not. * @param {boolean} minus Whether the cursor should be moved to the left or not.
*/ */
updateCaretPosAction(length, minus) { updateCaretPosAction(length: number, minus = false) {
const options = this.getOptions(); const options = this.getOptions();
let caretPosition = this.getCaretPosition(); let caretPosition = this.getCaretPosition();
@ -219,7 +241,7 @@ class Utilities {
} }
if (options.debug) { if (options.debug) {
console.log("Caret at:", caretPosition, `(${this.keyboardDOMClass})`); console.log("Caret at:", caretPosition);
} }
return caretPosition; return caretPosition;
@ -234,8 +256,8 @@ class Utilities {
* @param {boolean} moveCaret Whether to update simple-keyboard's cursor * @param {boolean} moveCaret Whether to update simple-keyboard's cursor
*/ */
addStringAt( addStringAt(
source, source: string,
str, str: string,
position = source.length, position = source.length,
positionEnd = source.length, positionEnd = source.length,
moveCaret = false moveCaret = false
@ -268,7 +290,7 @@ class Utilities {
* @param {boolean} moveCaret Whether to update simple-keyboard's cursor * @param {boolean} moveCaret Whether to update simple-keyboard's cursor
*/ */
removeAt( removeAt(
source, source: string,
position = source.length, position = source.length,
positionEnd = source.length, positionEnd = source.length,
moveCaret = false moveCaret = false
@ -314,7 +336,7 @@ class Utilities {
} else { } else {
output = source.slice(0, position) + source.slice(positionEnd); output = source.slice(0, position) + source.slice(positionEnd);
if (moveCaret) { if (moveCaret) {
this.dispatch(instance => { this.dispatch((instance: any) => {
instance.setCaretPosition(position); instance.setCaretPosition(position);
}); });
} }
@ -328,7 +350,7 @@ class Utilities {
* @param {object} inputObj * @param {object} inputObj
* @param {string} updatedInput * @param {string} updatedInput
*/ */
handleMaxLength(inputObj, updatedInput) { handleMaxLength(inputObj: KeyboardInput, updatedInput: string) {
const options = this.getOptions(); const options = this.getOptions();
const maxLength = options.maxLength; const maxLength = options.maxLength;
const currentInput = inputObj[options.inputName]; const currentInput = inputObj[options.inputName];
@ -403,7 +425,7 @@ class Utilities {
* Bind all methods in a given class * Bind all methods in a given class
*/ */
static bindMethods(myClass, instance) { static bindMethods(myClass: any, instance: any) {
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
for (const myMethod of Object.getOwnPropertyNames(myClass.prototype)) { for (const myMethod of Object.getOwnPropertyNames(myClass.prototype)) {
const excludeMethod = const excludeMethod =
@ -419,8 +441,8 @@ class Utilities {
* *
* @param {string} str The string to transform. * @param {string} str The string to transform.
*/ */
camelCase(str) { camelCase(str: string): string {
if (!str) return false; if (!str) return;
return str return str
.toLowerCase() .toLowerCase()

View File

@ -1,5 +0,0 @@
// jest-dom adds custom jest matchers for asserting on DOM nodes.
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom/extend-expect';

17
tsconfig.json Normal file
View File

@ -0,0 +1,17 @@
{
"compilerOptions": {
"noImplicitAny": true,
"outFile": "build/index.d.ts",
"module": "esnext",
"target": "es5",
"allowJs": true,
"sourceMap": true,
"declaration": true,
"emitDeclarationOnly": true,
"suppressImplicitAnyIndexErrors": true,
"lib": ["es2018", "dom"],
"moduleResolution": "node",
},
"include": ["src/lib"],
"exclude": ["src/**/tests"],
}

90
webpack.config.js Normal file
View File

@ -0,0 +1,90 @@
const path = require('path');
const webpack = require('webpack');
const PrettierPlugin = require("prettier-webpack-plugin");
const TerserPlugin = require('terser-webpack-plugin');
const getPackageJson = require('./scripts/getPackageJson');
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");
const {
version,
name,
license,
repository,
author,
} = getPackageJson('version', 'name', 'license', 'repository', 'author');
const banner = `
${name} v${version}
${repository.url}
Copyright (c) ${author.replace(/ *<[^)]*> */g, " ")} and project contributors.
This source code is licensed under the ${license} license found in the
LICENSE file in the root directory of this source tree.
`;
module.exports = {
mode: "production",
devtool: 'source-map',
entry: './src/lib/index.ts',
output: {
filename: 'index.js',
path: path.resolve(__dirname, 'build'),
library: "SimpleKeyboard",
libraryTarget: 'umd',
clean: true
},
optimization: {
minimize: true,
minimizer: [
new TerserPlugin({ extractComments: false }),
new OptimizeCSSAssetsPlugin({
cssProcessorOptions: {
map: {
inline: false
}
}
})
],
},
devServer: {
open: true,
hot: true,
host: "localhost",
static: path.join(__dirname, 'demo'),
port: 9000
},
module: {
rules: [
{
test: /\.(m|j|t)s$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader'
}
},
{
test: /\.(sa|sc|c)ss$/,
use: [
MiniCssExtractPlugin.loader,
{ loader: "css-loader", options: { sourceMap: true } },
],
},
{
test: /\.(png|jpe?g|gif|svg|eot|ttf|woff|woff2)$/,
use: ['url-loader'],
}
]
},
plugins: [
new PrettierPlugin(),
new MiniCssExtractPlugin({
filename: 'css/index.css'
}),
new webpack.BannerPlugin(banner)
],
resolve: {
extensions: ['.ts', '.js', '.json']
}
};