2.30.0 - Updated boilerplate, added selection handling - per #637

This commit is contained in:
Francisco Hodge 2020-08-07 17:52:12 -04:00
parent 975978025d
commit 1f335f6b62
34 changed files with 3845 additions and 8230 deletions

View File

@ -1,6 +1,6 @@
/*! /*!
* *
* simple-keyboard v2.29.91 * simple-keyboard v2.30.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)
@ -8,5 +8,5 @@
* 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%;-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}
/*# sourceMappingURL=index.css.map */ /*# sourceMappingURL=index.css.map */

File diff suppressed because one or more lines are too long

13
build/index.d.ts vendored
View File

@ -213,6 +213,19 @@ declare module 'simple-keyboard' {
*/ */
caretPosition?: number; 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;
/** /**
* 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).

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -15,7 +15,7 @@ if (!NODE_ENV) {
} }
// https://github.com/bkeepers/dotenv#what-other-env-files-can-i-use // https://github.com/bkeepers/dotenv#what-other-env-files-can-i-use
var dotenvFiles = [ const dotenvFiles = [
`${paths.dotenv}.${NODE_ENV}.local`, `${paths.dotenv}.${NODE_ENV}.local`,
`${paths.dotenv}.${NODE_ENV}`, `${paths.dotenv}.${NODE_ENV}`,
// Don't include `.env.local` for `test` environment // Don't include `.env.local` for `test` environment
@ -46,7 +46,7 @@ dotenvFiles.forEach(dotenvFile => {
// It works similar to `NODE_PATH` in Node itself: // It works similar to `NODE_PATH` in Node itself:
// https://nodejs.org/api/modules.html#modules_loading_from_the_global_folders // 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. // 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. // 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 // https://github.com/facebook/create-react-app/issues/1023#issuecomment-265344421
// We also resolve them to make sure all tools using them work consistently. // We also resolve them to make sure all tools using them work consistently.
const appDirectory = fs.realpathSync(process.cwd()); const appDirectory = fs.realpathSync(process.cwd());
@ -57,7 +57,7 @@ process.env.NODE_PATH = (process.env.NODE_PATH || '')
.join(path.delimiter); .join(path.delimiter);
// Grab NODE_ENV and REACT_APP_* environment variables and prepare them to be // Grab NODE_ENV and REACT_APP_* environment variables and prepare them to be
// injected into the application via DefinePlugin in Webpack configuration. // injected into the application via DefinePlugin in webpack configuration.
const REACT_APP = /^REACT_APP_/i; const REACT_APP = /^REACT_APP_/i;
function getClientEnvironment(publicUrl) { function getClientEnvironment(publicUrl) {
@ -77,9 +77,17 @@ function getClientEnvironment(publicUrl) {
// This should only be used as an escape hatch. Normally you would put // 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. // images into the `src` and `import` them in code to get their paths.
PUBLIC_URL: publicUrl, 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 // Stringify all values so we can feed into webpack DefinePlugin
const stringified = { const stringified = {
'process.env': Object.keys(raw).reduce((env, key) => { 'process.env': Object.keys(raw).reduce((env, key) => {
env[key] = JSON.stringify(raw[key]); env[key] = JSON.stringify(raw[key]);

66
config/getHttpsConfig.js Normal file
View File

@ -0,0 +1,66 @@
'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

@ -13,10 +13,10 @@ module.exports = {
if (filename.match(/\.svg$/)) { if (filename.match(/\.svg$/)) {
// Based on how SVGR generates a component name: // Based on how SVGR generates a component name:
// https://github.com/smooth-code/svgr/blob/01b194cf967347d43d4cbe6b434404731b87cf27/packages/core/src/state.js#L6 // https://github.com/smooth-code/svgr/blob/01b194cf967347d43d4cbe6b434404731b87cf27/packages/core/src/state.js#L6
const pascalCaseFileName = camelcase(path.parse(filename).name, { const pascalCaseFilename = camelcase(path.parse(filename).name, {
pascalCase: true, pascalCase: true,
}); });
const componentName = `Svg${pascalCaseFileName}`; const componentName = `Svg${pascalCaseFilename}`;
return `const React = require('react'); return `const React = require('react');
module.exports = { module.exports = {
__esModule: true, __esModule: true,

View File

@ -7,7 +7,7 @@ const chalk = require('react-dev-utils/chalk');
const resolve = require('resolve'); const resolve = require('resolve');
/** /**
* Get the baseUrl of a compilerOptions object. * Get additional module paths based on the baseUrl of a compilerOptions object.
* *
* @param {Object} options * @param {Object} options
*/ */
@ -38,6 +38,15 @@ function getAdditionalModulePaths(options = {}) {
return [paths.appSrc]; 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. // Otherwise, throw an error.
throw new Error( throw new Error(
chalk.red.bold( chalk.red.bold(
@ -47,6 +56,48 @@ function getAdditionalModulePaths(options = {}) {
); );
} }
/**
* 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() { function getModules() {
// Check if TypeScript is setup // Check if TypeScript is setup
const hasTsConfig = fs.existsSync(paths.appTsConfig); const hasTsConfig = fs.existsSync(paths.appTsConfig);
@ -81,6 +132,8 @@ function getModules() {
return { return {
additionalModulePaths: additionalModulePaths, additionalModulePaths: additionalModulePaths,
webpackAliases: getWebpackAliases(options),
jestAliases: getJestAliases(options),
hasTsConfig, hasTsConfig,
}; };
} }

View File

@ -1,42 +1,22 @@
'use strict';
const path = require('path'); const path = require('path');
const fs = require('fs'); const fs = require('fs');
const url = require('url'); const getPublicUrlOrPath = require('react-dev-utils/getPublicUrlOrPath');
// Make sure any symlinks in the project folder are resolved: // Make sure any symlinks in the project folder are resolved:
// https://github.com/facebook/create-react-app/issues/637 // https://github.com/facebook/create-react-app/issues/637
const appDirectory = fs.realpathSync(process.cwd()); const appDirectory = fs.realpathSync(process.cwd());
const resolveApp = relativePath => path.resolve(appDirectory, relativePath); const resolveApp = relativePath => path.resolve(appDirectory, relativePath);
const envPublicUrl = process.env.PUBLIC_URL;
function ensureSlash(inputPath, needsSlash) {
const hasSlash = inputPath.endsWith('/');
if (hasSlash && !needsSlash) {
return inputPath.substr(0, inputPath.length - 1);
} else if (!hasSlash && needsSlash) {
return `${inputPath}/`;
} else {
return inputPath;
}
}
const getPublicUrl = appPackageJson =>
envPublicUrl || require(appPackageJson).homepage;
// We use `PUBLIC_URL` environment variable or "homepage" field to infer // We use `PUBLIC_URL` environment variable or "homepage" field to infer
// "public path" at which the app is served. // "public path" at which the app is served.
// Webpack needs to know it to put the right <script> hrefs into HTML even in // 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. // 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 // 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. // like /todos/42/static/js/bundle.7289d.js. We have to know the root.
function getServedPath(appPackageJson) { const publicUrlOrPath = '/';
const publicUrl = getPublicUrl(appPackageJson);
const servedUrl =
envPublicUrl || (publicUrl ? url.parse(publicUrl).pathname : '/');
return ensureSlash(servedUrl, true);
}
const moduleFileExtensions = [ const moduleFileExtensions = [
'web.mjs', 'web.mjs',
@ -87,9 +67,7 @@ module.exports = {
testsSetup: resolveModule(resolveApp, 'src/setupTests'), testsSetup: resolveModule(resolveApp, 'src/setupTests'),
proxySetup: resolveApp('src/setupProxy.js'), proxySetup: resolveApp('src/setupProxy.js'),
appNodeModules: resolveApp('node_modules'), appNodeModules: resolveApp('node_modules'),
publicUrl: getPublicUrl(resolveApp('package.json')), publicUrlOrPath
//servedPath: getServedPath(resolveApp('package.json')),
servedPath: ''
}; };
module.exports.moduleFileExtensions = moduleFileExtensions; module.exports.moduleFileExtensions = moduleFileExtensions;

View File

@ -1,7 +1,4 @@
'use strict';
const fs = require('fs'); const fs = require('fs');
const isWsl = require('is-wsl');
const path = require('path'); const path = require('path');
const webpack = require('webpack'); const webpack = require('webpack');
const resolve = require('resolve'); const resolve = require('resolve');
@ -38,6 +35,8 @@ const shouldUseSourceMap = process.env.GENERATE_SOURCEMAP !== 'false';
// makes for a smoother build process. // makes for a smoother build process.
const shouldInlineRuntimeChunk = process.env.INLINE_RUNTIME_CHUNK !== 'false'; const shouldInlineRuntimeChunk = process.env.INLINE_RUNTIME_CHUNK !== 'false';
const isExtendingEslintConfig = process.env.EXTEND_ESLINT === 'true';
const imageInlineSizeLimit = parseInt( const imageInlineSizeLimit = parseInt(
process.env.IMAGE_INLINE_SIZE_LIMIT || '10000' process.env.IMAGE_INLINE_SIZE_LIMIT || '10000'
); );
@ -57,20 +56,16 @@ module.exports = function(webpackEnv) {
const isEnvDevelopment = webpackEnv === 'development'; const isEnvDevelopment = webpackEnv === 'development';
const isEnvProduction = webpackEnv === 'production'; const isEnvProduction = webpackEnv === 'production';
// Webpack uses `publicPath` to determine where the app is being served from. // Variable used for enabling profiling in Production
// It requires a trailing slash, or the file assets will get an incorrect path. // passed into alias object. Uses a flag if passed into the build command
// In development, we always serve from the root. This makes config easier. const isEnvProductionProfile =
const publicPath = paths.servedPath isEnvProduction && process.argv.includes('--profile');
// Some apps do not use client-side routing with pushState.
// For these, "homepage" can be set to "." to enable relative asset paths.
const shouldUseRelativeAssetPaths = publicPath === './';
// `publicUrl` is just like `publicPath`, but we will provide it to our app // We will provide `paths.publicUrlOrPath` to our app
// as %PUBLIC_URL% in `index.html` and `process.env.PUBLIC_URL` in JavaScript. // 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. // Omit trailing slash as %PUBLIC_URL%/xyz looks better than %PUBLIC_URL%xyz.
const publicUrl = publicPath.slice(0, -1);
// Get environment variables to inject into our app. // Get environment variables to inject into our app.
const env = getClientEnvironment(publicUrl); const env = getClientEnvironment(paths.publicUrlOrPath.slice(0, -1));
// common function to get style loaders // common function to get style loaders
const getStyleLoaders = (cssOptions, preProcessor) => { const getStyleLoaders = (cssOptions, preProcessor) => {
@ -78,7 +73,11 @@ module.exports = function(webpackEnv) {
isEnvDevelopment && require.resolve('style-loader'), isEnvDevelopment && require.resolve('style-loader'),
isEnvProduction && { isEnvProduction && {
loader: MiniCssExtractPlugin.loader, loader: MiniCssExtractPlugin.loader,
options: shouldUseRelativeAssetPaths ? { publicPath: '../../' } : {}, // 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'), loader: require.resolve('css-loader'),
@ -114,16 +113,16 @@ module.exports = function(webpackEnv) {
loaders.push( loaders.push(
{ {
loader: require.resolve('resolve-url-loader'), loader: require.resolve('resolve-url-loader'),
options: { options: {
sourceMap: isEnvProduction && shouldUseSourceMap, sourceMap: isEnvProduction && shouldUseSourceMap,
}, },
}, },
{ {
loader: require.resolve(preProcessor), loader: require.resolve(preProcessor),
options: { options: {
sourceMap: true, sourceMap: true,
}, },
} }
); );
} }
return loaders; return loaders;
@ -170,10 +169,13 @@ module.exports = function(webpackEnv) {
// TODO: remove this when upgrading to webpack 5 // TODO: remove this when upgrading to webpack 5
futureEmitAssets: true, futureEmitAssets: true,
// There are also additional JS chunk files if you use code splitting. // There are also additional JS chunk files if you use code splitting.
chunkFilename: 'static/js/[name].[contenthash:8].chunk.js', 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. // We inferred the "public path" (such as / or /my-project) from homepage.
// We use "/" in development. publicPath: paths.publicUrlOrPath,
publicPath: publicPath,
// Point sourcemap entries to original disk location (format as URL on Windows) // Point sourcemap entries to original disk location (format as URL on Windows)
devtoolModuleFilenameTemplate: isEnvProduction devtoolModuleFilenameTemplate: isEnvProduction
? info => ? info =>
@ -182,9 +184,12 @@ module.exports = function(webpackEnv) {
.replace(/\\/g, '/') .replace(/\\/g, '/')
: isEnvDevelopment && : isEnvDevelopment &&
(info => path.resolve(info.absoluteResourcePath).replace(/\\/g, '/')), (info => path.resolve(info.absoluteResourcePath).replace(/\\/g, '/')),
// Prevents conflicts when multiple Webpack runtimes (from different apps) // Prevents conflicts when multiple webpack runtimes (from different apps)
// are used on the same page. // are used on the same page.
jsonpFunction: `webpackJsonp${appPackageJson.name}`, 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: { optimization: {
minimize: isEnvProduction, minimize: isEnvProduction,
@ -228,12 +233,6 @@ module.exports = function(webpackEnv) {
ascii_only: true, ascii_only: true,
}, },
}, },
// Use multi-process parallel running to improve the build speed
// Default number of concurrent runs: os.cpus().length - 1
// Disabled on WSL (Windows Subsystem for Linux) due to an issue with Terser
// https://github.com/webpack-contrib/terser-webpack-plugin/issues/21
parallel: !isWsl,
// Enable file caching
cache: true, cache: true,
sourceMap: shouldUseSourceMap, sourceMap: shouldUseSourceMap,
}), }),
@ -251,24 +250,26 @@ module.exports = function(webpackEnv) {
} }
: false, : false,
}, },
cssProcessorPluginOptions: {
preset: ['default', { minifyFontValues: { removeQuotes: false } }],
},
}), }),
], ],
// Automatically split vendor and commons // Automatically split vendor and commons
// https://twitter.com/wSokra/status/969633336732905474 // https://twitter.com/wSokra/status/969633336732905474
// https://medium.com/webpack/webpack-4-code-splitting-chunk-graph-and-the-splitchunks-optimization-be739a861366 // https://medium.com/webpack/webpack-4-code-splitting-chunk-graph-and-the-splitchunks-optimization-be739a861366
splitChunks: { splitChunks: {
/*chunks: 'all', cacheGroups: {
name: false,*/ default: false,
cacheGroups: { }
default: false,
}
}, },
// Keep the runtime chunk separated to enable long term caching // Keep the runtime chunk separated to enable long term caching
// https://twitter.com/wSokra/status/969679223278505985 // https://twitter.com/wSokra/status/969679223278505985
runtimeChunk: false, // https://github.com/facebook/create-react-app/issues/5358
runtimeChunk: false,
}, },
resolve: { resolve: {
// This allows you to set a fallback for where Webpack should look for modules. // 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" // We placed these paths second because we want `node_modules` to "win"
// if there are any conflicts. This matches Node resolution mechanism. // if there are any conflicts. This matches Node resolution mechanism.
// https://github.com/facebook/create-react-app/issues/253 // https://github.com/facebook/create-react-app/issues/253
@ -288,6 +289,12 @@ module.exports = function(webpackEnv) {
// Support React Native Web // Support React Native Web
// https://www.smashingmagazine.com/2016/08/a-glimpse-into-the-future-with-react-native-for-web/ // https://www.smashingmagazine.com/2016/08/a-glimpse-into-the-future-with-react-native-for-web/
'react-native': 'react-native-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: [ plugins: [
// Adds support for installing with Plug'n'Play, leading to faster installs and adding // Adds support for installing with Plug'n'Play, leading to faster installs and adding
@ -303,7 +310,7 @@ module.exports = function(webpackEnv) {
}, },
resolveLoader: { resolveLoader: {
plugins: [ plugins: [
// Also related to Plug'n'Play, but this time it tells Webpack to load its loaders // Also related to Plug'n'Play, but this time it tells webpack to load its loaders
// from the current package. // from the current package.
PnpWebpackPlugin.moduleLoader(module), PnpWebpackPlugin.moduleLoader(module),
], ],
@ -322,6 +329,7 @@ module.exports = function(webpackEnv) {
use: [ use: [
{ {
options: { options: {
cache: true,
formatter: require.resolve('react-dev-utils/eslintFormatter'), formatter: require.resolve('react-dev-utils/eslintFormatter'),
eslintPath: require.resolve('eslint'), eslintPath: require.resolve('eslint'),
resolvePluginsRelativeTo: __dirname, resolvePluginsRelativeTo: __dirname,
@ -330,7 +338,7 @@ module.exports = function(webpackEnv) {
loader: require.resolve('eslint-loader'), loader: require.resolve('eslint-loader'),
}, },
], ],
include: paths.appSrcDemo, include: paths.appSrcDemo,
}, },
{ {
// "oneOf" will traverse all following loaders until one will // "oneOf" will traverse all following loaders until one will
@ -352,7 +360,7 @@ module.exports = function(webpackEnv) {
// The preset includes JSX, Flow, TypeScript, and some ESnext features. // The preset includes JSX, Flow, TypeScript, and some ESnext features.
{ {
test: /\.(js|mjs|jsx|ts|tsx)$/, test: /\.(js|mjs|jsx|ts|tsx)$/,
include: paths.appSrcDemo, include: paths.appSrcDemo,
loader: require.resolve('babel-loader'), loader: require.resolve('babel-loader'),
options: { options: {
customize: require.resolve( customize: require.resolve(
@ -365,7 +373,8 @@ module.exports = function(webpackEnv) {
{ {
loaderMap: { loaderMap: {
svg: { svg: {
ReactComponent: '@svgr/webpack?-prettier,-svgo,+ref![path]', ReactComponent:
'@svgr/webpack?-svgo,+titleProp,+ref![path]',
}, },
}, },
}, },
@ -375,7 +384,8 @@ module.exports = function(webpackEnv) {
// It enables caching results in ./node_modules/.cache/babel-loader/ // It enables caching results in ./node_modules/.cache/babel-loader/
// directory for faster rebuilds. // directory for faster rebuilds.
cacheDirectory: true, cacheDirectory: true,
cacheCompression: isEnvProduction, // See #6846 for context on why cacheCompression is disabled
cacheCompression: false,
compact: isEnvProduction, compact: isEnvProduction,
}, },
}, },
@ -403,13 +413,14 @@ module.exports = function(webpackEnv) {
] ]
], ],
cacheDirectory: true, cacheDirectory: true,
cacheCompression: isEnvProduction, // See #6846 for context on why cacheCompression is disabled
cacheCompression: false,
// If an error happens in a package, it's possible to be // Babel sourcemaps are needed for debugging into node_modules
// because it was compiled. Thus, we don't want the browser // code. Without the options below, debuggers like VSCode
// debugger to show the original code. Instead, the code // show incorrect code and set breakpoints on the wrong lines.
// being evaluated would be much more helpful. sourceMaps: shouldUseSourceMap,
sourceMaps: false, inputSourceMap: shouldUseSourceMap,
}, },
}, },
// "postcss" loader applies autoprefixer to our CSS. // "postcss" loader applies autoprefixer to our CSS.
@ -439,8 +450,9 @@ module.exports = function(webpackEnv) {
use: getStyleLoaders({ use: getStyleLoaders({
importLoaders: 1, importLoaders: 1,
sourceMap: isEnvProduction && shouldUseSourceMap, sourceMap: isEnvProduction && shouldUseSourceMap,
modules: true, modules: {
getLocalIdent: getCSSModuleLocalIdent, getLocalIdent: getCSSModuleLocalIdent,
},
}), }),
}, },
// Opt-in support for SASS (using .scss or .sass extensions). // Opt-in support for SASS (using .scss or .sass extensions).
@ -451,7 +463,7 @@ module.exports = function(webpackEnv) {
exclude: sassModuleRegex, exclude: sassModuleRegex,
use: getStyleLoaders( use: getStyleLoaders(
{ {
importLoaders: 2, importLoaders: 3,
sourceMap: isEnvProduction && shouldUseSourceMap, sourceMap: isEnvProduction && shouldUseSourceMap,
}, },
'sass-loader' 'sass-loader'
@ -468,10 +480,11 @@ module.exports = function(webpackEnv) {
test: sassModuleRegex, test: sassModuleRegex,
use: getStyleLoaders( use: getStyleLoaders(
{ {
importLoaders: 2, importLoaders: 3,
sourceMap: isEnvProduction && shouldUseSourceMap, sourceMap: isEnvProduction && shouldUseSourceMap,
modules: true, modules: {
getLocalIdent: getCSSModuleLocalIdent, getLocalIdent: getCSSModuleLocalIdent,
},
}, },
'sass-loader' 'sass-loader'
), ),
@ -505,7 +518,7 @@ module.exports = function(webpackEnv) {
{}, {},
{ {
inject: true, inject: true,
template: paths.appHtml template: paths.appHtml,
}, },
isEnvProduction isEnvProduction
? { ? {
@ -527,15 +540,15 @@ module.exports = function(webpackEnv) {
), ),
// Inlines the webpack runtime script. This script is too small to warrant // Inlines the webpack runtime script. This script is too small to warrant
// a network request. // a network request.
// https://github.com/facebook/create-react-app/issues/5358
isEnvProduction && isEnvProduction &&
shouldInlineRuntimeChunk && shouldInlineRuntimeChunk &&
new InlineChunkHtmlPlugin(HtmlWebpackPlugin, [/runtime~.+[.]js/]), new InlineChunkHtmlPlugin(HtmlWebpackPlugin, [/runtime-.+[.]js/]),
// Makes some environment variables available in index.html. // Makes some environment variables available in index.html.
// The public URL is available as %PUBLIC_URL% in index.html, e.g.: // The public URL is available as %PUBLIC_URL% in index.html, e.g.:
// <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico"> // <link rel="icon" href="%PUBLIC_URL%/favicon.ico">
// In production, it will be an empty string unless you specify "homepage" // It will be an empty string unless you specify "homepage"
// in `package.json`, in which case it will be the pathname of that URL. // in `package.json`, in which case it will be the pathname of that URL.
// In development, this will be an empty string.
new InterpolateHtmlPlugin(HtmlWebpackPlugin, env.raw), new InterpolateHtmlPlugin(HtmlWebpackPlugin, env.raw),
// This gives some necessary context to module not found errors, such as // This gives some necessary context to module not found errors, such as
// the requesting resource. // the requesting resource.
@ -553,7 +566,7 @@ module.exports = function(webpackEnv) {
// See https://github.com/facebook/create-react-app/issues/240 // See https://github.com/facebook/create-react-app/issues/240
isEnvDevelopment && new CaseSensitivePathsPlugin(), isEnvDevelopment && new CaseSensitivePathsPlugin(),
// If you require a missing module and then `npm install` it, you still have // 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 // to restart the development server for webpack to discover it. This plugin
// makes the discovery automatic so you don't have to restart. // makes the discovery automatic so you don't have to restart.
// See https://github.com/facebook/create-react-app/issues/186 // See https://github.com/facebook/create-react-app/issues/186
isEnvDevelopment && isEnvDevelopment &&
@ -565,43 +578,52 @@ module.exports = function(webpackEnv) {
filename: 'index.css', filename: 'index.css',
chunkFilename: 'index.[contenthash:8].chunk.css', chunkFilename: 'index.[contenthash:8].chunk.css',
}), }),
// Generate a manifest file which contains a mapping of all asset filenames // Generate an asset manifest file with the following content:
// to their corresponding output file so that tools can pick it up without // - "files" key: Mapping of all asset filenames to their corresponding
// having to parse `index.html`. // output file so that tools can pick it up without having to parse
/*new ManifestPlugin({ // `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', fileName: 'asset-manifest.json',
publicPath: publicPath, publicPath: paths.publicUrlOrPath,
generate: (seed, files) => { generate: (seed, files, entrypoints) => {
const manifestFiles = files.reduce(function(manifest, file) { const manifestFiles = files.reduce((manifest, file) => {
manifest[file.name] = file.path; manifest[file.name] = file.path;
return manifest; return manifest;
}, seed); }, seed);
const entrypointFiles = entrypoints.main.filter(
fileName => !fileName.endsWith('.map')
);
return { return {
files: manifestFiles, files: manifestFiles,
entrypoints: entrypointFiles,
}; };
}, },
}),*/ }),*/
// Moment.js is an extremely popular library that bundles large locale files // 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 // by default due to how webpack interprets its code. This is a practical
// solution that requires the user to opt into importing specific locales. // solution that requires the user to opt into importing specific locales.
// https://github.com/jmblog/how-to-optimize-momentjs-with-webpack // https://github.com/jmblog/how-to-optimize-momentjs-with-webpack
// You can remove this if you don't use Moment.js: // You can remove this if you don't use Moment.js:
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/), new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
// Generate a service worker script that will precache, and keep up to date, // Generate a service worker script that will precache, and keep up to date,
// the HTML & assets that are part of the Webpack build. // the HTML & assets that are part of the webpack build.
/*isEnvProduction && /*isEnvProduction &&
new WorkboxWebpackPlugin.GenerateSW({ new WorkboxWebpackPlugin.GenerateSW({
clientsClaim: true, clientsClaim: true,
exclude: [/\.map$/, /asset-manifest\.json$/], exclude: [/\.map$/, /asset-manifest\.json$/],
importWorkboxFrom: 'cdn', importWorkboxFrom: 'cdn',
navigateFallback: publicUrl + '/index.html', navigateFallback: paths.publicUrlOrPath + 'index.html',
navigateFallbackBlacklist: [ navigateFallbackBlacklist: [
// Exclude URLs starting with /_, as they're likely an API call // Exclude URLs starting with /_, as they're likely an API call
new RegExp('^/_'), new RegExp('^/_'),
// Exclude URLs containing a dot, as they're likely a resource in // Exclude any URLs whose last part seems to be a file extension
// public/ and not a SPA route // as they're likely a resource and not a SPA route.
new RegExp('/[^/]+\\.[^/]+$'), // 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 // TypeScript type checking
@ -610,6 +632,7 @@ module.exports = function(webpackEnv) {
typescript: resolve.sync('typescript', { typescript: resolve.sync('typescript', {
basedir: paths.appNodeModules, basedir: paths.appNodeModules,
}), }),
async: isEnvDevelopment,
useTypescriptIncrementalApi: true, useTypescriptIncrementalApi: true,
checkSyntacticErrors: true, checkSyntacticErrors: true,
resolveModuleNameModule: process.versions.pnp resolveModuleNameModule: process.versions.pnp
@ -633,7 +656,7 @@ module.exports = function(webpackEnv) {
}), }),
].filter(Boolean), ].filter(Boolean),
// Some libraries import Node modules but don't use them in the browser. // 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. // Tell webpack to provide empty mocks for them so importing them works.
node: { node: {
module: 'empty', module: 'empty',
dgram: 'empty', dgram: 'empty',

View File

@ -1,5 +1,4 @@
const fs = require('fs'); const fs = require('fs');
const isWsl = require('is-wsl');
const path = require('path'); const path = require('path');
const webpack = require('webpack'); const webpack = require('webpack');
const resolve = require('resolve'); const resolve = require('resolve');
@ -11,7 +10,9 @@ const TerserPlugin = require('terser-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin'); const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const safePostCssParser = require('postcss-safe-parser'); const safePostCssParser = require('postcss-safe-parser');
const ManifestPlugin = require('webpack-manifest-plugin');
const InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin'); const InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin');
const WorkboxWebpackPlugin = require('workbox-webpack-plugin');
const WatchMissingNodeModulesPlugin = require('react-dev-utils/WatchMissingNodeModulesPlugin'); const WatchMissingNodeModulesPlugin = require('react-dev-utils/WatchMissingNodeModulesPlugin');
const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin'); const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin');
const getCSSModuleLocalIdent = require('react-dev-utils/getCSSModuleLocalIdent'); const getCSSModuleLocalIdent = require('react-dev-utils/getCSSModuleLocalIdent');
@ -24,6 +25,7 @@ const typescriptFormatter = require('react-dev-utils/typescriptFormatter');
const CopyWebpackPlugin = require('copy-webpack-plugin'); const CopyWebpackPlugin = require('copy-webpack-plugin');
const getPackageJson = require('./getPackageJson'); const getPackageJson = require('./getPackageJson');
const PrettierPlugin = require("prettier-webpack-plugin"); const PrettierPlugin = require("prettier-webpack-plugin");
const eslint = require('eslint');
const postcssNormalize = require('postcss-normalize'); const postcssNormalize = require('postcss-normalize');
const appPackageJson = require(paths.appPackageJson); const appPackageJson = require(paths.appPackageJson);
@ -33,6 +35,8 @@ const shouldUseSourceMap = process.env.GENERATE_SOURCEMAP !== 'false';
// makes for a smoother build process. // makes for a smoother build process.
const shouldInlineRuntimeChunk = process.env.INLINE_RUNTIME_CHUNK !== 'false'; const shouldInlineRuntimeChunk = process.env.INLINE_RUNTIME_CHUNK !== 'false';
const isExtendingEslintConfig = process.env.EXTEND_ESLINT === 'true';
const imageInlineSizeLimit = parseInt( const imageInlineSizeLimit = parseInt(
process.env.IMAGE_INLINE_SIZE_LIMIT || '10000' process.env.IMAGE_INLINE_SIZE_LIMIT || '10000'
); );
@ -70,24 +74,16 @@ module.exports = function(webpackEnv) {
const isEnvDevelopment = webpackEnv === 'development'; const isEnvDevelopment = webpackEnv === 'development';
const isEnvProduction = webpackEnv === 'production'; const isEnvProduction = webpackEnv === 'production';
// Webpack uses `publicPath` to determine where the app is being served from. // Variable used for enabling profiling in Production
// It requires a trailing slash, or the file assets will get an incorrect path. // passed into alias object. Uses a flag if passed into the build command
// In development, we always serve from the root. This makes config easier. const isEnvProductionProfile =
const publicPath = isEnvProduction isEnvProduction && process.argv.includes('--profile');
? paths.servedPath
: isEnvDevelopment && '/';
// Some apps do not use client-side routing with pushState.
// For these, "homepage" can be set to "." to enable relative asset paths.
const shouldUseRelativeAssetPaths = publicPath === './';
// `publicUrl` is just like `publicPath`, but we will provide it to our app // We will provide `paths.publicUrlOrPath` to our app
// as %PUBLIC_URL% in `index.html` and `process.env.PUBLIC_URL` in JavaScript. // 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. // Omit trailing slash as %PUBLIC_URL%/xyz looks better than %PUBLIC_URL%xyz.
const publicUrl = isEnvProduction
? publicPath.slice(0, -1)
: isEnvDevelopment && '';
// Get environment variables to inject into our app. // Get environment variables to inject into our app.
const env = getClientEnvironment(publicUrl); const env = getClientEnvironment(paths.publicUrlOrPath.slice(0, -1));
// common function to get style loaders // common function to get style loaders
const getStyleLoaders = (cssOptions, preProcessor) => { const getStyleLoaders = (cssOptions, preProcessor) => {
@ -95,7 +91,11 @@ module.exports = function(webpackEnv) {
isEnvDevelopment && require.resolve('style-loader'), isEnvDevelopment && require.resolve('style-loader'),
isEnvProduction && { isEnvProduction && {
loader: MiniCssExtractPlugin.loader, loader: MiniCssExtractPlugin.loader,
options: shouldUseRelativeAssetPaths ? { publicPath: '../../' } : {}, // 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'), loader: require.resolve('css-loader'),
@ -131,16 +131,16 @@ module.exports = function(webpackEnv) {
loaders.push( loaders.push(
{ {
loader: require.resolve('resolve-url-loader'), loader: require.resolve('resolve-url-loader'),
options: { options: {
sourceMap: isEnvProduction && shouldUseSourceMap, sourceMap: isEnvProduction && shouldUseSourceMap,
}, },
}, },
{ {
loader: require.resolve(preProcessor), loader: require.resolve(preProcessor),
options: { options: {
sourceMap: true, sourceMap: true,
}, },
} }
); );
} }
return loaders; return loaders;
@ -192,9 +192,10 @@ module.exports = function(webpackEnv) {
chunkFilename: isEnvProduction chunkFilename: isEnvProduction
? 'static/js/[name].[contenthash:8].chunk.js' ? 'static/js/[name].[contenthash:8].chunk.js'
: isEnvDevelopment && 'static/js/[name].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. // We inferred the "public path" (such as / or /my-project) from homepage.
// We use "/" in development. publicPath: paths.publicUrlOrPath,
publicPath: publicPath,
library: "SimpleKeyboard", library: "SimpleKeyboard",
libraryTarget: 'umd', libraryTarget: 'umd',
umdNamedDefine: true, umdNamedDefine: true,
@ -206,9 +207,12 @@ module.exports = function(webpackEnv) {
.replace(/\\/g, '/') .replace(/\\/g, '/')
: isEnvDevelopment && : isEnvDevelopment &&
(info => path.resolve(info.absoluteResourcePath).replace(/\\/g, '/')), (info => path.resolve(info.absoluteResourcePath).replace(/\\/g, '/')),
// Prevents conflicts when multiple Webpack runtimes (from different apps) // Prevents conflicts when multiple webpack runtimes (from different apps)
// are used on the same page. // are used on the same page.
jsonpFunction: `webpackJsonp${appPackageJson.name}`, 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: { optimization: {
minimize: isEnvProduction, minimize: isEnvProduction,
@ -244,6 +248,9 @@ module.exports = function(webpackEnv) {
keep_fnames: true, keep_fnames: true,
module: true module: true
}, },
// Added for profiling in devtools
keep_classnames: isEnvProductionProfile,
keep_fnames: isEnvProductionProfile,
output: { output: {
ecma: 5, ecma: 5,
comments: false, comments: false,
@ -252,15 +259,10 @@ module.exports = function(webpackEnv) {
ascii_only: true, ascii_only: true,
}, },
}, },
// Use multi-process parallel running to improve the build speed
// Default number of concurrent runs: os.cpus().length - 1
// Disabled on WSL (Windows Subsystem for Linux) due to an issue with Terser
// https://github.com/webpack-contrib/terser-webpack-plugin/issues/21
parallel: !isWsl,
// Enable file caching
cache: true, cache: true,
sourceMap: shouldUseSourceMap, sourceMap: shouldUseSourceMap,
}), }),
// This is only used in production mode
new webpack.BannerPlugin({ new webpack.BannerPlugin({
banner: banner, banner: banner,
entryOnly: true entryOnly: true
@ -279,23 +281,26 @@ module.exports = function(webpackEnv) {
} }
: false, : false,
}, },
cssProcessorPluginOptions: {
preset: ['default', { minifyFontValues: { removeQuotes: false } }],
},
}), }),
], ],
// Automatically split vendor and commons // Automatically split vendor and commons
// https://twitter.com/wSokra/status/969633336732905474 // https://twitter.com/wSokra/status/969633336732905474
// https://medium.com/webpack/webpack-4-code-splitting-chunk-graph-and-the-splitchunks-optimization-be739a861366 // https://medium.com/webpack/webpack-4-code-splitting-chunk-graph-and-the-splitchunks-optimization-be739a861366
splitChunks: { splitChunks: {
//chunks: 'all', cacheGroups: {
cacheGroups: { default: false,
default: false, }
}
}, },
// Keep the runtime chunk separated to enable long term caching // Keep the runtime chunk separated to enable long term caching
// https://twitter.com/wSokra/status/969679223278505985 // https://twitter.com/wSokra/status/969679223278505985
runtimeChunk: false, // https://github.com/facebook/create-react-app/issues/5358
runtimeChunk: false,
}, },
resolve: { resolve: {
// This allows you to set a fallback for where Webpack should look for modules. // 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" // We placed these paths second because we want `node_modules` to "win"
// if there are any conflicts. This matches Node resolution mechanism. // if there are any conflicts. This matches Node resolution mechanism.
// https://github.com/facebook/create-react-app/issues/253 // https://github.com/facebook/create-react-app/issues/253
@ -315,6 +320,12 @@ module.exports = function(webpackEnv) {
// Support React Native Web // Support React Native Web
// https://www.smashingmagazine.com/2016/08/a-glimpse-into-the-future-with-react-native-for-web/ // https://www.smashingmagazine.com/2016/08/a-glimpse-into-the-future-with-react-native-for-web/
'react-native': 'react-native-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: [ plugins: [
// Adds support for installing with Plug'n'Play, leading to faster installs and adding // Adds support for installing with Plug'n'Play, leading to faster installs and adding
@ -330,7 +341,7 @@ module.exports = function(webpackEnv) {
}, },
resolveLoader: { resolveLoader: {
plugins: [ plugins: [
// Also related to Plug'n'Play, but this time it tells Webpack to load its loaders // Also related to Plug'n'Play, but this time it tells webpack to load its loaders
// from the current package. // from the current package.
PnpWebpackPlugin.moduleLoader(module), PnpWebpackPlugin.moduleLoader(module),
], ],
@ -349,6 +360,7 @@ module.exports = function(webpackEnv) {
use: [ use: [
{ {
options: { options: {
cache: true,
formatter: require.resolve('react-dev-utils/eslintFormatter'), formatter: require.resolve('react-dev-utils/eslintFormatter'),
eslintPath: require.resolve('eslint'), eslintPath: require.resolve('eslint'),
resolvePluginsRelativeTo: __dirname, resolvePluginsRelativeTo: __dirname,
@ -357,7 +369,7 @@ module.exports = function(webpackEnv) {
loader: require.resolve('eslint-loader'), loader: require.resolve('eslint-loader'),
}, },
], ],
include: paths.appSrcLib, include: paths.appSrcLib,
}, },
{ {
// "oneOf" will traverse all following loaders until one will // "oneOf" will traverse all following loaders until one will
@ -392,7 +404,8 @@ module.exports = function(webpackEnv) {
{ {
loaderMap: { loaderMap: {
svg: { svg: {
ReactComponent: '@svgr/webpack?-prettier,-svgo,+ref![path]', ReactComponent:
'@svgr/webpack?-svgo,+titleProp,+ref![path]',
}, },
}, },
}, },
@ -402,7 +415,8 @@ module.exports = function(webpackEnv) {
// It enables caching results in ./node_modules/.cache/babel-loader/ // It enables caching results in ./node_modules/.cache/babel-loader/
// directory for faster rebuilds. // directory for faster rebuilds.
cacheDirectory: true, cacheDirectory: true,
cacheCompression: isEnvProduction, // See #6846 for context on why cacheCompression is disabled
cacheCompression: false,
compact: isEnvProduction, compact: isEnvProduction,
}, },
}, },
@ -430,13 +444,14 @@ module.exports = function(webpackEnv) {
] ]
], ],
cacheDirectory: true, cacheDirectory: true,
cacheCompression: isEnvProduction, // See #6846 for context on why cacheCompression is disabled
cacheCompression: false,
// If an error happens in a package, it's possible to be // Babel sourcemaps are needed for debugging into node_modules
// because it was compiled. Thus, we don't want the browser // code. Without the options below, debuggers like VSCode
// debugger to show the original code. Instead, the code // show incorrect code and set breakpoints on the wrong lines.
// being evaluated would be much more helpful. sourceMaps: shouldUseSourceMap,
sourceMaps: false, inputSourceMap: shouldUseSourceMap,
}, },
}, },
// "postcss" loader applies autoprefixer to our CSS. // "postcss" loader applies autoprefixer to our CSS.
@ -466,8 +481,9 @@ module.exports = function(webpackEnv) {
use: getStyleLoaders({ use: getStyleLoaders({
importLoaders: 1, importLoaders: 1,
sourceMap: isEnvProduction && shouldUseSourceMap, sourceMap: isEnvProduction && shouldUseSourceMap,
modules: true, modules: {
getLocalIdent: getCSSModuleLocalIdent, getLocalIdent: getCSSModuleLocalIdent,
},
}), }),
}, },
// Opt-in support for SASS (using .scss or .sass extensions). // Opt-in support for SASS (using .scss or .sass extensions).
@ -478,7 +494,7 @@ module.exports = function(webpackEnv) {
exclude: sassModuleRegex, exclude: sassModuleRegex,
use: getStyleLoaders( use: getStyleLoaders(
{ {
importLoaders: 2, importLoaders: 3,
sourceMap: isEnvProduction && shouldUseSourceMap, sourceMap: isEnvProduction && shouldUseSourceMap,
}, },
'sass-loader' 'sass-loader'
@ -495,10 +511,11 @@ module.exports = function(webpackEnv) {
test: sassModuleRegex, test: sassModuleRegex,
use: getStyleLoaders( use: getStyleLoaders(
{ {
importLoaders: 2, importLoaders: 3,
sourceMap: isEnvProduction && shouldUseSourceMap, sourceMap: isEnvProduction && shouldUseSourceMap,
modules: true, modules: {
getLocalIdent: getCSSModuleLocalIdent, getLocalIdent: getCSSModuleLocalIdent,
},
}, },
'sass-loader' 'sass-loader'
), ),
@ -555,15 +572,15 @@ module.exports = function(webpackEnv) {
), ),
// Inlines the webpack runtime script. This script is too small to warrant // Inlines the webpack runtime script. This script is too small to warrant
// a network request. // a network request.
// https://github.com/facebook/create-react-app/issues/5358
isEnvProduction && isEnvProduction &&
shouldInlineRuntimeChunk && shouldInlineRuntimeChunk &&
new InlineChunkHtmlPlugin(HtmlWebpackPlugin, [/runtime~.+[.]js/]), new InlineChunkHtmlPlugin(HtmlWebpackPlugin, [/runtime-.+[.]js/]),
// Makes some environment variables available in index.html. // Makes some environment variables available in index.html.
// The public URL is available as %PUBLIC_URL% in index.html, e.g.: // The public URL is available as %PUBLIC_URL% in index.html, e.g.:
// <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico"> // <link rel="icon" href="%PUBLIC_URL%/favicon.ico">
// In production, it will be an empty string unless you specify "homepage" // It will be an empty string unless you specify "homepage"
// in `package.json`, in which case it will be the pathname of that URL. // in `package.json`, in which case it will be the pathname of that URL.
// In development, this will be an empty string.
new InterpolateHtmlPlugin(HtmlWebpackPlugin, env.raw), new InterpolateHtmlPlugin(HtmlWebpackPlugin, env.raw),
// This gives some necessary context to module not found errors, such as // This gives some necessary context to module not found errors, such as
// the requesting resource. // the requesting resource.
@ -581,7 +598,7 @@ module.exports = function(webpackEnv) {
// See https://github.com/facebook/create-react-app/issues/240 // See https://github.com/facebook/create-react-app/issues/240
isEnvDevelopment && new CaseSensitivePathsPlugin(), isEnvDevelopment && new CaseSensitivePathsPlugin(),
// If you require a missing module and then `npm install` it, you still have // 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 // to restart the development server for webpack to discover it. This plugin
// makes the discovery automatic so you don't have to restart. // makes the discovery automatic so you don't have to restart.
// See https://github.com/facebook/create-react-app/issues/186 // See https://github.com/facebook/create-react-app/issues/186
isEnvDevelopment && isEnvDevelopment &&
@ -593,25 +610,32 @@ module.exports = function(webpackEnv) {
filename: 'css/index.css', filename: 'css/index.css',
chunkFilename: 'css/index.[contenthash:8].chunk.css', chunkFilename: 'css/index.[contenthash:8].chunk.css',
}), }),
// Generate a manifest file which contains a mapping of all asset filenames // Generate an asset manifest file with the following content:
// to their corresponding output file so that tools can pick it up without // - "files" key: Mapping of all asset filenames to their corresponding
// having to parse `index.html`. // output file so that tools can pick it up without having to parse
/*new ManifestPlugin({ // `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', fileName: 'asset-manifest.json',
publicPath: publicPath, publicPath: paths.publicUrlOrPath,
generate: (seed, files) => { generate: (seed, files, entrypoints) => {
const manifestFiles = files.reduce(function(manifest, file) { const manifestFiles = files.reduce((manifest, file) => {
manifest[file.name] = file.path; manifest[file.name] = file.path;
return manifest; return manifest;
}, seed); }, seed);
const entrypointFiles = entrypoints.main.filter(
fileName => !fileName.endsWith('.map')
);
return { return {
files: manifestFiles, files: manifestFiles,
entrypoints: entrypointFiles,
}; };
}, },
}),*/ }),*/
// Moment.js is an extremely popular library that bundles large locale files // 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 // by default due to how webpack interprets its code. This is a practical
// solution that requires the user to opt into importing specific locales. // solution that requires the user to opt into importing specific locales.
// https://github.com/jmblog/how-to-optimize-momentjs-with-webpack // https://github.com/jmblog/how-to-optimize-momentjs-with-webpack
// You can remove this if you don't use Moment.js: // You can remove this if you don't use Moment.js:
@ -624,19 +648,21 @@ module.exports = function(webpackEnv) {
]), ]),
new PrettierPlugin(), new PrettierPlugin(),
// Generate a service worker script that will precache, and keep up to date, // Generate a service worker script that will precache, and keep up to date,
// the HTML & assets that are part of the Webpack build. // the HTML & assets that are part of the webpack build.
/*isEnvProduction && /*isEnvProduction &&
new WorkboxWebpackPlugin.GenerateSW({ new WorkboxWebpackPlugin.GenerateSW({
clientsClaim: true, clientsClaim: true,
exclude: [/\.map$/, /asset-manifest\.json$/], exclude: [/\.map$/, /asset-manifest\.json$/],
importWorkboxFrom: 'cdn', importWorkboxFrom: 'cdn',
navigateFallback: publicUrl + '/index.html', navigateFallback: paths.publicUrlOrPath + 'index.html',
navigateFallbackBlacklist: [ navigateFallbackBlacklist: [
// Exclude URLs starting with /_, as they're likely an API call // Exclude URLs starting with /_, as they're likely an API call
new RegExp('^/_'), new RegExp('^/_'),
// Exclude URLs containing a dot, as they're likely a resource in // Exclude any URLs whose last part seems to be a file extension
// public/ and not a SPA route // as they're likely a resource and not a SPA route.
new RegExp('/[^/]+\\.[^/]+$'), // 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 // TypeScript type checking
@ -662,14 +688,13 @@ module.exports = function(webpackEnv) {
'!**/src/setupProxy.*', '!**/src/setupProxy.*',
'!**/src/setupTests.*', '!**/src/setupTests.*',
], ],
watch: paths.appSrcLib,
silent: true, silent: true,
// The formatter is invoked directly in WebpackDevServerUtils during development // The formatter is invoked directly in WebpackDevServerUtils during development
formatter: isEnvProduction ? typescriptFormatter : undefined, formatter: isEnvProduction ? typescriptFormatter : undefined,
}), }),
].filter(Boolean), ].filter(Boolean),
// Some libraries import Node modules but don't use them in the browser. // 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. // Tell webpack to provide empty mocks for them so importing them works.
node: { node: {
module: 'empty', module: 'empty',
dgram: 'empty', dgram: 'empty',

View File

@ -1,14 +1,18 @@
'use strict'; 'use strict';
const fs = require('fs');
const errorOverlayMiddleware = require('react-dev-utils/errorOverlayMiddleware'); const errorOverlayMiddleware = require('react-dev-utils/errorOverlayMiddleware');
const evalSourceMapMiddleware = require('react-dev-utils/evalSourceMapMiddleware'); const evalSourceMapMiddleware = require('react-dev-utils/evalSourceMapMiddleware');
const noopServiceWorkerMiddleware = require('react-dev-utils/noopServiceWorkerMiddleware'); const noopServiceWorkerMiddleware = require('react-dev-utils/noopServiceWorkerMiddleware');
const ignoredFiles = require('react-dev-utils/ignoredFiles'); const ignoredFiles = require('react-dev-utils/ignoredFiles');
const redirectServedPath = require('react-dev-utils/redirectServedPathMiddleware');
const paths = require('./paths'); const paths = require('./paths');
const fs = require('fs'); const getHttpsConfig = require('./getHttpsConfig');
const protocol = process.env.HTTPS === 'true' ? 'https' : 'http';
const host = process.env.HOST || '0.0.0.0'; 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) { module.exports = function(proxy, allowedHost) {
return { return {
@ -43,24 +47,39 @@ module.exports = function(proxy, allowedHost) {
// Instead, we establish a convention that only files in `public` directory // Instead, we establish a convention that only files in `public` directory
// get served. Our build script will copy `public` into the `build` folder. // 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%: // In `index.html`, you can get URL of `public` folder with %PUBLIC_URL%:
// <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico"> // <link rel="icon" href="%PUBLIC_URL%/favicon.ico">
// In JavaScript code, you can access it with `process.env.PUBLIC_URL`. // 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 // 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 files like `favicon.ico`, `manifest.json`, and libraries that are
// for some reason broken when imported through Webpack. If you just want to // 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. // use an image, put it in `src` and `import` it from JavaScript instead.
contentBase: paths.appPublic, contentBase: paths.appPublic,
contentBasePublicPath: paths.publicUrlOrPath,
// By default files from `contentBase` will not trigger a page reload. // By default files from `contentBase` will not trigger a page reload.
watchContentBase: true, watchContentBase: true,
// Enable hot reloading server. It will provide /sockjs-node/ endpoint // Enable hot reloading server. It will provide WDS_SOCKET_PATH endpoint
// for the WebpackDevServer client so it can learn when the files were // for the WebpackDevServer client so it can learn when the files were
// updated. The WebpackDevServer client is included as an entry point // updated. The WebpackDevServer client is included as an entry point
// in the Webpack development configuration. Note that only changes // in the webpack development configuration. Note that only changes
// to CSS are currently hot reloaded. JS changes will refresh the browser. // to CSS are currently hot reloaded. JS changes will refresh the browser.
hot: true, hot: true,
// It is important to tell WebpackDevServer to use the same "root" path // Use 'ws' instead of 'sockjs-node' on server since we're using native
// as we specified in the config. In development, we always serve from /. // websockets in `webpackHotDevClient`.
publicPath: '/', 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 // WebpackDevServer is noisy by default so we emit custom message instead
// by listening to the compiler events with `compiler.hooks[...].tap` calls above. // by listening to the compiler events with `compiler.hooks[...].tap` calls above.
quiet: true, quiet: true,
@ -71,34 +90,41 @@ module.exports = function(proxy, allowedHost) {
watchOptions: { watchOptions: {
ignored: ignoredFiles(paths.appSrc), ignored: ignoredFiles(paths.appSrc),
}, },
// Enable HTTPS if the HTTPS environment variable is set to 'true' https: getHttpsConfig(),
https: protocol === 'https',
host, host,
overlay: false, overlay: false,
historyApiFallback: { historyApiFallback: {
// Paths with dots should still use the history fallback. // Paths with dots should still use the history fallback.
// See https://github.com/facebook/create-react-app/issues/387. // See https://github.com/facebook/create-react-app/issues/387.
disableDotRule: true, disableDotRule: true,
index: paths.publicUrlOrPath,
}, },
public: allowedHost, public: allowedHost,
// `proxy` is run between `before` and `after` `webpack-dev-server` hooks
proxy, proxy,
before(app, server) { before(app, server) {
if (fs.existsSync(paths.proxySetup)) { // Keep `evalSourceMapMiddleware` and `errorOverlayMiddleware`
// This registers user provided middleware for proxy reasons // middlewares before `redirectServedPath` otherwise will not have any effect
require(paths.proxySetup)(app);
}
// This lets us fetch source contents from webpack for the error overlay // This lets us fetch source contents from webpack for the error overlay
app.use(evalSourceMapMiddleware(server)); app.use(evalSourceMapMiddleware(server));
// This lets us open files from the runtime error overlay. // This lets us open files from the runtime error overlay.
app.use(errorOverlayMiddleware()); 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 // This service worker file is effectively a 'no-op' that will reset any
// previous service worker registered for the same host:port combination. // previous service worker registered for the same host:port combination.
// We do this in development to avoid hitting the production cache if // We do this in development to avoid hitting the production cache if
// it used the same host and port. // it used the same host and port.
// https://github.com/facebook/create-react-app/issues/2272#issuecomment-302832432 // https://github.com/facebook/create-react-app/issues/2272#issuecomment-302832432
app.use(noopServiceWorkerMiddleware()); app.use(noopServiceWorkerMiddleware(paths.publicUrlOrPath));
}, },
}; };
}; };

10257
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{ {
"name": "simple-keyboard", "name": "simple-keyboard",
"version": "2.29.91", "version": "2.30.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",
@ -39,14 +39,17 @@
"js" "js"
], ],
"license": "MIT", "license": "MIT",
"devDependencies": { "dependencies": {
"@babel/core": "7.11.1", "@babel/core": "7.9.0",
"@babel/plugin-proposal-class-properties": "^7.10.4", "@babel/plugin-proposal-class-properties": "^7.10.4",
"@babel/preset-env": "^7.11.0", "@babel/preset-env": "^7.11.0",
"@babel/preset-react": "^7.10.4", "@babel/preset-react": "^7.10.4",
"@svgr/webpack": "5.4.0", "@svgr/webpack": "5.4.0",
"@typescript-eslint/eslint-plugin": "1.13.0", "@testing-library/jest-dom": "^4.2.4",
"@typescript-eslint/parser": "1.13.0", "@testing-library/react": "^9.3.2",
"@testing-library/user-event": "^7.1.2",
"@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.2.2", "babel-jest": "^26.2.2",
"babel-loader": "8.1.0", "babel-loader": "8.1.0",
@ -70,7 +73,6 @@
"fs-extra": "9.0.1", "fs-extra": "9.0.1",
"html-webpack-plugin": "4.3.0", "html-webpack-plugin": "4.3.0",
"identity-obj-proxy": "3.0.0", "identity-obj-proxy": "3.0.0",
"is-wsl": "^2.2.0",
"jest": "26.2.2", "jest": "26.2.2",
"jest-environment-jsdom-fourteen": "1.0.1", "jest-environment-jsdom-fourteen": "1.0.1",
"jest-resolve": "26.2.2", "jest-resolve": "26.2.2",
@ -87,7 +89,7 @@
"prettier-webpack-plugin": "^1.2.0", "prettier-webpack-plugin": "^1.2.0",
"react": "^16.13.1", "react": "^16.13.1",
"react-app-polyfill": "^1.0.6", "react-app-polyfill": "^1.0.6",
"react-dev-utils": "10.1.0", "react-dev-utils": "^10.2.1",
"react-dom": "^16.13.1", "react-dom": "^16.13.1",
"resolve": "1.17.0", "resolve": "1.17.0",
"resolve-url-loader": "3.1.1", "resolve-url-loader": "3.1.1",
@ -134,7 +136,9 @@
"setupFiles": [ "setupFiles": [
"react-app-polyfill/jsdom" "react-app-polyfill/jsdom"
], ],
"setupFilesAfterEnv": [], "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}"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@ -4,25 +4,14 @@
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta <meta
name="viewport" name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" content="width=device-width, initial-scale=1, shrink-to-fit=no"
/> />
<meta name="theme-color" content="#000000" /> <title>Demo</title>
<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> </head>
<body> <body>
<noscript> <noscript>
You need to enable JavaScript to run this app. You need to enable JavaScript to run this app.
</noscript> </noscript>
<div id="root"></div> <div id="root">Demo Page</div>
</body> </body>
</html> </html>

View File

@ -1,4 +1,4 @@
'use strict';
// Do this as the first thing so that any code reading it knows the right env. // Do this as the first thing so that any code reading it knows the right env.
process.env.BABEL_ENV = 'production'; process.env.BABEL_ENV = 'production';
@ -94,7 +94,7 @@ checkBrowsers(paths.appPath, isInteractive)
console.log(); console.log();
const appPackage = require(paths.appPackageJson); const appPackage = require(paths.appPackageJson);
const publicUrl = paths.publicUrl; const publicUrl = paths.publicUrlOrPath;
const publicPath = config.output.publicPath; const publicPath = config.output.publicPath;
const buildFolder = path.relative(process.cwd(), paths.appBuild); const buildFolder = path.relative(process.cwd(), paths.appBuild);
printHostingInstructions( printHostingInstructions(
@ -106,9 +106,19 @@ checkBrowsers(paths.appPath, isInteractive)
); );
}, },
err => { err => {
console.error('Failed to compile'); const tscCompileOnError = process.env.TSC_COMPILE_ON_ERROR === 'true';
printBuildError(err); if (tscCompileOnError) {
process.exit(1); 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 => { .catch(err => {
@ -142,8 +152,18 @@ function build(previousFileSizes) {
if (!err.message) { if (!err.message) {
return reject(err); 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({ messages = formatWebpackMessages({
errors: [err.message], errors: [errMessage],
warnings: [], warnings: [],
}); });
} else { } else {

View File

@ -1,5 +1,3 @@
'use strict';
// Do this as the first thing so that any code reading it knows the right env. // Do this as the first thing so that any code reading it knows the right env.
process.env.BABEL_ENV = 'production'; process.env.BABEL_ENV = 'production';
process.env.NODE_ENV = 'production'; process.env.NODE_ENV = 'production';
@ -94,7 +92,7 @@ checkBrowsers(paths.appPath, isInteractive)
console.log(); console.log();
const appPackage = require(paths.appPackageJson); const appPackage = require(paths.appPackageJson);
const publicUrl = paths.publicUrl; const publicUrl = paths.publicUrlOrPath;
const publicPath = config.output.publicPath; const publicPath = config.output.publicPath;
const buildFolder = path.relative(process.cwd(), paths.appDemo); const buildFolder = path.relative(process.cwd(), paths.appDemo);
printHostingInstructions( printHostingInstructions(
@ -106,9 +104,19 @@ checkBrowsers(paths.appPath, isInteractive)
); );
}, },
err => { err => {
console.error('Failed to compile'); const tscCompileOnError = process.env.TSC_COMPILE_ON_ERROR === 'true';
printBuildError(err); if (tscCompileOnError) {
process.exit(1); 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 => { .catch(err => {
@ -142,8 +150,18 @@ function build(previousFileSizes) {
if (!err.message) { if (!err.message) {
return reject(err); 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({ messages = formatWebpackMessages({
errors: [err.message], errors: [errMessage],
warnings: [], warnings: [],
}); });
} else { } else {

View File

@ -1,4 +1,4 @@
'use strict';
// Do this as the first thing so that any code reading it knows the right env. // Do this as the first thing so that any code reading it knows the right env.
process.env.BABEL_ENV = 'development'; process.env.BABEL_ENV = 'development';
@ -81,7 +81,13 @@ checkBrowsers(paths.appPath, isInteractive)
const protocol = process.env.HTTPS === 'true' ? 'https' : 'http'; const protocol = process.env.HTTPS === 'true' ? 'https' : 'http';
const appName = require(paths.appPackageJson).name; const appName = require(paths.appPackageJson).name;
const useTypeScript = fs.existsSync(paths.appTsConfig); const useTypeScript = fs.existsSync(paths.appTsConfig);
const urls = prepareUrls(protocol, HOST, port); const tscCompileOnError = process.env.TSC_COMPILE_ON_ERROR === 'true';
const urls = prepareUrls(
protocol,
HOST,
port,
paths.publicUrlOrPath.slice(0, -1)
);
const devSocket = { const devSocket = {
warnings: warnings => warnings: warnings =>
devServer.sockWrite(devServer.sockets, 'warnings', warnings), devServer.sockWrite(devServer.sockets, 'warnings', warnings),
@ -96,11 +102,15 @@ checkBrowsers(paths.appPath, isInteractive)
urls, urls,
useYarn, useYarn,
useTypeScript, useTypeScript,
tscCompileOnError,
webpack, webpack,
}); });
// Load proxy config // Load proxy config
const proxySetting = require(paths.appPackageJson).proxy; const proxySetting = require(paths.appPackageJson).proxy;
const proxyConfig = prepareProxy(proxySetting, paths.appPublic); const proxyConfig = prepareProxy(
proxySetting,
paths.appPublic
);
// Serve webpack assets generated by the compiler over a web server. // Serve webpack assets generated by the compiler over a web server.
const serverConfig = createDevServerConfig( const serverConfig = createDevServerConfig(
proxyConfig, proxyConfig,
@ -141,11 +151,10 @@ checkBrowsers(paths.appPath, isInteractive)
if (stats.errors && stats.errors.length > 0) { if (stats.errors && stats.errors.length > 0) {
devServer.close(); devServer.close();
process.exit(1); process.exit(1);
return;
} }
console.warn("App started in test mode. Closing in 5 seconds."); console.warn("App started in test mode. Closing in 5 seconds.");
let closeTimeout = setTimeout(() => { const closeTimeout = setTimeout(() => {
clearTimeout(closeTimeout); clearTimeout(closeTimeout);
devServer.close(); devServer.close();
process.exit(); process.exit();
@ -160,6 +169,15 @@ checkBrowsers(paths.appPath, isInteractive)
process.exit(); 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 => { .catch(err => {
if (err && err.message) { if (err && err.message) {

View File

@ -32,7 +32,10 @@ class Demo {
}); });
console.log(this.keyboard); console.log(this.keyboard);
setTimeout(this.keyboard.destroy, 10000); setTimeout(() => {
this.keyboard.destroy();
document.querySelector(".input").value = "";
}, 10000);
setTimeout(() => { setTimeout(() => {
this.keyboard = new Keyboard({ this.keyboard = new Keyboard({
theme: "hg-theme-default myTheme", theme: "hg-theme-default myTheme",

View File

@ -1,16 +1,14 @@
import TestUtility from '../../utils/TestUtility'; import { setDOM } from '../../utils/TestUtility';
import BasicDemo from '../BasicDemo'; import BasicDemo from '../BasicDemo';
const testUtil = new TestUtility();
it('Demo will load', () => { it('Demo will load', () => {
testUtil.setDOM(); setDOM();
new BasicDemo(); new BasicDemo();
}); });
it('Demo onDOMLoaded will work', () => { it('Demo onDOMLoaded will work', () => {
testUtil.setDOM(); setDOM();
const demo = new BasicDemo(); const demo = new BasicDemo();
@ -18,7 +16,7 @@ it('Demo onDOMLoaded will work', () => {
}); });
it('Demo onChange will work', () => { it('Demo onChange will work', () => {
testUtil.setDOM(); setDOM();
const demo = new BasicDemo(); const demo = new BasicDemo();
@ -28,7 +26,7 @@ it('Demo onChange will work', () => {
}); });
it('Demo onChange will work', () => { it('Demo onChange will work', () => {
testUtil.setDOM(); setDOM();
const demo = new BasicDemo(); const demo = new BasicDemo();
@ -38,7 +36,7 @@ it('Demo onChange will work', () => {
}); });
it('Demo input change will work', () => { it('Demo input change will work', () => {
testUtil.setDOM(); setDOM();
const demo = new BasicDemo(); const demo = new BasicDemo();
@ -49,7 +47,7 @@ it('Demo input change will work', () => {
}); });
it('Demo handleShiftButton will work', () => { it('Demo handleShiftButton will work', () => {
testUtil.setDOM(); setDOM();
const demo = new BasicDemo(); const demo = new BasicDemo();

View File

@ -1,16 +1,14 @@
import TestUtility from '../../utils/TestUtility'; import { setDOM } from '../../utils/TestUtility';
import ButtonThemeDemo from '../ButtonThemeDemo'; import ButtonThemeDemo from '../ButtonThemeDemo';
const testUtil = new TestUtility();
it('Demo will load', () => { it('Demo will load', () => {
testUtil.setDOM(); setDOM();
new ButtonThemeDemo(); new ButtonThemeDemo();
}); });
it('Demo onDOMLoaded will work', () => { it('Demo onDOMLoaded will work', () => {
testUtil.setDOM(); setDOM();
const demo = new ButtonThemeDemo(); const demo = new ButtonThemeDemo();
@ -18,7 +16,7 @@ it('Demo onDOMLoaded will work', () => {
}); });
it('Demo onChange will work', () => { it('Demo onChange will work', () => {
testUtil.setDOM(); setDOM();
const demo = new ButtonThemeDemo(); const demo = new ButtonThemeDemo();
@ -28,7 +26,7 @@ it('Demo onChange will work', () => {
}); });
it('Demo onChange will work', () => { it('Demo onChange will work', () => {
testUtil.setDOM(); setDOM();
const demo = new ButtonThemeDemo(); const demo = new ButtonThemeDemo();
@ -38,7 +36,7 @@ it('Demo onChange will work', () => {
}); });
it('Demo input change will work', () => { it('Demo input change will work', () => {
testUtil.setDOM(); setDOM();
const demo = new ButtonThemeDemo(); const demo = new ButtonThemeDemo();
@ -49,7 +47,7 @@ it('Demo input change will work', () => {
}); });
it('Demo handleShiftButton will work', () => { it('Demo handleShiftButton will work', () => {
testUtil.setDOM(); setDOM();
const demo = new ButtonThemeDemo(); const demo = new ButtonThemeDemo();
@ -61,7 +59,7 @@ it('Demo handleShiftButton will work', () => {
}); });
it('Demo buttons will have proper attributes and classes', () => { it('Demo buttons will have proper attributes and classes', () => {
testUtil.setDOM(); setDOM();
const demo = new ButtonThemeDemo(); const demo = new ButtonThemeDemo();

View File

@ -1,15 +1,13 @@
import TestUtility from '../../utils/TestUtility'; import { setDOM } from '../../utils/TestUtility';
import FullKeyboardDemo from '../FullKeyboardDemo'; import FullKeyboardDemo from '../FullKeyboardDemo';
const testUtil = new TestUtility();
it('Demo will load', () => { it('Demo will load', () => {
testUtil.setDOM(); setDOM();
new FullKeyboardDemo(); new FullKeyboardDemo();
}); });
it('Demo onDOMLoaded will work', () => { it('Demo onDOMLoaded will work', () => {
testUtil.setDOM(); setDOM();
const demo = new FullKeyboardDemo(); const demo = new FullKeyboardDemo();
@ -17,7 +15,7 @@ it('Demo onDOMLoaded will work', () => {
}); });
it('Demo onChange will work', () => { it('Demo onChange will work', () => {
testUtil.setDOM(); setDOM();
const demo = new FullKeyboardDemo(); const demo = new FullKeyboardDemo();
@ -27,7 +25,7 @@ it('Demo onChange will work', () => {
}); });
it('Demo onChange will work', () => { it('Demo onChange will work', () => {
testUtil.setDOM(); setDOM();
const demo = new FullKeyboardDemo(); const demo = new FullKeyboardDemo();
@ -37,7 +35,7 @@ it('Demo onChange will work', () => {
}); });
it('Demo input change will work', () => { it('Demo input change will work', () => {
testUtil.setDOM(); setDOM();
const demo = new FullKeyboardDemo(); const demo = new FullKeyboardDemo();
@ -49,7 +47,7 @@ it('Demo input change will work', () => {
}); });
it('Demo handleShiftButton will work', () => { it('Demo handleShiftButton will work', () => {
testUtil.setDOM(); setDOM();
const demo = new FullKeyboardDemo(); const demo = new FullKeyboardDemo();

View File

@ -1,18 +1,16 @@
import TestUtility from '../../utils/TestUtility'; import { setDOM } from '../../utils/TestUtility';
import MultipleKeyboardsDestroyDemo from '../MultipleKeyboardsDestroyDemo'; import MultipleKeyboardsDestroyDemo from '../MultipleKeyboardsDestroyDemo';
jest.useFakeTimers(); jest.useFakeTimers();
const testUtil = new TestUtility();
it('Demo will load', () => { it('Demo will load', () => {
testUtil.setDOM(); setDOM();
new MultipleKeyboardsDestroyDemo(); new MultipleKeyboardsDestroyDemo();
}); });
it('Demo onDOMLoaded will work', () => { it('Demo onDOMLoaded will work', () => {
testUtil.setDOM(); setDOM();
const demo = new MultipleKeyboardsDestroyDemo(); const demo = new MultipleKeyboardsDestroyDemo();
@ -20,7 +18,7 @@ it('Demo onDOMLoaded will work', () => {
}); });
it('Demo onChange will work', () => { it('Demo onChange will work', () => {
testUtil.setDOM(); setDOM();
const demo = new MultipleKeyboardsDestroyDemo(); const demo = new MultipleKeyboardsDestroyDemo();
@ -32,7 +30,7 @@ it('Demo onChange will work', () => {
}); });
it('Demo onChange will work', () => { it('Demo onChange will work', () => {
testUtil.setDOM(); setDOM();
const demo = new MultipleKeyboardsDestroyDemo(); const demo = new MultipleKeyboardsDestroyDemo();
@ -42,7 +40,7 @@ it('Demo onChange will work', () => {
}); });
it('Demo input change will work', () => { it('Demo input change will work', () => {
testUtil.setDOM(); setDOM();
const demo = new MultipleKeyboardsDestroyDemo(); const demo = new MultipleKeyboardsDestroyDemo();
@ -57,7 +55,7 @@ it('Demo input change will work', () => {
}); });
it('Demo handleShiftButton will work', () => { it('Demo handleShiftButton will work', () => {
testUtil.setDOM(); setDOM();
const demo = new MultipleKeyboardsDestroyDemo(); const demo = new MultipleKeyboardsDestroyDemo();
@ -69,7 +67,7 @@ it('Demo handleShiftButton will work', () => {
}); });
it('MultipleKeyboardsDestroyDemo will run all timers', () => { it('MultipleKeyboardsDestroyDemo will run all timers', () => {
testUtil.setDOM(); setDOM();
const demo = new MultipleKeyboardsDestroyDemo(); const demo = new MultipleKeyboardsDestroyDemo();
jest.runAllTimers(); jest.runAllTimers();

View File

@ -213,6 +213,19 @@ declare module 'simple-keyboard' {
*/ */
caretPosition?: number; 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;
/** /**
* 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).

View File

@ -27,7 +27,9 @@ class SimpleKeyboard {
*/ */
this.utilities = new Utilities({ this.utilities = new Utilities({
getOptions: this.getOptions, getOptions: this.getOptions,
setCaretPosition: this.setCaretPosition,
getCaretPosition: this.getCaretPosition, getCaretPosition: this.getCaretPosition,
getCaretPositionEnd: this.getCaretPositionEnd,
dispatch: this.dispatch dispatch: this.dispatch
}); });
@ -36,6 +38,11 @@ class SimpleKeyboard {
*/ */
this.caretPosition = null; this.caretPosition = null;
/**
* Caret position end
*/
this.caretPositionEnd = null;
/** /**
* Processing options * Processing options
*/ */
@ -219,6 +226,15 @@ class SimpleKeyboard {
*/ */
getOptions = () => this.options; getOptions = () => this.options;
getCaretPosition = () => this.caretPosition; getCaretPosition = () => this.caretPosition;
getCaretPositionEnd = () => this.caretPositionEnd;
/**
* Setters
*/
setCaretPosition = (position, endPosition) => {
this.caretPosition = position;
this.caretPositionEnd = endPosition || position;
};
/** /**
* Handles clicks made to keyboard buttons * Handles clicks made to keyboard buttons
@ -244,7 +260,8 @@ class SimpleKeyboard {
const updatedInput = this.utilities.getUpdatedInput( const updatedInput = this.utilities.getUpdatedInput(
button, button,
this.input[this.options.inputName], this.input[this.options.inputName],
this.caretPosition this.caretPosition,
this.caretPositionEnd
); );
if ( if (
@ -270,11 +287,21 @@ class SimpleKeyboard {
button, button,
this.input[this.options.inputName], this.input[this.options.inputName],
this.caretPosition, this.caretPosition,
this.caretPositionEnd,
true true
); );
if (debug) console.log("Input changed:", this.input); if (debug) console.log("Input changed:", this.input);
if (this.options.debug) {
console.log(
"Caret at: ",
this.getCaretPosition(),
this.getCaretPositionEnd(),
`(${this.keyboardDOMClass})`
);
}
/** /**
* Enforce syncInstanceInputs, if set * Enforce syncInstanceInputs, if set
*/ */
@ -314,15 +341,14 @@ class SimpleKeyboard {
*/ */
if (e) e.target.classList.add(this.activeButtonClass); if (e) e.target.classList.add(this.activeButtonClass);
if (this.holdInteractionTimeout) clearTimeout(this.holdInteractionTimeout);
if (this.holdTimeout) clearTimeout(this.holdTimeout);
/** /**
* @type {boolean} Whether the mouse is being held onKeyPress * @type {boolean} Whether the mouse is being held onKeyPress
*/ */
this.isMouseHold = true; this.isMouseHold = true;
if (this.holdInteractionTimeout) clearTimeout(this.holdInteractionTimeout);
if (this.holdTimeout) clearTimeout(this.holdTimeout);
/** /**
* @type {object} Time to wait until a key hold is detected * @type {object} Time to wait until a key hold is detected
*/ */
@ -355,15 +381,18 @@ class SimpleKeyboard {
* Handles button mouseup * Handles button mouseup
*/ */
handleButtonMouseUp(button) { handleButtonMouseUp(button) {
/** this.dispatch(instance => {
* Remove active class /**
*/ * Remove active class
this.recurseButtons(buttonElement => { */
buttonElement.classList.remove(this.activeButtonClass); instance.recurseButtons(buttonElement => {
}); buttonElement.classList.remove(this.activeButtonClass);
});
this.isMouseHold = false; instance.isMouseHold = false;
if (this.holdInteractionTimeout) clearTimeout(this.holdInteractionTimeout); if (instance.holdInteractionTimeout)
clearTimeout(instance.holdInteractionTimeout);
});
/** /**
* Calling onKeyReleased * Calling onKeyReleased
@ -408,7 +437,7 @@ class SimpleKeyboard {
syncInstanceInputs() { syncInstanceInputs() {
this.dispatch(instance => { this.dispatch(instance => {
instance.replaceInput(this.input); instance.replaceInput(this.input);
instance.caretPosition = this.caretPosition; instance.setCaretPosition(this.caretPosition, this.caretPositionEnd);
}); });
} }
@ -423,7 +452,7 @@ class SimpleKeyboard {
/** /**
* Reset caretPosition * Reset caretPosition
*/ */
this.caretPosition = 0; this.setCaretPosition(0);
/** /**
* Enforce syncInstanceInputs, if set * Enforce syncInstanceInputs, if set
@ -519,7 +548,7 @@ class SimpleKeyboard {
console.log("inputName changed. caretPosition reset."); console.log("inputName changed. caretPosition reset.");
} }
this.caretPosition = null; this.setCaretPosition(null);
} }
} }
@ -713,6 +742,8 @@ class SimpleKeyboard {
* Handles simple-keyboard event listeners * Handles simple-keyboard event listeners
*/ */
setEventListeners() { setEventListeners() {
const { useTouchEvents, useMouseEvents } = this.options;
/** /**
* Only first instance should set the event listeners * Only first instance should set the event listeners
*/ */
@ -724,10 +755,42 @@ class SimpleKeyboard {
/** /**
* Event Listeners * Event Listeners
*/ */
document.addEventListener("keyup", this.handleKeyUp); document.onkeyup = this.handleKeyUp;
document.addEventListener("keydown", this.handleKeyDown); document.onkeydown = this.handleKeyDown;
document.addEventListener("mouseup", this.handleMouseUp);
document.addEventListener("touchend", this.handleTouchEnd); /**
* Pointer events
*/
if (
this.utilities.pointerEventsSupported() &&
!useTouchEvents &&
!useMouseEvents
) {
document.onpointerdown = this.handlePointerDown;
document.onpointerup = this.handlePointerUp;
document.onpointercancel = this.handlePointerUp;
this.keyboardDOM.onpointerdown = this.handleKeyboardContainerMouseDown;
/**
* Touch events
*/
} else if (useTouchEvents) {
document.ontouchstart = this.handlePointerDown;
document.ontouchend = this.handlePointerUp;
document.ontouchcancel = this.handlePointerUp;
this.keyboardDOM.ontouchstart = this.handleKeyboardContainerMouseDown;
/**
* Mouse events
*/
} else if (!useTouchEvents) {
document.onmousedown = this.handlePointerDown;
document.onmouseup = this.handlePointerUp;
this.keyboardDOM.onmousedown = this.handleKeyboardContainerMouseDown;
}
} }
} }
@ -752,16 +815,17 @@ class SimpleKeyboard {
} }
/** /**
* Event Handler: MouseUp * Event Handler: PointerDown
*/ */
handleMouseUp(event) { handlePointerDown(event) {
this.caretEventHandler(event); this.caretEventHandler(event);
} }
/** /**
* Event Handler: TouchEnd * Event Handler: PointerUp
*/ */
handleTouchEnd(event) { handlePointerUp(event) {
this.handleButtonMouseUp();
this.caretEventHandler(event); this.caretEventHandler(event);
} }
@ -769,39 +833,48 @@ class SimpleKeyboard {
* 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) {
if (this.options.disableCaretPositioning) {
this.setCaretPosition(null);
return;
}
let targetTagName; let targetTagName;
if (event.target.tagName) { if (event.target.tagName) {
targetTagName = event.target.tagName.toLowerCase(); targetTagName = event.target.tagName.toLowerCase();
} }
/* istanbul ignore next */
this.dispatch(instance => { this.dispatch(instance => {
if (instance.isMouseHold) { const isKeyboard =
instance.isMouseHold = false; event.target === instance.keyboardDOM ||
} (event.target && instance.keyboardDOM.contains(event.target));
if ( // if (!this.isMouseHold) {
(targetTagName === "textarea" || targetTagName === "input") && // instance.isMouseHold = false;
!instance.options.disableCaretPositioning // }
) {
if (targetTagName === "textarea" || targetTagName === "input") {
/** /**
* Tracks current cursor position * Tracks current cursor position
* As keys are pressed, text will be added/removed at that position within the input. * As keys are pressed, text will be added/removed at that position within the input.
*/ */
instance.caretPosition = event.target.selectionStart; instance.setCaretPosition(
event.target.selectionStart,
event.target.selectionEnd
);
if (instance.options.debug) { if (instance.options.debug) {
console.log( console.log(
"Caret at: ", "Caret at: ",
event.target.selectionStart, instance.getCaretPosition(),
event.target.tagName.toLowerCase(), instance.getCaretPositionEnd(),
event && event.target.tagName.toLowerCase(),
`(${instance.keyboardDOMClass})` `(${instance.keyboardDOMClass})`
); );
} }
} else if (instance.options.disableCaretPositioning) { } else if (!isKeyboard) {
/** instance.setCaretPosition(null);
* If we toggled off disableCaretPositioning, we must ensure caretPosition doesn't persist once reactivated.
*/
instance.caretPosition = null;
} }
}); });
} }
@ -826,18 +899,6 @@ class SimpleKeyboard {
`Destroying simple-keyboard instance: ${this.currentInstanceName}` `Destroying simple-keyboard instance: ${this.currentInstanceName}`
); );
/**
* Remove document listeners
*/
document.removeEventListener("keyup", this.handleKeyUp);
document.removeEventListener("keydown", this.handleKeyDown);
document.removeEventListener("mouseup", this.handleMouseUp);
document.removeEventListener("touchend", this.handleTouchEnd);
document.onpointerup = null;
document.ontouchend = null;
document.ontouchcancel = null;
document.onmouseup = null;
/** /**
* Remove buttons * Remove buttons
*/ */
@ -857,8 +918,6 @@ class SimpleKeyboard {
}; };
this.recurseButtons(deleteButton); this.recurseButtons(deleteButton);
this.recurseButtons = null;
deleteButton = null; deleteButton = null;
/** /**
@ -873,12 +932,53 @@ class SimpleKeyboard {
*/ */
this.clear(); this.clear();
/**
* Remove timouts
*/
/* istanbul ignore next */
if (this.holdInteractionTimeout) clearTimeout(this.holdInteractionTimeout);
/* istanbul ignore next */
if (this.holdTimeout) clearTimeout(this.holdTimeout);
/** /**
* Remove instance * Remove instance
*/ */
window["SimpleKeyboardInstances"][this.currentInstanceName] = null; window["SimpleKeyboardInstances"][this.currentInstanceName] = null;
delete window["SimpleKeyboardInstances"][this.currentInstanceName]; delete window["SimpleKeyboardInstances"][this.currentInstanceName];
/**
* Removing document listeners if there are no more instances
*/
if (!Object.keys(window["SimpleKeyboardInstances"]).length) {
/**
* Remove document listeners
*/
document.onkeydown = null;
document.onkeyup = null;
document.onpointerdown = null;
document.onpointerup = null;
document.onmousedown = null;
document.onmouseup = null;
document.ontouchstart = null;
document.ontouchend = null;
document.ontouchcancel = null;
if (this.options.debug) {
console.log(
"Destroy: No instances remaining. Document listeners removed",
window["SimpleKeyboardInstances"]
);
}
} else {
console.log(
"Destroy: Instances remaining! Document listeners not removed",
window["SimpleKeyboardInstances"]
);
}
/** /**
* Reset initialized flag * Reset initialized flag
*/ */
@ -1392,7 +1492,6 @@ class SimpleKeyboard {
* Handle mouse events * Handle mouse events
*/ */
buttonDOM.onclick = () => { buttonDOM.onclick = () => {
this.isMouseHold = false;
this.handleButtonClicked(button); this.handleButtonClicked(button);
}; };
buttonDOM.onmousedown = e => { buttonDOM.onmousedown = e => {
@ -1463,36 +1562,6 @@ class SimpleKeyboard {
*/ */
this.initialized = true; this.initialized = true;
/**
* Handling parent events
*/
/* istanbul ignore next */
if (
this.utilities.pointerEventsSupported() &&
!useTouchEvents &&
!useMouseEvents
) {
document.onpointerup = () => this.handleButtonMouseUp();
this.keyboardDOM.onpointerdown = e =>
this.handleKeyboardContainerMouseDown(e);
} else if (useTouchEvents) {
/**
* Handling ontouchend, ontouchcancel
*/
document.ontouchend = () => this.handleButtonMouseUp();
document.ontouchcancel = () => this.handleButtonMouseUp();
this.keyboardDOM.ontouchstart = e =>
this.handleKeyboardContainerMouseDown(e);
} else if (!useTouchEvents) {
/**
* Handling mouseup
*/
document.onmouseup = () => this.handleButtonMouseUp();
this.keyboardDOM.onmousedown = e =>
this.handleKeyboardContainerMouseDown(e);
}
/** /**
* Calling onInit * Calling onInit
*/ */

View File

@ -1,7 +1,5 @@
import Keyboard from '../Keyboard'; import Keyboard from '../Keyboard';
import TestUtility from '../../../utils/TestUtility'; import { setDOM, clearDOM, testLayoutStdButtons, iterateButtons, triggerDocumentPointerUp, triggerDocumentPointerDown } from '../../../utils/TestUtility';
const testUtil = new TestUtility();
it('Keyboard will not render without target element', () => { it('Keyboard will not render without target element', () => {
try { try {
@ -14,7 +12,7 @@ it('Keyboard will not render without target element', () => {
it('Keyboard will run without options', () => { it('Keyboard will run without options', () => {
// Prepare target DOM element // Prepare target DOM element
testUtil.setDOM(); setDOM();
// No options // No options
new Keyboard(); new Keyboard();
@ -22,21 +20,21 @@ it('Keyboard will run without options', () => {
it('Keyboard will run with empty options', () => { it('Keyboard will run with empty options', () => {
// Prepare target DOM element // Prepare target DOM element
testUtil.setDOM(); setDOM();
// No options // No options
new Keyboard({}); new Keyboard({});
}); });
it('Keyboard will run with custom DOM target', () => { it('Keyboard will run with custom DOM target', () => {
testUtil.setDOM("myTestDiv"); setDOM("myTestDiv");
new Keyboard(".myTestDiv"); new Keyboard(".myTestDiv");
expect(document.body.querySelector(".myTestDiv")).toBeDefined(); expect(document.body.querySelector(".myTestDiv")).toBeDefined();
}); });
it('Keyboard will run with debug option set', () => { it('Keyboard will run with debug option set', () => {
testUtil.setDOM(); setDOM();
const keyboard = new Keyboard({ const keyboard = new Keyboard({
debug: true debug: true
@ -48,7 +46,7 @@ it('Keyboard will run with debug option set', () => {
it('Keyboard will use touch events', () => { it('Keyboard will use touch events', () => {
let touched = false let touched = false
testUtil.clear() clearDOM();
document.body.innerHTML = ` document.body.innerHTML = `
<div class="keyboard"></div> <div class="keyboard"></div>
@ -72,19 +70,19 @@ it('Keyboard will use touch events', () => {
}) })
it('Keyboard standard buttons will work', () => { it('Keyboard standard buttons will work', () => {
testUtil.setDOM(); setDOM();
const keyboard = new Keyboard({ const keyboard = new Keyboard({
maxLength: { maxLength: {
"default": 10 "default": 10
} }
}); });
testUtil.testLayoutStdButtons(keyboard); testLayoutStdButtons(keyboard);
}); });
it('Keyboard shift buttons will work', () => { it('Keyboard shift buttons will work', () => {
testUtil.setDOM(); setDOM();
const keyboard = new Keyboard(); const keyboard = new Keyboard();
keyboard.setOptions({ keyboard.setOptions({
@ -92,18 +90,18 @@ it('Keyboard shift buttons will work', () => {
maxLength: 42 maxLength: 42
}); });
testUtil.testLayoutStdButtons(keyboard); testLayoutStdButtons(keyboard);
}); });
it('Keyboard setOptions will work without a param', () => { it('Keyboard setOptions will work without a param', () => {
testUtil.setDOM(); setDOM();
const keyboard = new Keyboard(); const keyboard = new Keyboard();
keyboard.setOptions(); keyboard.setOptions();
}); });
it('Keyboard empty buttons wont do anything as expected', () => { it('Keyboard empty buttons wont do anything as expected', () => {
testUtil.setDOM(); setDOM();
const keyboard = new Keyboard({ const keyboard = new Keyboard({
layout: { layout: {
@ -118,7 +116,7 @@ it('Keyboard empty buttons wont do anything as expected', () => {
}); });
it('Keyboard onKeyPress will work', () => { it('Keyboard onKeyPress will work', () => {
testUtil.setDOM(); setDOM();
let pressed = false; let pressed = false;
@ -135,13 +133,13 @@ it('Keyboard onKeyPress will work', () => {
}); });
it('Keyboard standard function buttons will not change input', () => { it('Keyboard standard function buttons will not change input', () => {
testUtil.setDOM(); setDOM();
const keyboard = new Keyboard({ const keyboard = new Keyboard({
useButtonTag: true useButtonTag: true
}); });
testUtil.iterateButtons((button) => { iterateButtons((button) => {
if(button.getAttribute("data-skbtn") === "{shift}"){ if(button.getAttribute("data-skbtn") === "{shift}"){
button.onclick(); button.onclick();
} }
@ -151,7 +149,7 @@ it('Keyboard standard function buttons will not change input', () => {
}); });
it('Keyboard syncInstanceInputs will work', () => { it('Keyboard syncInstanceInputs will work', () => {
testUtil.clear(); clearDOM();
document.body.innerHTML = ` document.body.innerHTML = `
<div class="keyboard1"></div> <div class="keyboard1"></div>
@ -180,7 +178,7 @@ it('Keyboard syncInstanceInputs will work', () => {
keyboard1.getButtonElement("5").onclick(); keyboard1.getButtonElement("5").onclick();
keyboard1.getButtonElement("6").onclick(); keyboard1.getButtonElement("6").onclick();
keyboard1.caretPosition = 1; keyboard1.setCaretPosition(1);
keyboard1.getButtonElement("2").onclick(); keyboard1.getButtonElement("2").onclick();
keyboard1.getButtonElement("3").onclick(); keyboard1.getButtonElement("3").onclick();
@ -191,7 +189,7 @@ it('Keyboard syncInstanceInputs will work', () => {
}); });
it('Keyboard onChange will work', () => { it('Keyboard onChange will work', () => {
testUtil.setDOM(); setDOM();
let output = false; let output = false;
@ -208,7 +206,7 @@ it('Keyboard onChange will work', () => {
}); });
it('Keyboard onChangeAll will work', () => { it('Keyboard onChangeAll will work', () => {
testUtil.setDOM(); setDOM();
let output; let output;
@ -225,7 +223,7 @@ it('Keyboard onChangeAll will work', () => {
}); });
it('Keyboard clearInput will work', () => { it('Keyboard clearInput will work', () => {
testUtil.setDOM(); setDOM();
const keyboard = new Keyboard(); const keyboard = new Keyboard();
@ -242,7 +240,7 @@ it('Keyboard clearInput will work', () => {
}); });
it('Keyboard clearInput will work with syncInstanceInputs', () => { it('Keyboard clearInput will work with syncInstanceInputs', () => {
testUtil.clear(); clearDOM();
document.body.innerHTML = ` document.body.innerHTML = `
<div class="keyboard1"></div> <div class="keyboard1"></div>
@ -269,7 +267,7 @@ it('Keyboard clearInput will work with syncInstanceInputs', () => {
}); });
it('Keyboard setInput will work', () => { it('Keyboard setInput will work', () => {
testUtil.setDOM(); setDOM();
const keyboard = new Keyboard(); const keyboard = new Keyboard();
@ -279,7 +277,7 @@ it('Keyboard setInput will work', () => {
}); });
it('Keyboard setInput will work with syncInstanceInputs', () => { it('Keyboard setInput will work with syncInstanceInputs', () => {
testUtil.clear(); clearDOM();
document.body.innerHTML = ` document.body.innerHTML = `
<div class="keyboard1"></div> <div class="keyboard1"></div>
@ -299,7 +297,7 @@ it('Keyboard setInput will work with syncInstanceInputs', () => {
}); });
it('Keyboard dispatch will work', () => { it('Keyboard dispatch will work', () => {
testUtil.setDOM(); setDOM();
document.body.innerHTML = ` document.body.innerHTML = `
<div class="keyboard1"></div> <div class="keyboard1"></div>
@ -324,7 +322,7 @@ it('Keyboard dispatch will work', () => {
}); });
it('Keyboard dispatch will not work without SimpleKeyboardInstances', () => { it('Keyboard dispatch will not work without SimpleKeyboardInstances', () => {
testUtil.setDOM(); setDOM();
document.body.innerHTML = ` document.body.innerHTML = `
<div class="keyboard1"></div> <div class="keyboard1"></div>
@ -355,7 +353,7 @@ it('Keyboard dispatch will not work without SimpleKeyboardInstances', () => {
}); });
it('Keyboard addButtonTheme will work', () => { it('Keyboard addButtonTheme will work', () => {
testUtil.setDOM(); setDOM();
const keyboard = new Keyboard(); const keyboard = new Keyboard();
keyboard.addButtonTheme("q", "test"); keyboard.addButtonTheme("q", "test");
@ -364,7 +362,7 @@ it('Keyboard addButtonTheme will work', () => {
}); });
it('Keyboard addButtonTheme will not work without params', () => { it('Keyboard addButtonTheme will not work without params', () => {
testUtil.setDOM(); setDOM();
const keyboard = new Keyboard(); const keyboard = new Keyboard();
const returnVal = keyboard.addButtonTheme(); const returnVal = keyboard.addButtonTheme();
@ -373,7 +371,7 @@ it('Keyboard addButtonTheme will not work without params', () => {
}); });
it('Keyboard addButtonTheme will amend a buttonTheme', () => { it('Keyboard addButtonTheme will amend a buttonTheme', () => {
testUtil.setDOM(); setDOM();
const keyboard = new Keyboard({ const keyboard = new Keyboard({
buttonTheme: [ buttonTheme: [
@ -390,7 +388,7 @@ it('Keyboard addButtonTheme will amend a buttonTheme', () => {
}); });
it('Keyboard addButtonTheme will create a buttonTheme', () => { it('Keyboard addButtonTheme will create a buttonTheme', () => {
testUtil.setDOM(); setDOM();
const keyboard = new Keyboard({ const keyboard = new Keyboard({
buttonTheme: [ buttonTheme: [
@ -407,7 +405,7 @@ it('Keyboard addButtonTheme will create a buttonTheme', () => {
}); });
it('Keyboard addButtonTheme will ignore a repeated buttonTheme', () => { it('Keyboard addButtonTheme will ignore a repeated buttonTheme', () => {
testUtil.setDOM(); setDOM();
const keyboard = new Keyboard({ const keyboard = new Keyboard({
buttonTheme: [ buttonTheme: [
@ -424,7 +422,7 @@ it('Keyboard addButtonTheme will ignore a repeated buttonTheme', () => {
}); });
it('Keyboard addButtonTheme will amend a buttonTheme', () => { it('Keyboard addButtonTheme will amend a buttonTheme', () => {
testUtil.setDOM(); setDOM();
const keyboard = new Keyboard({ const keyboard = new Keyboard({
buttonTheme: [ buttonTheme: [
@ -442,7 +440,7 @@ it('Keyboard addButtonTheme will amend a buttonTheme', () => {
it('Keyboard removeButtonTheme without params will remove all button themes', () => { it('Keyboard removeButtonTheme without params will remove all button themes', () => {
testUtil.setDOM(); setDOM();
const keyboard = new Keyboard({ const keyboard = new Keyboard({
buttonTheme: [ buttonTheme: [
@ -460,7 +458,7 @@ it('Keyboard removeButtonTheme without params will remove all button themes', ()
it('Keyboard removeButtonTheme will work', () => { it('Keyboard removeButtonTheme will work', () => {
testUtil.setDOM(); setDOM();
const keyboard = new Keyboard({ const keyboard = new Keyboard({
buttonTheme: [ buttonTheme: [
@ -477,7 +475,7 @@ it('Keyboard removeButtonTheme will work', () => {
}); });
it('Keyboard removeButtonTheme will work wihtout a class', () => { it('Keyboard removeButtonTheme will work wihtout a class', () => {
testUtil.setDOM(); setDOM();
const keyboard = new Keyboard({ const keyboard = new Keyboard({
buttonTheme: [ buttonTheme: [
@ -494,7 +492,7 @@ it('Keyboard removeButtonTheme will work wihtout a class', () => {
}); });
it('Keyboard removeButtonTheme will do nothing without a button param', () => { it('Keyboard removeButtonTheme will do nothing without a button param', () => {
testUtil.setDOM(); setDOM();
const keyboard = new Keyboard({ const keyboard = new Keyboard({
buttonTheme: [ buttonTheme: [
@ -511,7 +509,7 @@ it('Keyboard removeButtonTheme will do nothing without a button param', () => {
}); });
it('Keyboard removeButtonTheme does nothing if req button doesnt have a buttonTheme', () => { it('Keyboard removeButtonTheme does nothing if req button doesnt have a buttonTheme', () => {
testUtil.setDOM(); setDOM();
const keyboard = new Keyboard({ const keyboard = new Keyboard({
buttonTheme: [ buttonTheme: [
@ -528,7 +526,7 @@ it('Keyboard removeButtonTheme does nothing if req button doesnt have a buttonTh
}); });
it('Keyboard removeButtonTheme does nothing if buttonTheme class does not exist', () => { it('Keyboard removeButtonTheme does nothing if buttonTheme class does not exist', () => {
testUtil.setDOM(); setDOM();
const keyboard = new Keyboard({ const keyboard = new Keyboard({
buttonTheme: [ buttonTheme: [
@ -545,7 +543,7 @@ it('Keyboard removeButtonTheme does nothing if buttonTheme class does not exist'
}); });
it('Keyboard removeButtonTheme does nothing if buttonTheme doesnt have the requested buttons', () => { it('Keyboard removeButtonTheme does nothing if buttonTheme doesnt have the requested buttons', () => {
testUtil.setDOM(); setDOM();
const keyboard = new Keyboard({ const keyboard = new Keyboard({
buttonTheme: [ buttonTheme: [
@ -562,7 +560,7 @@ it('Keyboard removeButtonTheme does nothing if buttonTheme doesnt have the reque
}); });
it('Keyboard getButtonElement will not return anything if empty match', () => { it('Keyboard getButtonElement will not return anything if empty match', () => {
testUtil.setDOM(); setDOM();
const keyboard = new Keyboard({ const keyboard = new Keyboard({
layout: { layout: {
@ -577,7 +575,7 @@ it('Keyboard getButtonElement will not return anything if empty match', () => {
}); });
it('Keyboard getButtonElement will return multiple matched buttons', () => { it('Keyboard getButtonElement will return multiple matched buttons', () => {
testUtil.setDOM(); setDOM();
const keyboard = new Keyboard(); const keyboard = new Keyboard();
@ -585,60 +583,54 @@ it('Keyboard getButtonElement will return multiple matched buttons', () => {
}); });
it('Keyboard will receive physical keyboard events', () => { it('Keyboard will receive physical keyboard events', () => {
testUtil.setDOM(); setDOM();
new Keyboard({ new Keyboard({
debug: true, debug: true,
physicalKeyboardHighlight: true physicalKeyboardHighlight: true
}); });
document.dispatchEvent(new KeyboardEvent('keyup', { document.onkeyup({
charCode: 0, charCode: 0,
code: "KeyF", code: "KeyF",
key: "f", key: "f",
which: 70, which: 70,
target: { target: document.createElement('input')
tagName: "input" });
}
}));
}); });
it('Keyboard caretEventHandler will detect input, textarea focus', () => { it('Keyboard caretEventHandler will detect input, textarea focus', () => {
testUtil.setDOM(); setDOM();
const keyboard = new Keyboard(); const keyboard = new Keyboard();
const myInput = document.createElement('input');
keyboard.caretEventHandler({ keyboard.caretEventHandler({
charCode: 0, charCode: 0,
code: "KeyF", code: "KeyF",
key: "f", key: "f",
which: 70, which: 70,
target: { target: myInput
tagName: "input",
selectionStart: 3
}
}); });
expect(keyboard.caretPosition).toBe(3); expect(keyboard.getCaretPosition()).toBe(0);
}); });
it('Keyboard caretEventHandler will not set caretPosition on disableCaretPositioning', () => { it('Keyboard caretEventHandler will not set caretPosition on disableCaretPositioning', () => {
testUtil.setDOM(); setDOM();
const keyboard = new Keyboard(); const keyboard = new Keyboard();
const myInput = document.createElement('input');
keyboard.caretEventHandler({ keyboard.caretEventHandler({
charCode: 0, charCode: 0,
code: "KeyF", code: "KeyF",
key: "f", key: "f",
which: 70, which: 70,
target: { target: myInput
tagName: "input",
selectionStart: 3
}
}); });
expect(keyboard.caretPosition).toBe(3); expect(keyboard.getCaretPosition()).toBe(0);
keyboard.setOptions({ keyboard.setOptions({
disableCaretPositioning: true disableCaretPositioning: true
@ -649,17 +641,14 @@ it('Keyboard caretEventHandler will not set caretPosition on disableCaretPositio
code: "KeyF", code: "KeyF",
key: "f", key: "f",
which: 70, which: 70,
target: { target: myInput
tagName: "input",
selectionStart: 3
}
}); });
expect(keyboard.caretPosition).toBeFalsy(); expect(keyboard.getCaretPosition()).toBe(null);
}); });
it('Keyboard caretEventHandler ignore positioning if input, textarea is blur', () => { it('Keyboard caretEventHandler ignore positioning if input, textarea is blur', () => {
testUtil.setDOM(); setDOM();
const keyboard = new Keyboard(); const keyboard = new Keyboard();
@ -670,38 +659,39 @@ it('Keyboard caretEventHandler ignore positioning if input, textarea is blur', (
code: "KeyF", code: "KeyF",
key: "f", key: "f",
which: 70, which: 70,
target: { target: document.createElement('div')
tagName: "div",
selectionStart: 4
}
}); });
expect(keyboard.caretPosition).toBeFalsy(); expect(keyboard.getCaretPosition()).toBeFalsy();
}); });
it('Keyboard caretEventHandler will work with debug', () => { it('Keyboard caretEventHandler will work with debug', () => {
testUtil.setDOM(); setDOM();
const keyboard = new Keyboard({ const keyboard = new Keyboard({
debug: true debug: true
}); });
keyboard.input.default = "hello";
keyboard.setCaretPosition(2)
expect(keyboard.getCaretPosition()).toBe(2);
const myInput = document.createElement('input');
keyboard.caretEventHandler({ keyboard.caretEventHandler({
charCode: 0, charCode: 0,
code: "KeyF", code: "KeyF",
key: "f", key: "f",
which: 70, which: 70,
target: { target: myInput
tagName: "input",
selectionStart: 3
}
}); });
expect(keyboard.caretPosition).toBe(3); expect(keyboard.getCaretPosition()).toBe(0);
}); });
it('Keyboard onInit will work', () => { it('Keyboard onInit will work', () => {
testUtil.setDOM(); setDOM();
let passed = false; let passed = false;
@ -715,7 +705,7 @@ it('Keyboard onInit will work', () => {
}); });
it('Keyboard onRender will work', () => { it('Keyboard onRender will work', () => {
testUtil.setDOM(); setDOM();
let passed = false; let passed = false;
@ -729,7 +719,7 @@ it('Keyboard onRender will work', () => {
}); });
it('Keyboard buttonTheme that is invalid will be ignored and not throw', () => { it('Keyboard buttonTheme that is invalid will be ignored and not throw', () => {
testUtil.setDOM(); setDOM();
new Keyboard({ new Keyboard({
buttonTheme: [ buttonTheme: [
@ -742,7 +732,7 @@ it('Keyboard buttonTheme that is invalid will be ignored and not throw', () => {
}); });
it('Keyboard buttonTheme buttons that are invalid will be ignored and not throw', () => { it('Keyboard buttonTheme buttons that are invalid will be ignored and not throw', () => {
testUtil.setDOM(); setDOM();
new Keyboard({ new Keyboard({
buttonTheme: [ buttonTheme: [
@ -755,7 +745,7 @@ it('Keyboard buttonTheme buttons that are invalid will be ignored and not throw'
}); });
it('Keyboard buttonTheme will be ignored if buttons param not a string', () => { it('Keyboard buttonTheme will be ignored if buttons param not a string', () => {
testUtil.setDOM(); setDOM();
new Keyboard({ new Keyboard({
buttonTheme: [ buttonTheme: [
@ -770,7 +760,7 @@ it('Keyboard buttonTheme will be ignored if buttons param not a string', () => {
}); });
it('Keyboard buttonTheme will be ignored if already added', () => { it('Keyboard buttonTheme will be ignored if already added', () => {
testUtil.setDOM(); setDOM();
new Keyboard({ new Keyboard({
buttonTheme: [ buttonTheme: [
@ -799,7 +789,7 @@ it('Keyboard buttonTheme will be ignored if already added', () => {
}); });
it('Keyboard can set a module', () => { it('Keyboard can set a module', () => {
testUtil.setDOM(); setDOM();
const keyboard = new Keyboard(); const keyboard = new Keyboard();
@ -814,7 +804,7 @@ it('Keyboard can set a module', () => {
}); });
it('Keyboard registerModule will return current module tree', () => { it('Keyboard registerModule will return current module tree', () => {
testUtil.setDOM(); setDOM();
const keyboard = new Keyboard(); const keyboard = new Keyboard();
@ -834,7 +824,7 @@ it('Keyboard registerModule will return current module tree', () => {
}); });
it('Keyboard can set a module by amending the modules tree', () => { it('Keyboard can set a module by amending the modules tree', () => {
testUtil.setDOM(); setDOM();
const keyboard = new Keyboard(); const keyboard = new Keyboard();
@ -855,7 +845,7 @@ it('Keyboard can set a module by amending the modules tree', () => {
}); });
it('Keyboard will not retrieve an option for an inexistent module', () => { it('Keyboard will not retrieve an option for an inexistent module', () => {
testUtil.setDOM(); setDOM();
const keyboard = new Keyboard(); const keyboard = new Keyboard();
@ -863,7 +853,7 @@ it('Keyboard will not retrieve an option for an inexistent module', () => {
}); });
it('Keyboard will get a list of modules', () => { it('Keyboard will get a list of modules', () => {
testUtil.setDOM(); setDOM();
const keyboard = new Keyboard(); const keyboard = new Keyboard();
@ -878,7 +868,7 @@ it('Keyboard will get a list of modules', () => {
}); });
it('Keyboard loadModules will load a simple module', () => { it('Keyboard loadModules will load a simple module', () => {
testUtil.setDOM(); setDOM();
class myClass { class myClass {
init = (module) => { init = (module) => {
@ -894,48 +884,48 @@ it('Keyboard loadModules will load a simple module', () => {
}); });
it('Keyboard handleButtonMouseUp will set isMouseHold to false', () => { it('Keyboard handleButtonMouseUp will set isMouseHold to false', () => {
testUtil.setDOM(); setDOM();
const keyboard = new Keyboard(); const keyboard = new Keyboard();
keyboard.isMouseHold = true; keyboard.isMouseHold = true;
document.onmouseup(); document.onmouseup({
target: document.body
});
expect(keyboard.isMouseHold).toBeFalsy(); expect(keyboard.isMouseHold).toBeFalsy();
}); });
it('Keyboard handleButtonMouseUp clear holdInteractionTimeout', () => { it('Keyboard handleButtonMouseUp clear holdInteractionTimeout', () => {
testUtil.setDOM(); setDOM();
const keyboard = new Keyboard(); const keyboard = new Keyboard();
keyboard.isMouseHold = true; keyboard.isMouseHold = true;
keyboard.holdInteractionTimeout = setTimeout(() => {}, 10000); keyboard.holdInteractionTimeout = setTimeout(() => {}, 10000);
document.onmouseup(); document.onmouseup({
target: document.body
});
}); });
it('Keyboard handleButtonMouseDown will work', () => { it('Keyboard handleButtonMouseDown will work', () => {
testUtil.setDOM(); setDOM();
const keyboard = new Keyboard(); const keyboard = new Keyboard({ useMouseEvents: true });
keyboard.handleButtonMouseDown("q", { console.log(keyboard.getButtonElement("q"))
target: keyboard.getButtonElement("q"), keyboard.getButtonElement("q").onclick();
preventDefault: () => {},
stopPropagation: () => {} document.onmouseup({
target: document.body
}); });
var clickEvent = document.createEvent('MouseEvents');
clickEvent.initEvent('mousedown', true, true);
keyboard.getButtonElement("q").dispatchEvent(clickEvent);
document.onmouseup();
}); });
it('Keyboard handleButtonMouseDown will work with preventMouseDownDefault', () => { it('Keyboard handleButtonMouseDown will work with preventMouseDownDefault', () => {
testUtil.setDOM(); setDOM();
const keyboard = new Keyboard(); const keyboard = new Keyboard();
@ -947,15 +937,15 @@ it('Keyboard handleButtonMouseDown will work with preventMouseDownDefault', () =
stopPropagation: () => {} stopPropagation: () => {}
}); });
var clickEvent = document.createEvent('MouseEvents'); keyboard.getButtonElement("q").onclick();
clickEvent.initEvent('mousedown', true, true); document.onmouseup({
keyboard.getButtonElement("q").dispatchEvent(clickEvent); target: document.body
document.onmouseup(); });
}); });
it('Keyboard onModulesLoaded will work', () => { it('Keyboard onModulesLoaded will work', () => {
testUtil.setDOM(); setDOM();
class myClass { class myClass {
init = (module) => { init = (module) => {
@ -978,7 +968,7 @@ it('Keyboard onModulesLoaded will work', () => {
}); });
it('Keyboard inputPattern will work globally', () => { it('Keyboard inputPattern will work globally', () => {
testUtil.setDOM(); setDOM();
const keyboard = new Keyboard({ const keyboard = new Keyboard({
inputPattern: /^\d+$/, inputPattern: /^\d+$/,
@ -995,7 +985,7 @@ it('Keyboard inputPattern will work globally', () => {
}); });
it('Keyboard inputPattern will work by input name', () => { it('Keyboard inputPattern will work by input name', () => {
testUtil.setDOM(); setDOM();
const keyboard = new Keyboard({ const keyboard = new Keyboard({
debug: true, debug: true,
@ -1022,7 +1012,7 @@ it('Keyboard inputPattern will work by input name', () => {
}); });
it('Keyboard processAutoTouchEvents will work', () => { it('Keyboard processAutoTouchEvents will work', () => {
testUtil.setDOM(); setDOM();
navigator.maxTouchPoints = true; navigator.maxTouchPoints = true;
@ -1034,7 +1024,7 @@ it('Keyboard processAutoTouchEvents will work', () => {
}); });
it('Keyboard processAutoTouchEvents will work with debugging enabled', () => { it('Keyboard processAutoTouchEvents will work with debugging enabled', () => {
testUtil.setDOM(); setDOM();
navigator.maxTouchPoints = true; navigator.maxTouchPoints = true;
@ -1047,7 +1037,7 @@ it('Keyboard processAutoTouchEvents will work with debugging enabled', () => {
}); });
it('Keyboard beforeFirstRender method will work', () => { it('Keyboard beforeFirstRender method will work', () => {
testUtil.setDOM(); setDOM();
let timesCalled = 0; let timesCalled = 0;
@ -1068,7 +1058,7 @@ it('Keyboard beforeFirstRender method will work', () => {
}); });
it('Keyboard beforeFirstRender will show PointerEvents warning', () => { it('Keyboard beforeFirstRender will show PointerEvents warning', () => {
testUtil.setDOM(); setDOM();
let timesCalled = 0; let timesCalled = 0;
@ -1085,7 +1075,7 @@ it('Keyboard beforeFirstRender will show PointerEvents warning', () => {
}); });
it('Keyboard beforeRender method will work', () => { it('Keyboard beforeRender method will work', () => {
testUtil.setDOM(); setDOM();
let timesCalled = 0; let timesCalled = 0;
@ -1106,7 +1096,7 @@ it('Keyboard beforeRender method will work', () => {
}); });
it('Keyboard parseRowDOMContainers will work', () => { it('Keyboard parseRowDOMContainers will work', () => {
testUtil.setDOM(); setDOM();
const keyboard = new Keyboard({ const keyboard = new Keyboard({
layout: { layout: {
@ -1139,7 +1129,7 @@ it('Keyboard parseRowDOMContainers will work', () => {
}); });
it('Keyboard parseRowDOMContainers will ignore empty rows', () => { it('Keyboard parseRowDOMContainers will ignore empty rows', () => {
testUtil.setDOM(); setDOM();
let failed = false; let failed = false;
@ -1157,7 +1147,7 @@ it('Keyboard parseRowDOMContainers will ignore empty rows', () => {
it('Keyboard parseRowDOMContainers will ignore missing endIndex or endIndex before startIndex', () => { it('Keyboard parseRowDOMContainers will ignore missing endIndex or endIndex before startIndex', () => {
testUtil.setDOM(); setDOM();
new Keyboard({ new Keyboard({
layout: { layout: {
@ -1174,7 +1164,7 @@ it('Keyboard parseRowDOMContainers will ignore missing endIndex or endIndex befo
}); });
it('Keyboard disableRowButtonContainers will bypass parseRowDOMContainers', () => { it('Keyboard disableRowButtonContainers will bypass parseRowDOMContainers', () => {
testUtil.setDOM(); setDOM();
new Keyboard({ new Keyboard({
disableRowButtonContainers: true, disableRowButtonContainers: true,
@ -1202,16 +1192,16 @@ it('Keyboard disableRowButtonContainers will bypass parseRowDOMContainers', () =
}); });
it('Keyboard inputName change will trigget caretPosition reset', () => { it('Keyboard inputName change will trigget caretPosition reset', () => {
testUtil.setDOM(); setDOM();
const keyboard = new Keyboard(); const keyboard = new Keyboard();
keyboard.caretPosition = 0; keyboard.setCaretPosition(0);
keyboard.getButtonElement("q").onpointerdown(); keyboard.getButtonElement("q").onpointerdown();
keyboard.getButtonElement("1").onpointerdown(); keyboard.getButtonElement("1").onpointerdown();
expect(keyboard.caretPosition).toBe(2); expect(keyboard.getCaretPosition()).toBe(2);
keyboard.setOptions({ keyboard.setOptions({
inputName: "myInput" inputName: "myInput"
@ -1221,19 +1211,34 @@ it('Keyboard inputName change will trigget caretPosition reset', () => {
keyboard.getButtonElement("1").onpointerdown(); keyboard.getButtonElement("1").onpointerdown();
keyboard.getButtonElement("b").onpointerdown(); keyboard.getButtonElement("b").onpointerdown();
expect(keyboard.caretPosition).toBe(null); expect(keyboard.getCaretPosition()).toBe(null);
}); });
it('Keyboard destroy will work', () => { it('Keyboard destroy will work', () => {
testUtil.setDOM(); setDOM();
const keyboard = new Keyboard(); const keyboard = new Keyboard();
keyboard.destroy(); keyboard.destroy();
expect(keyboard.keyboardDOM.innerHTML).toBe(""); expect(keyboard.keyboardDOM.innerHTML).toBe("");
expect(document.onkeydown).toBe(null);
expect(document.onkeyup).toBe(null);
expect(document.onpointerdown).toBe(null);
expect(document.onpointerup).toBe(null);
expect(document.onmousedown).toBe(null);
expect(document.onmouseup).toBe(null);
expect(document.ontouchstart).toBe(null);
expect(document.ontouchend).toBe(null);
expect(document.ontouchcancel).toBe(null);
expect(keyboard.initialized).toBe(false);
}); });
it('Keyboard destroy will work with debug option', () => { it('Keyboard destroy will work with debug option', () => {
testUtil.setDOM(); setDOM();
const keyboard = new Keyboard({ debug: true }); const keyboard = new Keyboard({ debug: true });
keyboard.destroy(); keyboard.destroy();
@ -1241,7 +1246,7 @@ it('Keyboard destroy will work with debug option', () => {
}); });
it('Keyboard disableButtonHold will work', () => { it('Keyboard disableButtonHold will work', () => {
testUtil.setDOM(); setDOM();
const keyboard = new Keyboard({ const keyboard = new Keyboard({
disableButtonHold: true disableButtonHold: true
@ -1251,39 +1256,33 @@ it('Keyboard disableButtonHold will work', () => {
}); });
it('Keyboard caretEventHandler will be triggered on mouseup and ontouchend', () => { it('Keyboard caretEventHandler will be triggered on mouseup and ontouchend', () => {
testUtil.setDOM(); setDOM();
const keyboard = new Keyboard({ const keyboard = new Keyboard({
disableCaretPositioning: true disableCaretPositioning: true
}); });
keyboard.caretPosition = 6; keyboard.setCaretPosition(6);
document.dispatchEvent(new MouseEvent('mouseup', { expect(keyboard.getCaretPosition()).toBe(6);
target: {
tagName: "input"
}
}));
expect(keyboard.caretPosition).toBe(null); const event = {
target: document.body
};
triggerDocumentPointerUp(event);
expect(keyboard.getCaretPosition()).toBe(null);
keyboard.setOptions({ keyboard.setOptions({
disableCaretPositioning: false disableCaretPositioning: false
}) })
keyboard.caretPosition = 10; keyboard.setCaretPosition(10);
document.dispatchEvent(new TouchEvent('touchend', {
target: {
tagName: "input"
}
}));
expect(keyboard.caretPosition).toBe(10);
}); });
it('Keyboard onKeyReleased will work', () => { it('Keyboard onKeyReleased will work', () => {
testUtil.setDOM(); setDOM();
let pressed = false; let pressed = false;
let firedTimes = 0; let firedTimes = 0;
@ -1307,7 +1306,7 @@ it('Keyboard onKeyReleased will work', () => {
}); });
it('Keyboard buttonAttribute will work', () => { it('Keyboard buttonAttribute will work', () => {
testUtil.setDOM(); setDOM();
new Keyboard({ new Keyboard({
buttonAttributes: [ buttonAttributes: [
@ -1321,7 +1320,7 @@ it('Keyboard buttonAttribute will work', () => {
}); });
it('Keyboard buttonAttribute will warn about invalid entries', () => { it('Keyboard buttonAttribute will warn about invalid entries', () => {
testUtil.setDOM(); setDOM();
new Keyboard({ new Keyboard({
buttonAttributes: [ buttonAttributes: [
@ -1334,7 +1333,7 @@ it('Keyboard buttonAttribute will warn about invalid entries', () => {
}); });
it('Keyboard recurseButtons will not work without a valid param', () => { it('Keyboard recurseButtons will not work without a valid param', () => {
testUtil.setDOM(); setDOM();
const keyboard = new Keyboard(); const keyboard = new Keyboard();
expect(keyboard.recurseButtons()).toBe(false); expect(keyboard.recurseButtons()).toBe(false);
}); });
@ -1361,3 +1360,37 @@ it('Keyboard will work with a DOM element param with class', () => {
expect(true).toBe(false); expect(true).toBe(false);
} }
}); });
it('Keyboard handleKeyboardContainerMouseDown will work', () => {
setDOM();
const keyboard = new Keyboard();
keyboard.keyboardDOM.onpointerdown();
});
it('Keyboard handleKeyboardContainerMouseDown will respect preventMouseDownDefault', () => {
setDOM();
let works = false;
const keyboard = new Keyboard({ preventMouseDownDefault: true });
keyboard.keyboardDOM.onpointerdown({ preventDefault: () => {
works = true
}});
expect(works).toBe(true);
});
it('Keyboard handlePointerDown will work', () => {
setDOM();
const keyboard = new Keyboard();
keyboard.setCaretPosition(3);
expect(keyboard.getCaretPosition()).toBe(3);
triggerDocumentPointerDown({
target: document.body
})
expect(keyboard.getCaretPosition()).toBe(null);
});

View File

@ -31,9 +31,9 @@ class PhysicalKeyboard {
if (buttonDOM) { if (buttonDOM) {
buttonDOM.style.backgroundColor = buttonDOM.style.backgroundColor =
options.physicalKeyboardHighlightBgColor || "#9ab4d0"; options.physicalKeyboardHighlightBgColor || "#dadce4";
buttonDOM.style.color = buttonDOM.style.color =
options.physicalKeyboardHighlightTextColor || "white"; options.physicalKeyboardHighlightTextColor || "black";
} }
}); });
} }

View File

@ -5,9 +5,17 @@ class Utilities {
/** /**
* Creates an instance of the Utility service * Creates an instance of the Utility service
*/ */
constructor({ getOptions, getCaretPosition, dispatch }) { constructor({
getOptions,
setCaretPosition,
getCaretPosition,
getCaretPositionEnd,
dispatch
}) {
this.getOptions = getOptions; this.getOptions = getOptions;
this.getCaretPosition = getCaretPosition; this.getCaretPosition = getCaretPosition;
this.getCaretPositionEnd = getCaretPositionEnd;
this.setCaretPosition = setCaretPosition;
this.dispatch = dispatch; this.dispatch = dispatch;
/** /**
@ -125,19 +133,28 @@ class Utilities {
* @param {string} button The button's layout name * @param {string} button The button's layout name
* @param {string} input The input string * @param {string} input The input string
* @param {number} caretPos The cursor's current position * @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 * @param {boolean} moveCaret Whether to update simple-keyboard's cursor
*/ */
getUpdatedInput(button, input, caretPos, moveCaret) { getUpdatedInput(
button,
input,
caretPos,
caretPosEnd = caretPos,
moveCaret = false
) {
const options = this.getOptions(); const options = this.getOptions();
const commonParams = [caretPos, caretPosEnd, moveCaret];
let output = input; let output = input;
if ( if (
(button === "{bksp}" || button === "{backspace}") && (button === "{bksp}" || button === "{backspace}") &&
output.length > 0 output.length > 0
) { ) {
output = this.removeAt(output, caretPos, moveCaret); output = this.removeAt(output, ...commonParams);
} else if (button === "{space}") } else if (button === "{space}")
output = this.addStringAt(output, " ", caretPos, moveCaret); output = this.addStringAt(output, " ", ...commonParams);
else if ( else if (
button === "{tab}" && button === "{tab}" &&
!( !(
@ -145,12 +162,12 @@ class Utilities {
options.tabCharOnTab === false options.tabCharOnTab === false
) )
) { ) {
output = this.addStringAt(output, "\t", caretPos, moveCaret); output = this.addStringAt(output, "\t", ...commonParams);
} else if ( } else if (
(button === "{enter}" || button === "{numpadenter}") && (button === "{enter}" || button === "{numpadenter}") &&
options.newLineOnEnter options.newLineOnEnter
) )
output = this.addStringAt(output, "\n", caretPos, moveCaret); output = this.addStringAt(output, "\n", ...commonParams);
else if ( else if (
button.includes("numpad") && button.includes("numpad") &&
Number.isInteger(Number(button[button.length - 2])) Number.isInteger(Number(button[button.length - 2]))
@ -158,23 +175,22 @@ class Utilities {
output = this.addStringAt( output = this.addStringAt(
output, output,
button[button.length - 2], button[button.length - 2],
caretPos, ...commonParams
moveCaret
); );
} else if (button === "{numpaddivide}") } else if (button === "{numpaddivide}")
output = this.addStringAt(output, "/", caretPos, moveCaret); output = this.addStringAt(output, "/", ...commonParams);
else if (button === "{numpadmultiply}") else if (button === "{numpadmultiply}")
output = this.addStringAt(output, "*", caretPos, moveCaret); output = this.addStringAt(output, "*", ...commonParams);
else if (button === "{numpadsubtract}") else if (button === "{numpadsubtract}")
output = this.addStringAt(output, "-", caretPos, moveCaret); output = this.addStringAt(output, "-", ...commonParams);
else if (button === "{numpadadd}") else if (button === "{numpadadd}")
output = this.addStringAt(output, "+", caretPos, moveCaret); output = this.addStringAt(output, "+", ...commonParams);
else if (button === "{numpaddecimal}") else if (button === "{numpaddecimal}")
output = this.addStringAt(output, ".", caretPos, moveCaret); output = this.addStringAt(output, ".", ...commonParams);
else if (button === "{" || button === "}") else if (button === "{" || button === "}")
output = this.addStringAt(output, button, caretPos, moveCaret); output = this.addStringAt(output, button, ...commonParams);
else if (!button.includes("{") && !button.includes("}")) else if (!button.includes("{") && !button.includes("}"))
output = this.addStringAt(output, button, caretPos, moveCaret); output = this.addStringAt(output, button, ...commonParams);
return output; return output;
} }
@ -187,10 +203,15 @@ class Utilities {
*/ */
updateCaretPos(length, minus) { updateCaretPos(length, minus) {
const newCaretPos = this.updateCaretPosAction(length, minus); const newCaretPos = this.updateCaretPosAction(length, minus);
const { syncInstanceInputs } = this.getOptions();
this.dispatch(instance => { if (syncInstanceInputs) {
instance.caretPosition = newCaretPos; this.dispatch(instance => {
}); instance.setCaretPosition(newCaretPos);
});
} else {
this.setCaretPosition(newCaretPos);
}
} }
/** /**
@ -220,17 +241,23 @@ class Utilities {
* Adds a string to the input at a given position * Adds a string to the input at a given position
* *
* @param {string} source The source input * @param {string} source The source input
* @param {string} string The string to add * @param {string} str The string to add
* @param {number} position The (cursor) position where the string should be added * @param {number} position The (cursor) position where the string should be added
* @param {boolean} moveCaret Whether to update simple-keyboard's cursor * @param {boolean} moveCaret Whether to update simple-keyboard's cursor
*/ */
addStringAt(source, string, position, moveCaret) { addStringAt(
source,
str,
position = source.length,
positionEnd = source.length,
moveCaret = false
) {
let output; let output;
if (!position && position !== 0) { if (!position && position !== 0) {
output = source + string; output = source + str;
} else { } else {
output = [source.slice(0, position), string, source.slice(position)].join( output = [source.slice(0, position), str, source.slice(positionEnd)].join(
"" ""
); );
@ -238,7 +265,7 @@ class Utilities {
* Avoid caret position change when maxLength is set * Avoid caret position change when maxLength is set
*/ */
if (!this.isMaxLengthReached()) { if (!this.isMaxLengthReached()) {
if (moveCaret) this.updateCaretPos(string.length); if (moveCaret) this.updateCaretPos(str.length);
} }
} }
@ -252,43 +279,62 @@ class Utilities {
* @param {number} position The (cursor) position from where the characters should be removed * @param {number} position The (cursor) position from where the characters should be removed
* @param {boolean} moveCaret Whether to update simple-keyboard's cursor * @param {boolean} moveCaret Whether to update simple-keyboard's cursor
*/ */
removeAt(source, position, moveCaret) { removeAt(
const caretPosition = this.getCaretPosition(); source,
position = source.length,
positionEnd = source.length,
moveCaret = false
) {
const { syncInstanceInputs } = this.getOptions();
if (caretPosition === 0) { if (position === 0 && positionEnd === 0) {
return source; return source;
} }
let output; let output;
let prevTwoChars;
let emojiMatched;
const emojiMatchedReg = /([\uD800-\uDBFF][\uDC00-\uDFFF])/g;
/** if (position === positionEnd) {
* Emojis are made out of two characters, so we must take a custom approach to trim them. let prevTwoChars;
* For more info: https://mathiasbynens.be/notes/javascript-unicode let emojiMatched;
*/ const emojiMatchedReg = /([\uD800-\uDBFF][\uDC00-\uDFFF])/g;
if (position && position >= 0) {
prevTwoChars = source.substring(position - 2, position);
emojiMatched = prevTwoChars.match(emojiMatchedReg);
if (emojiMatched) { /**
output = source.substr(0, position - 2) + source.substr(position); * Emojis are made out of two characters, so we must take a custom approach to trim them.
if (moveCaret) this.updateCaretPos(2, true); * For more info: https://mathiasbynens.be/notes/javascript-unicode
*/
if (position && position >= 0) {
prevTwoChars = source.substring(position - 2, position);
emojiMatched = prevTwoChars.match(emojiMatchedReg);
if (emojiMatched) {
output = source.substr(0, position - 2) + source.substr(position);
if (moveCaret) this.updateCaretPos(2, true);
} else {
output = source.substr(0, position - 1) + source.substr(position);
if (moveCaret) this.updateCaretPos(1, true);
}
} else { } else {
output = source.substr(0, position - 1) + source.substr(position); prevTwoChars = source.slice(-2);
if (moveCaret) this.updateCaretPos(1, true); emojiMatched = prevTwoChars.match(emojiMatchedReg);
if (emojiMatched) {
output = source.slice(0, -2);
if (moveCaret) this.updateCaretPos(2, true);
} else {
output = source.slice(0, -1);
if (moveCaret) this.updateCaretPos(1, true);
}
} }
} else { } else {
prevTwoChars = source.slice(-2); output = source.slice(0, position) + source.slice(positionEnd);
emojiMatched = prevTwoChars.match(emojiMatchedReg); if (moveCaret) {
if (syncInstanceInputs) {
if (emojiMatched) { this.dispatch(instance => {
output = source.slice(0, -2); instance.setCaretPosition(position);
if (moveCaret) this.updateCaretPos(2, true); });
} else { } else {
output = source.slice(0, -1); this.setCaretPosition(position);
if (moveCaret) this.updateCaretPos(1, true); }
} }
} }
@ -389,17 +435,17 @@ class Utilities {
/** /**
* Transforms an arbitrary string to camelCase * Transforms an arbitrary string to camelCase
* *
* @param {string} string The string to transform. * @param {string} str The string to transform.
*/ */
camelCase(string) { camelCase(str) {
if (!string) return false; if (!str) return false;
return string return str
.toLowerCase() .toLowerCase()
.trim() .trim()
.split(/[.\-_\s]/g) .split(/[.\-_\s]/g)
.reduce((string, word) => .reduce((str, word) =>
word.length ? string + word[0].toUpperCase() + word.slice(1) : string word.length ? str + word[0].toUpperCase() + word.slice(1) : str
); );
} }
} }

View File

@ -1,10 +1,8 @@
import Keyboard from '../../components/Keyboard'; import Keyboard from '../../components/Keyboard';
import TestUtility from '../../../utils/TestUtility'; import { setDOM } from '../../../utils/TestUtility';
const testUtil = new TestUtility();
it('PhysicalKeyboard keydown will be handled with physicalKeyboardHighlight', () => { it('PhysicalKeyboard keydown will be handled with physicalKeyboardHighlight', () => {
testUtil.setDOM(); setDOM();
new Keyboard({ new Keyboard({
physicalKeyboardHighlight: true physicalKeyboardHighlight: true
@ -20,7 +18,7 @@ it('PhysicalKeyboard keydown will be handled with physicalKeyboardHighlight', ()
}); });
it('PhysicalKeyboard keydown will be handled without physicalKeyboardHighlight', () => { it('PhysicalKeyboard keydown will be handled without physicalKeyboardHighlight', () => {
testUtil.setDOM(); setDOM();
new Keyboard({ new Keyboard({
physicalKeyboardHighlight: false physicalKeyboardHighlight: false
@ -36,7 +34,7 @@ it('PhysicalKeyboard keydown will be handled without physicalKeyboardHighlight',
}); });
it('PhysicalKeyboard keydown will not style non-existent buttons', () => { it('PhysicalKeyboard keydown will not style non-existent buttons', () => {
testUtil.setDOM(); setDOM();
new Keyboard({ new Keyboard({
physicalKeyboardHighlight: true physicalKeyboardHighlight: true
@ -52,7 +50,7 @@ it('PhysicalKeyboard keydown will not style non-existent buttons', () => {
}); });
it('PhysicalKeyboard keyup will be handled with physicalKeyboardHighlight', () => { it('PhysicalKeyboard keyup will be handled with physicalKeyboardHighlight', () => {
testUtil.setDOM(); setDOM();
new Keyboard({ new Keyboard({
physicalKeyboardHighlight: true physicalKeyboardHighlight: true
@ -68,7 +66,7 @@ it('PhysicalKeyboard keyup will be handled with physicalKeyboardHighlight', () =
}); });
it('PhysicalKeyboard keyup will be handle special buttons', () => { it('PhysicalKeyboard keyup will be handle special buttons', () => {
testUtil.setDOM(); setDOM();
new Keyboard({ new Keyboard({
physicalKeyboardHighlight: true physicalKeyboardHighlight: true
@ -84,7 +82,7 @@ it('PhysicalKeyboard keyup will be handle special buttons', () => {
}); });
it('PhysicalKeyboard keyup will not style non-existent buttons', () => { it('PhysicalKeyboard keyup will not style non-existent buttons', () => {
testUtil.setDOM(); setDOM();
new Keyboard({ new Keyboard({
physicalKeyboardHighlight: true, physicalKeyboardHighlight: true,
@ -101,7 +99,7 @@ it('PhysicalKeyboard keyup will not style non-existent buttons', () => {
}); });
it('PhysicalKeyboard will work with F1-F12 keys', () => { it('PhysicalKeyboard will work with F1-F12 keys', () => {
testUtil.setDOM(); setDOM();
new Keyboard({ new Keyboard({
physicalKeyboardHighlight: true, physicalKeyboardHighlight: true,

View File

@ -1,10 +1,8 @@
import Keyboard from '../../components/Keyboard'; import Keyboard from '../../components/Keyboard';
import TestUtility from '../../../utils/TestUtility'; import { setDOM, clearDOM, testLayoutFctButtons } from '../../../utils/TestUtility';
const testUtil = new TestUtility();
it('Keyboard mergeDisplay will work', () => { it('Keyboard mergeDisplay will work', () => {
testUtil.setDOM(); setDOM();
const keyboard = new Keyboard({ const keyboard = new Keyboard({
mergeDisplay: true, mergeDisplay: true,
@ -17,17 +15,17 @@ it('Keyboard mergeDisplay will work', () => {
}); });
it('Keyboard function buttons will work', () => { it('Keyboard function buttons will work', () => {
testUtil.setDOM(); setDOM();
new Keyboard(); new Keyboard();
testUtil.testLayoutFctButtons((fctBtnCount, fctBtnHasOnclickCount) => { testLayoutFctButtons((fctBtnCount, fctBtnHasOnclickCount) => {
expect(fctBtnCount).toBe(fctBtnHasOnclickCount); expect(fctBtnCount).toBe(fctBtnHasOnclickCount);
}); });
}); });
it('Keyboard {bksp} button will work', () => { it('Keyboard {bksp} button will work', () => {
testUtil.setDOM(); setDOM();
const keyboard = new Keyboard(); const keyboard = new Keyboard();
@ -37,7 +35,7 @@ it('Keyboard {bksp} button will work', () => {
}); });
it('Keyboard {space} button will work', () => { it('Keyboard {space} button will work', () => {
testUtil.setDOM(); setDOM();
const keyboard = new Keyboard(); const keyboard = new Keyboard();
@ -47,7 +45,7 @@ it('Keyboard {space} button will work', () => {
}); });
it('Keyboard {tab} button will work', () => { it('Keyboard {tab} button will work', () => {
testUtil.setDOM(); setDOM();
const keyboard = new Keyboard(); const keyboard = new Keyboard();
@ -57,7 +55,7 @@ it('Keyboard {tab} button will work', () => {
}); });
it('Keyboard {tab} button will work with tabCharOnTab:false', () => { it('Keyboard {tab} button will work with tabCharOnTab:false', () => {
testUtil.setDOM(); setDOM();
const keyboard = new Keyboard({ const keyboard = new Keyboard({
tabCharOnTab: false tabCharOnTab: false
@ -69,7 +67,7 @@ it('Keyboard {tab} button will work with tabCharOnTab:false', () => {
}); });
it('Keyboard {enter} button will work', () => { it('Keyboard {enter} button will work', () => {
testUtil.setDOM(); setDOM();
const keyboard = new Keyboard(); const keyboard = new Keyboard();
@ -79,7 +77,7 @@ it('Keyboard {enter} button will work', () => {
}); });
it('Keyboard {enter} button will work with newLineOnEnter:true', () => { it('Keyboard {enter} button will work with newLineOnEnter:true', () => {
testUtil.setDOM(); setDOM();
const keyboard = new Keyboard({ const keyboard = new Keyboard({
newLineOnEnter: true newLineOnEnter: true
@ -91,7 +89,7 @@ it('Keyboard {enter} button will work with newLineOnEnter:true', () => {
}); });
it('Keyboard {numpadX} buttons will work', () => { it('Keyboard {numpadX} buttons will work', () => {
testUtil.setDOM(); setDOM();
const keyboard = new Keyboard(); const keyboard = new Keyboard();
@ -102,7 +100,7 @@ it('Keyboard {numpadX} buttons will work', () => {
}); });
it('Keyboard {numpaddivide} button will work', () => { it('Keyboard {numpaddivide} button will work', () => {
testUtil.setDOM(); setDOM();
const keyboard = new Keyboard(); const keyboard = new Keyboard();
@ -112,7 +110,7 @@ it('Keyboard {numpaddivide} button will work', () => {
}); });
it('Keyboard {numpadmultiply} button will work', () => { it('Keyboard {numpadmultiply} button will work', () => {
testUtil.setDOM(); setDOM();
const keyboard = new Keyboard(); const keyboard = new Keyboard();
@ -122,7 +120,7 @@ it('Keyboard {numpadmultiply} button will work', () => {
}); });
it('Keyboard {numpadsubtract} button will work', () => { it('Keyboard {numpadsubtract} button will work', () => {
testUtil.setDOM(); setDOM();
const keyboard = new Keyboard(); const keyboard = new Keyboard();
@ -132,7 +130,7 @@ it('Keyboard {numpadsubtract} button will work', () => {
}); });
it('Keyboard {numpadadd} button will work', () => { it('Keyboard {numpadadd} button will work', () => {
testUtil.setDOM(); setDOM();
const keyboard = new Keyboard(); const keyboard = new Keyboard();
@ -142,7 +140,7 @@ it('Keyboard {numpadadd} button will work', () => {
}); });
it('Keyboard {numpadadd} button will work', () => { it('Keyboard {numpadadd} button will work', () => {
testUtil.setDOM(); setDOM();
const keyboard = new Keyboard(); const keyboard = new Keyboard();
@ -152,7 +150,7 @@ it('Keyboard {numpadadd} button will work', () => {
}); });
it('Keyboard {numpaddecimal} button will work', () => { it('Keyboard {numpaddecimal} button will work', () => {
testUtil.setDOM(); setDOM();
const keyboard = new Keyboard(); const keyboard = new Keyboard();
@ -162,7 +160,7 @@ it('Keyboard {numpaddecimal} button will work', () => {
}); });
it('Keyboard custom function buttons will work', () => { it('Keyboard custom function buttons will work', () => {
testUtil.setDOM(); setDOM();
const keyboard = new Keyboard({ const keyboard = new Keyboard({
layout: { layout: {
@ -179,7 +177,7 @@ it('Keyboard custom function buttons will work', () => {
}); });
it('Keyboard "{" button will work', () => { it('Keyboard "{" button will work', () => {
testUtil.setDOM(); setDOM();
const keyboard = new Keyboard(); const keyboard = new Keyboard();
@ -189,7 +187,7 @@ it('Keyboard "{" button will work', () => {
}); });
it('Keyboard "}" button will work', () => { it('Keyboard "}" button will work', () => {
testUtil.setDOM(); setDOM();
const keyboard = new Keyboard(); const keyboard = new Keyboard();
@ -199,7 +197,7 @@ it('Keyboard "}" button will work', () => {
}); });
it('Keyboard standard button will affect input', () => { it('Keyboard standard button will affect input', () => {
testUtil.setDOM(); setDOM();
const keyboard = new Keyboard(); const keyboard = new Keyboard();
@ -211,42 +209,42 @@ it('Keyboard standard button will affect input', () => {
}); });
it('Keyboard updateCaretPos will work with minus', () => { it('Keyboard updateCaretPos will work with minus', () => {
testUtil.setDOM(); setDOM();
const keyboard = new Keyboard({ const keyboard = new Keyboard({
syncInstanceInputs: true syncInstanceInputs: true
}); });
keyboard.caretPosition = 5; keyboard.setCaretPosition(5);
keyboard.utilities.updateCaretPos(2, true); keyboard.utilities.updateCaretPos(2, true);
expect(keyboard.caretPosition).toBe(3); expect(keyboard.caretPosition).toBe(3);
}); });
it('Keyboard updateCaretPos will work with minus', () => { it('Keyboard updateCaretPos will work with minus', () => {
testUtil.setDOM(); setDOM();
const keyboard = new Keyboard(); const keyboard = new Keyboard();
keyboard.caretPosition = 5; keyboard.setCaretPosition(5);
keyboard.utilities.updateCaretPos(2, true); keyboard.utilities.updateCaretPos(2, true);
expect(keyboard.caretPosition).toBe(3); expect(keyboard.caretPosition).toBe(3);
}); });
it('Keyboard updateCaretPos will work with plus', () => { it('Keyboard updateCaretPos will work with plus', () => {
testUtil.setDOM(); setDOM();
const keyboard = new Keyboard(); const keyboard = new Keyboard();
keyboard.caretPosition = 5; keyboard.setCaretPosition(5);
keyboard.utilities.updateCaretPos(2); keyboard.utilities.updateCaretPos(2);
expect(keyboard.caretPosition).toBe(7); expect(keyboard.caretPosition).toBe(7);
}); });
it('Keyboard addStringAt will work with debug', () => { it('Keyboard addStringAt will work with debug', () => {
testUtil.setDOM(); setDOM();
const keyboard = new Keyboard({ const keyboard = new Keyboard({
debug: true debug: true
@ -258,14 +256,14 @@ it('Keyboard addStringAt will work with debug', () => {
}); });
it('Keyboard addStringAt will work with position', () => { it('Keyboard addStringAt will work with position', () => {
testUtil.setDOM(); setDOM();
const keyboard = new Keyboard({ const keyboard = new Keyboard({
debug: true debug: true
}); });
keyboard.setInput("test"); keyboard.setInput("test");
keyboard.caretPosition = 4; keyboard.setCaretPosition(5);
keyboard.getButtonElement("q").onclick(); keyboard.getButtonElement("q").onclick();
@ -273,7 +271,7 @@ it('Keyboard addStringAt will work with position', () => {
}); });
it('Keyboard addStringAt will respect maxLength', () => { it('Keyboard addStringAt will respect maxLength', () => {
testUtil.setDOM(); setDOM();
const keyboard = new Keyboard({ const keyboard = new Keyboard({
debug: true, debug: true,
@ -281,16 +279,17 @@ it('Keyboard addStringAt will respect maxLength', () => {
}); });
keyboard.setInput("test"); keyboard.setInput("test");
keyboard.caretPosition = 4;
keyboard.setCaretPosition(4);
keyboard.utilities.handleMaxLength(keyboard.input, "testq") keyboard.utilities.handleMaxLength(keyboard.input, "testq")
keyboard.utilities.addStringAt("test", "q", 4); keyboard.utilities.addStringAt("test", "q", 4, 4);
expect(keyboard.caretPosition).toBe(4); expect(keyboard.caretPosition).toBe(4);
}); });
it('Keyboard handleMaxLength will exit out on same updatedInput', () => { it('Keyboard handleMaxLength will exit out on same updatedInput', () => {
testUtil.setDOM(); setDOM();
const keyboard = new Keyboard({ const keyboard = new Keyboard({
debug: true debug: true
@ -304,7 +303,7 @@ it('Keyboard handleMaxLength will exit out on same updatedInput', () => {
}); });
it('Keyboard handleMaxLength will work with object maxLength', () => { it('Keyboard handleMaxLength will work with object maxLength', () => {
testUtil.setDOM(); setDOM();
const keyboard = new Keyboard({ const keyboard = new Keyboard({
maxLength: { maxLength: {
@ -320,7 +319,7 @@ it('Keyboard handleMaxLength will work with object maxLength', () => {
}); });
it('Keyboard handleMaxLength will work with object maxLength and debug', () => { it('Keyboard handleMaxLength will work with object maxLength and debug', () => {
testUtil.setDOM(); setDOM();
const keyboard = new Keyboard({ const keyboard = new Keyboard({
maxLength: { maxLength: {
@ -337,7 +336,7 @@ it('Keyboard handleMaxLength will work with object maxLength and debug', () => {
}); });
it('Keyboard handleMaxLength will return false if obj maxLength not reached', () => { it('Keyboard handleMaxLength will return false if obj maxLength not reached', () => {
testUtil.setDOM(); setDOM();
const keyboard = new Keyboard({ const keyboard = new Keyboard({
maxLength: { maxLength: {
@ -354,7 +353,7 @@ it('Keyboard handleMaxLength will return false if obj maxLength not reached', ()
it('Keyboard handleMaxLength will work without debug', () => { it('Keyboard handleMaxLength will work without debug', () => {
testUtil.setDOM(); setDOM();
const keyboard = new Keyboard({ const keyboard = new Keyboard({
maxLength: 4 maxLength: 4
@ -369,7 +368,7 @@ it('Keyboard handleMaxLength will work without debug', () => {
it('Keyboard handleMaxLength will work with numeric maxLength', () => { it('Keyboard handleMaxLength will work with numeric maxLength', () => {
testUtil.setDOM(); setDOM();
const keyboard = new Keyboard({ const keyboard = new Keyboard({
maxLength: 3 maxLength: 3
@ -383,7 +382,7 @@ it('Keyboard handleMaxLength will work with numeric maxLength', () => {
}); });
it('Keyboard handleMaxLength wont work with non numeric or object maxLength', () => { it('Keyboard handleMaxLength wont work with non numeric or object maxLength', () => {
testUtil.setDOM(); setDOM();
const keyboard = new Keyboard({ const keyboard = new Keyboard({
maxLength: "wrong" maxLength: "wrong"
@ -397,7 +396,7 @@ it('Keyboard handleMaxLength wont work with non numeric or object maxLength', ()
}); });
it('Keyboard handleMaxLength wont work with non numeric or object maxLength (with debug)', () => { it('Keyboard handleMaxLength wont work with non numeric or object maxLength (with debug)', () => {
testUtil.setDOM(); setDOM();
const keyboard = new Keyboard({ const keyboard = new Keyboard({
maxLength: "wrong", maxLength: "wrong",
@ -412,7 +411,7 @@ it('Keyboard handleMaxLength wont work with non numeric or object maxLength (wit
}); });
it('Keyboard isMaxLengthReached will work', () => { it('Keyboard isMaxLengthReached will work', () => {
testUtil.setDOM(); setDOM();
const keyboard = new Keyboard({ const keyboard = new Keyboard({
maxLength: 5 maxLength: 5
@ -424,70 +423,165 @@ it('Keyboard isMaxLengthReached will work', () => {
}); });
it('Keyboard removeAt will exit out on caretPosition:0', () => { it('Keyboard removeAt will exit out on caretPosition:0', () => {
testUtil.setDOM(); setDOM();
const keyboard = new Keyboard(); const keyboard = new Keyboard();
keyboard.setInput("test"); keyboard.setInput("test");
keyboard.caretPosition = 0;
keyboard.setCaretPosition(0);
keyboard.utilities.removeAt(keyboard.getInput(), 0); keyboard.utilities.removeAt(keyboard.getInput(), 0);
expect(keyboard.getInput()).toBe("test"); expect(keyboard.getInput()).toBe("test");
keyboard.setInput("test"); keyboard.setInput("test");
keyboard.caretPosition = 5;
keyboard.utilities.removeAt(keyboard.getInput(), 0, true); keyboard.setCaretPosition(5);
expect(keyboard.caretPosition).toBe(4); keyboard.utilities.removeAt(keyboard.getInput(), 0, 0, true);
expect(keyboard.caretPosition).toBe(5);
}); });
it('Keyboard removeAt will remove multi-byte unicodes with caretPos>0', () => { it('Keyboard removeAt will remove multi-byte unicodes with caretPos>0', () => {
testUtil.setDOM(); setDOM();
const keyboard = new Keyboard(); const keyboard = new Keyboard();
keyboard.caretPosition = 6; keyboard.setCaretPosition(6);
let output = keyboard.utilities.removeAt("test\uD83D\uDE00", 6); let output = keyboard.utilities.removeAt("test\uD83D\uDE00", 6, 6);
expect(output).toBe("test"); expect(output).toBe("test");
keyboard.caretPosition = 6; keyboard.setCaretPosition(6);
output = keyboard.utilities.removeAt("test\uD83D\uDE00", 6, true); output = keyboard.utilities.removeAt("test\uD83D\uDE00", 6, 6, true);
expect(keyboard.caretPosition).toBe(4); expect(keyboard.caretPosition).toBe(4);
}); });
it('Keyboard removeAt will not remove multi-byte unicodes with caretPos:0', () => { it('Keyboard removeAt will not remove multi-byte unicodes with caretPos:0', () => {
testUtil.setDOM(); setDOM();
const keyboard = new Keyboard(); const keyboard = new Keyboard();
let output = keyboard.utilities.removeAt("\uD83D\uDE00"); let output = keyboard.utilities.removeAt("\uD83D\uDE00");
expect(output).toBeFalsy(); expect(output).toBeFalsy();
output = keyboard.utilities.removeAt("\uD83D\uDE00", 0, true); output = keyboard.utilities.removeAt("\uD83D\uDE00", 0, 0, true);
expect(output).toBeFalsy(); expect(output).toBe("\uD83D\uDE00");
});
it('Keyboard removeAt will propagate caretPosition', () => {
clearDOM();
document.body.innerHTML = `
<div class="simple-keyboard"></div>
<div class="keyboard2"></div>
`;
const keyboard = new Keyboard({ useMouseEvents: true });
const keyboard2 = new Keyboard('.keyboard2');
keyboard.input.default = "hello";
keyboard2.input.default = "world"
keyboard.setCaretPosition(1);
expect(keyboard.getCaretPosition()).toBe(1);
expect(keyboard.getCaretPositionEnd()).toBe(1);
keyboard.setCaretPosition(1, 3);
expect(keyboard.getCaretPosition()).toBe(1);
expect(keyboard.getCaretPositionEnd()).toBe(3);
keyboard.getButtonElement('{bksp}').onclick();
expect(keyboard.getCaretPosition()).toBe(1);
expect(keyboard2.getCaretPosition()).toBe(null);
expect(keyboard.getInput()).toBe('hlo');
expect(keyboard.getCaretPositionEnd()).toBe(1);
expect(keyboard2.getCaretPositionEnd()).toBe(null);
});
it('Keyboard removeAt will propagate caretPosition in a syncInstanceInputs setting', () => {
clearDOM();
document.body.innerHTML = `
<div class="simple-keyboard"></div>
<div class="keyboard2"></div>
`;
const keyboard = new Keyboard({ useMouseEvents: true, syncInstanceInputs: true });
const keyboard2 = new Keyboard('.keyboard2');
keyboard.input.default = "hello"
keyboard.setCaretPosition(1);
expect(keyboard.getCaretPosition()).toBe(1);
expect(keyboard.getCaretPositionEnd()).toBe(1);
keyboard.setCaretPosition(1, 3);
expect(keyboard.getCaretPosition()).toBe(1);
expect(keyboard.getCaretPositionEnd()).toBe(3);
keyboard.getButtonElement('{bksp}').onclick();
expect(keyboard.getCaretPosition()).toBe(1);
expect(keyboard2.getCaretPosition()).toBe(1);
expect(keyboard.getInput()).toBe('hlo');
expect(keyboard.getCaretPositionEnd()).toBe(1);
expect(keyboard2.getCaretPositionEnd()).toBe(1);
}); });
it('Keyboard removeAt will remove regular strings', () => { it('Keyboard removeAt will remove regular strings', () => {
testUtil.setDOM(); setDOM();
const keyboard = new Keyboard({ const keyboard = new Keyboard({
debug: true debug: true
}); });
keyboard.caretPosition = 6; keyboard.setCaretPosition(6);
let output = keyboard.utilities.removeAt("testie", 6); let output = keyboard.utilities.removeAt("testie", 6, 6);
expect(output).toBe("testi"); expect(output).toBe("testi");
keyboard.caretPosition = 6; keyboard.setCaretPosition(6);
output = keyboard.utilities.removeAt("testie", 6, true); output = keyboard.utilities.removeAt("testie", 6, 6, true);
expect(keyboard.caretPosition).toBe(5); expect(keyboard.caretPosition).toBe(5);
}); });
it('Keyboard removeAt will work with unset or start caretPosition', () => {
setDOM();
const keyboard = new Keyboard({
debug: true
});
let output = keyboard.utilities.removeAt("test");
expect(output).toBe("tes");
output = keyboard.utilities.removeAt("test", null, null);
expect(output).toBe("tes");
output = keyboard.utilities.removeAt("😀", null, null);
expect(output).toBe("");
/**
* Will also work with moveCaret
*/
keyboard.setCaretPosition(3);
output = keyboard.utilities.removeAt("test", null, null, true);
expect(output).toBe("tes");
expect(keyboard.getCaretPosition()).toBe(2);
keyboard.setCaretPosition(2);
output = keyboard.utilities.removeAt("😀", null, null, true);
expect(output).toBe("");
expect(keyboard.getCaretPosition()).toBe(0);
});
it('Keyboard will work with custom (and weird) class', () => { it('Keyboard will work with custom (and weird) class', () => {
testUtil.setDOM("my--weird--class"); setDOM("my--weird--class");
const keyboard = new Keyboard(".my--weird--class"); const keyboard = new Keyboard(".my--weird--class");
expect(keyboard.keyboardDOMClass).toBe("my--weird--class"); expect(keyboard.keyboardDOMClass).toBe("my--weird--class");
}); });
it('Keyboard camelCase will work with empty strings', () => { it('Keyboard camelCase will work with empty strings', () => {
testUtil.setDOM(); setDOM();
const keyboard = new Keyboard(); const keyboard = new Keyboard();
expect(keyboard.utilities.camelCase()).toBeFalsy(); expect(keyboard.utilities.camelCase()).toBeFalsy();
}); });

5
src/setupTests.js Normal file
View File

@ -0,0 +1,5 @@
// 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';

View File

@ -1,12 +1,11 @@
/** /**
* Test Utility Functions * Test Utility Functions
*/ */
export default class TestUtility {
/** /**
* Sets a basic DOM structure to test in * Sets a basic DOM structure to test in
*/ */
setDOM = (divClass) => { export const setDOM = (divClass) => {
this.clear(); clearDOM();
const wrapperDOM = document.createElement('div'); const wrapperDOM = document.createElement('div');
wrapperDOM.setAttribute("id", "root"); wrapperDOM.setAttribute("id", "root");
@ -20,19 +19,29 @@ export default class TestUtility {
/** /**
* Clears DOM structure * Clears DOM structure
*/ */
clear = () => { export const clearDOM = () => {
document.body.innerHTML = ""; document.body.innerHTML = "";
} }
/**
* Trigger pointerup
*/
export const triggerDocumentPointerUp = (e = {}) => (document.onpointerup || document.onmouseup || document.ontouchstart)(e);
/**
* Trigger pointerdown
*/
export const triggerDocumentPointerDown = (e = {}) => (document.onpointerdown || document.onmousedown || document.ontouchend)(e);
/** /**
* Test if standard buttons respect maxLength and do input a value * Test if standard buttons respect maxLength and do input a value
*/ */
testLayoutStdButtons = (keyboard) => { export const testLayoutStdButtons = (keyboard) => {
let stdBtnCount = 0; let stdBtnCount = 0;
let fullInput = ''; let fullInput = '';
this.iterateButtons((button) => { iterateButtons((button) => {
let label = button.getAttribute("data-skbtn"); const label = button.getAttribute("data-skbtn");
if(label.includes("{")) if(label.includes("{"))
return false; return false;
@ -41,7 +50,7 @@ export default class TestUtility {
button.onclick(); button.onclick();
// Recording fullInput, bypasses maxLength // Recording fullInput, bypasses maxLength
fullInput = keyboard.utilities.getUpdatedInput(label, fullInput, keyboard.options, null); fullInput = keyboard.utilities.getUpdatedInput(label, fullInput);
stdBtnCount += label.length; stdBtnCount += label.length;
}); });
@ -76,12 +85,12 @@ export default class TestUtility {
/** /**
* Test if function buttons are interactive (have an onclick) * Test if function buttons are interactive (have an onclick)
*/ */
testLayoutFctButtons = (callback) => { export const testLayoutFctButtons = (callback) => {
let fctBtnCount = 0; let fctBtnCount = 0;
let fctBtnHasOnclickCount = 0; let fctBtnHasOnclickCount = 0;
this.iterateButtons((button) => { iterateButtons((button) => {
let label = button.getAttribute("data-skbtn"); const label = button.getAttribute("data-skbtn");
if(!label.includes("{") && !label.includes("}")) if(!label.includes("{") && !label.includes("}"))
return false; return false;
@ -100,8 +109,8 @@ export default class TestUtility {
/** /**
* Iterates on the keyboard buttons * Iterates on the keyboard buttons
*/ */
iterateButtons = (callback, selector) => { export const iterateButtons = (callback, selector) => {
let rows = document.body.querySelector(selector || '.simple-keyboard').children; const rows = document.body.querySelector(selector || '.simple-keyboard').children;
Array.from(rows).forEach(row => { Array.from(rows).forEach(row => {
Array.from(row.children).forEach((button) => { Array.from(row.children).forEach((button) => {
@ -109,4 +118,3 @@ export default class TestUtility {
}); });
}); });
} }
}