├── .browserslistrc ├── .github └── workflows │ └── build.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── README ├── consistent-shading-v1.0.10.png └── consistent-shading.png ├── build └── rollup.config.js ├── package-lock.json ├── package.json ├── src ├── colors.ts ├── generator.ts ├── index.ts ├── types.d.ts └── util.ts └── tsconfig.json /.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | not dead -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | strategy: 11 | matrix: 12 | node-version: [12.x] 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | - name: Use Node.js ${{ matrix.node-version }} 17 | uses: actions/setup-node@v1 18 | with: 19 | node-version: ${{ matrix.node-version }} 20 | - run: npm install 21 | - run: npm run build 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | src/index.test.ts -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | #### 1.1.0 - Latest release 2 | 3 | - Added alpha shades. Now, instead of only being able to generate 100% opacity shades, transparent black/white shades can also be generated via `generateAlpha` from within `ConsistentShading` instances. 4 | 5 | - Added `rgba` as a result type for `generateAlpha`. It is a tuple of 4 numbers, and is not meant to work with `convert`. 6 | 7 | - Fixed some precision bugs. Shades should be more accurate now. 8 | 9 | - Fixed a bug which caused crashes. If the input type was `lch` the converter would try to convert a type to itself, which is not defined in `color-convert`. 10 | 11 | #### 1.0.0 - 1.0.10 12 | 13 | Initial release. Available utilities: 14 | 15 | - `Color` class, useful for conversion. 16 | - `ConsistentShading` class, the main generator. 17 | - Instances of this will contain a `generate` function, which will return the list of solid shades that match the given model. -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Gârleanu Alexandru-Ștefan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # consistent-shading 2 | 3 | ![GitHub Workflow Status](https://img.shields.io/github/workflow/status/ugudango/consistent-shading/build?style=for-the-badge) 4 | ![NPM](https://img.shields.io/npm/l/consistent-shading?style=for-the-badge) 5 | ![npm](https://img.shields.io/npm/v/consistent-shading?style=for-the-badge) 6 | ![npm bundle size](https://img.badgesize.io/https:/unpkg.com/consistent-shading/dist/index.min.js?label=Minified%20size&style=for-the-badge&color=blue) 7 | 8 | Shade consistency for various hues, based on luminance. **Supports both solid and transparent shades.** 9 | 10 | ![](README/consistent-shading.png) 11 | 12 | ## Installation 13 | 14 | For the npm package, use\: 15 | 16 | ```bash 17 | npm install --save consistent-shading 18 | ``` 19 | 20 | For the ECMAScript module in browsers, use\: 21 | 22 | ```html 23 | 24 | ``` 25 | 26 | For the minfied module _(only ~750 bytes!)_ in browsers, use\: 27 | 28 | ```html 29 | 30 | ``` 31 | 32 | ## Problem 33 | 34 | The colour spectrum differs in luminance, at maximum saturation. As such, applying the same shadows throughout your design will create uneven colouring, due to the difference in _contrast_. 35 | 36 | Finding aesthetically pleasing combinations of shadows and highlights is difficult enough for one colour, but getting consistent shading with a varied pallete can be really time consuming. 37 | 38 | ## Solution 39 | 40 | The easiest solution lies in the new HCL (or LCH) colour format, which manages to separate luminance into an independent parameter. 41 | 42 | To use this library you have to provide an _ideal base colour_, along with an array of _ideal shades_, that you have found through experimentation and determined to be fitting for your design. 43 | 44 | After providing these in your format of choice\* pick a new base colour and feed it into the generator. This will cause it to return an array of relevant, _contrast consistent_ shades, that _look pleasing_, just like the shaded base colour. 45 | 46 | ## Usage 47 | 48 | > Try to pick base colours that are not on either extremes of the luminance spectrum, since the generator caps at the maximum and minimum levels. (You can't obtain a color darker than black) 49 | 50 | ```typescript 51 | import { Color, ConsistentShading } from "consistent-shading"; 52 | 53 | const IdealBaseColor = new Color("hex", ["#3454D1"]); 54 | 55 | const IdealShades = [ 56 | new Color("rgb", [122, 143, 225]), 57 | new Color("lab", [31, 28, -58]), 58 | new Color("cmyk", [27, 22, 0, 7]), 59 | ]; 60 | 61 | const Generator = new ConsistentShading(IdealBaseColor, IdealShades); 62 | 63 | const AnyColor = new Color("hsl", [345, 63, 51]); 64 | 65 | Generator.generate(AnyColor); 66 | /* => Array of Color objects, representing shades of AnyColor. */ 67 | 68 | Generator.generateAlpha(AnyColor, /* optional */ 1.5); 69 | /* => Array of Color objects, rgba format, representing 70 | semi-transparent black or white shades of AnyColor. */ 71 | ``` 72 | 73 | ## API Description 74 | 75 | ### Color formats\* 76 | 77 | The color formats are taken directly from the [color-convert](https://www.npmjs.com/package/color-convert) package. These are as follows: 78 | 79 | ```typescript 80 | "rgb" | "hsl" | "hsv" | "hwb" | "cmyk" | "xyz" | "lab" | "lch" | "hex" | "keyword" | "ansi16" | "ansi256" | "hcg" | "apple" | "gray"; 81 | ``` 82 | 83 | ### Color class 84 | 85 | Color is provided with two fields, one of which is the `value`, that is mostly a tuple of varying sizes (check [this](../blob/master/src/colors.ts) for more details), and the other is the `format`, which enables the library to correctly interpret your colours and convert them accordingly. 86 | 87 | ```typescript 88 | const myColor = new Color("rgb", [255, 0, 255]); 89 | ``` 90 | 91 | ### Generator class 92 | 93 | The `ConsistentShading` class, which is in basic terms the shade generator and main functionality of this library (thus taking it's name), contains an important construrctor, which takes as a first paramenter the base `Color` and an array of shades, that are represented through an array `Color[]`. 94 | 95 | *** 96 | 97 | #### `generate(baseColor: Color)` 98 | After it has calculated the relative luminances of the provided shades, the `generate` method can be used to obtain **solid shades**. This method has a first mandatory parameter, which is the new base color, and an optional second parameter, which specifies a preferred format for the resulting shades. 99 | 100 | *** 101 | 102 | #### `generateAlpha(baseColor: Color, threshold?: number)` 103 | To obtain **alpha shadows**, a binary search method was used. 104 | 105 | The solid shades are calculated first, and then it is decided based on luminance if the base color of the `rgba` value should be black or white. 106 | 107 | After the decision is made, the opacity is guessed via binary search, with a configurable threshold. If the guessed opacity of the mask is, within the threshold values, equal in luminance to the generated solid shade, it is accepted. 108 | 109 | _Higher threshold will make the generation faster, but the results will be less accurate. It is defaulted to 1, which translates to 1% opacity threshold._ 110 | 111 | **Setting the threshold to a value close to 0 may result in an infinite loop.** 112 | 113 | *** 114 | 115 | > These methods can be called as many times as it's needed, since they do not mutate any internal state of the generator object. 116 | -------------------------------------------------------------------------------- /README/consistent-shading-v1.0.10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ugudango/consistent-shading/076a1b89320ad5533e7a05f60116802eea1719d6/README/consistent-shading-v1.0.10.png -------------------------------------------------------------------------------- /README/consistent-shading.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ugudango/consistent-shading/076a1b89320ad5533e7a05f60116802eea1719d6/README/consistent-shading.png -------------------------------------------------------------------------------- /build/rollup.config.js: -------------------------------------------------------------------------------- 1 | import { nodeResolve } from '@rollup/plugin-node-resolve'; 2 | import replace from '@rollup/plugin-replace'; 3 | import { terser } from 'rollup-plugin-terser'; 4 | import minimist from 'minimist'; 5 | import typescript from '@rollup/plugin-typescript'; 6 | import pkg from '../package.json'; 7 | 8 | const argv = minimist(process.argv.slice(2)); 9 | 10 | const external = [ 11 | 'color-convert', 12 | ]; 13 | 14 | const baseConfig = { 15 | input: 'src/index.ts', 16 | external, 17 | plugins: { 18 | customResolver: nodeResolve({ 19 | extensions: ['.js', '.jsx', '.ts', '.tsx', '.vue'], 20 | }), 21 | replace: { 22 | 'process.env.NODE_ENV': JSON.stringify('production'), 23 | }, 24 | } 25 | } 26 | 27 | const buildFormats = []; 28 | if(!argv.format || argv.format === 'es') { 29 | const esConfig = { 30 | ...baseConfig, 31 | output: { 32 | dir: 'dist/esm', 33 | format: 'esm', 34 | exports: 'named', 35 | }, 36 | plugins: [ 37 | replace(baseConfig.plugins.replace), 38 | typescript({ 39 | declaration: true, 40 | declarationDir: 'dist/esm/types/', 41 | rootDir: 'src/', 42 | }), 43 | ], 44 | }; 45 | 46 | buildFormats.push(esConfig); 47 | } 48 | 49 | if (!argv.format || argv.format === 'cjs') { 50 | const umdConfig = { 51 | ...baseConfig, 52 | output: { 53 | compact: true, 54 | file: pkg.main, 55 | format: 'cjs', 56 | name: 'ConsistentShading', 57 | exports: 'auto', 58 | }, 59 | plugins: [ 60 | replace(baseConfig.plugins.replace), 61 | typescript(), 62 | ], 63 | }; 64 | 65 | buildFormats.push(umdConfig); 66 | } 67 | 68 | if (!argv.format || argv.format === 'iife') { 69 | const unpkgConfig = { 70 | ...baseConfig, 71 | output: { 72 | compact: true, 73 | file: pkg.unpkg, 74 | format: 'iife', 75 | name: 'ConsistentShading', 76 | exports: 'auto', 77 | }, 78 | plugins: [ 79 | replace(baseConfig.plugins.replace), 80 | typescript(), 81 | terser({ 82 | output: { 83 | ecma: 5, 84 | }, 85 | }), 86 | ], 87 | }; 88 | 89 | buildFormats.push(unpkgConfig); 90 | } 91 | 92 | export default buildFormats; -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "consistent-shading", 3 | "version": "1.0.10", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@babel/code-frame": { 8 | "version": "7.10.4", 9 | "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", 10 | "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", 11 | "dev": true, 12 | "requires": { 13 | "@babel/highlight": "^7.10.4" 14 | } 15 | }, 16 | "@babel/helper-module-imports": { 17 | "version": "7.12.5", 18 | "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.12.5.tgz", 19 | "integrity": "sha512-SR713Ogqg6++uexFRORf/+nPXMmWIn80TALu0uaFb+iQIUoR7bOC7zBWyzBs5b3tBBJXuyD0cRu1F15GyzjOWA==", 20 | "dev": true, 21 | "requires": { 22 | "@babel/types": "^7.12.5" 23 | } 24 | }, 25 | "@babel/helper-validator-identifier": { 26 | "version": "7.10.4", 27 | "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", 28 | "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==", 29 | "dev": true 30 | }, 31 | "@babel/highlight": { 32 | "version": "7.10.4", 33 | "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", 34 | "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", 35 | "dev": true, 36 | "requires": { 37 | "@babel/helper-validator-identifier": "^7.10.4", 38 | "chalk": "^2.0.0", 39 | "js-tokens": "^4.0.0" 40 | } 41 | }, 42 | "@babel/types": { 43 | "version": "7.12.12", 44 | "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.12.12.tgz", 45 | "integrity": "sha512-lnIX7piTxOH22xE7fDXDbSHg9MM1/6ORnafpJmov5rs0kX5g4BZxeXNJLXsMRiO0U5Rb8/FvMS6xlTnTHvxonQ==", 46 | "dev": true, 47 | "requires": { 48 | "@babel/helper-validator-identifier": "^7.12.11", 49 | "lodash": "^4.17.19", 50 | "to-fast-properties": "^2.0.0" 51 | }, 52 | "dependencies": { 53 | "@babel/helper-validator-identifier": { 54 | "version": "7.12.11", 55 | "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz", 56 | "integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==", 57 | "dev": true 58 | } 59 | } 60 | }, 61 | "@rollup/plugin-babel": { 62 | "version": "5.2.3", 63 | "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.2.3.tgz", 64 | "integrity": "sha512-DOMc7nx6y5xFi86AotrFssQqCen6CxYn+zts5KSI879d4n1hggSb4TH3mjVgG17Vc3lZziWWfcXzrEmVdzPMdw==", 65 | "dev": true, 66 | "requires": { 67 | "@babel/helper-module-imports": "^7.10.4", 68 | "@rollup/pluginutils": "^3.1.0" 69 | } 70 | }, 71 | "@rollup/plugin-commonjs": { 72 | "version": "17.1.0", 73 | "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-17.1.0.tgz", 74 | "integrity": "sha512-PoMdXCw0ZyvjpCMT5aV4nkL0QywxP29sODQsSGeDpr/oI49Qq9tRtAsb/LbYbDzFlOydVEqHmmZWFtXJEAX9ew==", 75 | "dev": true, 76 | "requires": { 77 | "@rollup/pluginutils": "^3.1.0", 78 | "commondir": "^1.0.1", 79 | "estree-walker": "^2.0.1", 80 | "glob": "^7.1.6", 81 | "is-reference": "^1.2.1", 82 | "magic-string": "^0.25.7", 83 | "resolve": "^1.17.0" 84 | }, 85 | "dependencies": { 86 | "estree-walker": { 87 | "version": "2.0.2", 88 | "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", 89 | "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", 90 | "dev": true 91 | } 92 | } 93 | }, 94 | "@rollup/plugin-node-resolve": { 95 | "version": "11.1.1", 96 | "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-11.1.1.tgz", 97 | "integrity": "sha512-zlBXR4eRS+2m79TsUZWhsd0slrHUYdRx4JF+aVQm+MI0wsKdlpC2vlDVjmlGvtZY1vsefOT9w3JxvmWSBei+Lg==", 98 | "dev": true, 99 | "requires": { 100 | "@rollup/pluginutils": "^3.1.0", 101 | "@types/resolve": "1.17.1", 102 | "builtin-modules": "^3.1.0", 103 | "deepmerge": "^4.2.2", 104 | "is-module": "^1.0.0", 105 | "resolve": "^1.19.0" 106 | } 107 | }, 108 | "@rollup/plugin-replace": { 109 | "version": "2.3.4", 110 | "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-2.3.4.tgz", 111 | "integrity": "sha512-waBhMzyAtjCL1GwZes2jaE9MjuQ/DQF2BatH3fRivUF3z0JBFrU0U6iBNC/4WR+2rLKhaAhPWDNPYp4mI6RqdQ==", 112 | "dev": true, 113 | "requires": { 114 | "@rollup/pluginutils": "^3.1.0", 115 | "magic-string": "^0.25.7" 116 | } 117 | }, 118 | "@rollup/plugin-typescript": { 119 | "version": "8.1.1", 120 | "resolved": "https://registry.npmjs.org/@rollup/plugin-typescript/-/plugin-typescript-8.1.1.tgz", 121 | "integrity": "sha512-DPFy0SV8/GgHFL31yPFVo0G1T3yzwdw6R9KisBfO2zCYbDHUqDChSWr1KmtpGz/TmutpoGJjIvu80p9HzCEF0A==", 122 | "dev": true, 123 | "requires": { 124 | "@rollup/pluginutils": "^3.1.0", 125 | "resolve": "^1.17.0" 126 | } 127 | }, 128 | "@rollup/pluginutils": { 129 | "version": "3.1.0", 130 | "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz", 131 | "integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==", 132 | "dev": true, 133 | "requires": { 134 | "@types/estree": "0.0.39", 135 | "estree-walker": "^1.0.1", 136 | "picomatch": "^2.2.2" 137 | } 138 | }, 139 | "@types/estree": { 140 | "version": "0.0.39", 141 | "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", 142 | "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==", 143 | "dev": true 144 | }, 145 | "@types/node": { 146 | "version": "14.11.2", 147 | "resolved": "https://registry.npmjs.org/@types/node/-/node-14.11.2.tgz", 148 | "integrity": "sha512-jiE3QIxJ8JLNcb1Ps6rDbysDhN4xa8DJJvuC9prr6w+1tIh+QAbYyNF3tyiZNLDBIuBCf4KEcV2UvQm/V60xfA==", 149 | "dev": true 150 | }, 151 | "@types/resolve": { 152 | "version": "1.17.1", 153 | "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz", 154 | "integrity": "sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==", 155 | "dev": true, 156 | "requires": { 157 | "@types/node": "*" 158 | } 159 | }, 160 | "ansi-styles": { 161 | "version": "3.2.1", 162 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", 163 | "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", 164 | "dev": true, 165 | "requires": { 166 | "color-convert": "^1.9.0" 167 | }, 168 | "dependencies": { 169 | "color-convert": { 170 | "version": "1.9.3", 171 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", 172 | "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", 173 | "dev": true, 174 | "requires": { 175 | "color-name": "1.1.3" 176 | } 177 | }, 178 | "color-name": { 179 | "version": "1.1.3", 180 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", 181 | "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", 182 | "dev": true 183 | } 184 | } 185 | }, 186 | "balanced-match": { 187 | "version": "1.0.0", 188 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", 189 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", 190 | "dev": true 191 | }, 192 | "brace-expansion": { 193 | "version": "1.1.11", 194 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 195 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 196 | "dev": true, 197 | "requires": { 198 | "balanced-match": "^1.0.0", 199 | "concat-map": "0.0.1" 200 | } 201 | }, 202 | "buffer-from": { 203 | "version": "1.1.1", 204 | "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", 205 | "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", 206 | "dev": true 207 | }, 208 | "builtin-modules": { 209 | "version": "3.2.0", 210 | "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.2.0.tgz", 211 | "integrity": "sha512-lGzLKcioL90C7wMczpkY0n/oART3MbBa8R9OFGE1rJxoVI86u4WAGfEk8Wjv10eKSyTHVGkSo3bvBylCEtk7LA==", 212 | "dev": true 213 | }, 214 | "chalk": { 215 | "version": "2.4.2", 216 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", 217 | "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", 218 | "dev": true, 219 | "requires": { 220 | "ansi-styles": "^3.2.1", 221 | "escape-string-regexp": "^1.0.5", 222 | "supports-color": "^5.3.0" 223 | } 224 | }, 225 | "color-convert": { 226 | "version": "2.0.1", 227 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", 228 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", 229 | "requires": { 230 | "color-name": "~1.1.4" 231 | } 232 | }, 233 | "color-name": { 234 | "version": "1.1.4", 235 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", 236 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" 237 | }, 238 | "commander": { 239 | "version": "2.20.3", 240 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", 241 | "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", 242 | "dev": true 243 | }, 244 | "commondir": { 245 | "version": "1.0.1", 246 | "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", 247 | "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", 248 | "dev": true 249 | }, 250 | "concat-map": { 251 | "version": "0.0.1", 252 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 253 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", 254 | "dev": true 255 | }, 256 | "cross-env": { 257 | "version": "7.0.3", 258 | "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", 259 | "integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==", 260 | "dev": true, 261 | "requires": { 262 | "cross-spawn": "^7.0.1" 263 | } 264 | }, 265 | "cross-spawn": { 266 | "version": "7.0.3", 267 | "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", 268 | "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", 269 | "dev": true, 270 | "requires": { 271 | "path-key": "^3.1.0", 272 | "shebang-command": "^2.0.0", 273 | "which": "^2.0.1" 274 | } 275 | }, 276 | "deepmerge": { 277 | "version": "4.2.2", 278 | "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", 279 | "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", 280 | "dev": true 281 | }, 282 | "escape-string-regexp": { 283 | "version": "1.0.5", 284 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 285 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", 286 | "dev": true 287 | }, 288 | "estree-walker": { 289 | "version": "1.0.1", 290 | "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz", 291 | "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==", 292 | "dev": true 293 | }, 294 | "fs.realpath": { 295 | "version": "1.0.0", 296 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 297 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", 298 | "dev": true 299 | }, 300 | "fsevents": { 301 | "version": "2.1.3", 302 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", 303 | "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", 304 | "dev": true, 305 | "optional": true 306 | }, 307 | "function-bind": { 308 | "version": "1.1.1", 309 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", 310 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", 311 | "dev": true 312 | }, 313 | "glob": { 314 | "version": "7.1.6", 315 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", 316 | "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", 317 | "dev": true, 318 | "requires": { 319 | "fs.realpath": "^1.0.0", 320 | "inflight": "^1.0.4", 321 | "inherits": "2", 322 | "minimatch": "^3.0.4", 323 | "once": "^1.3.0", 324 | "path-is-absolute": "^1.0.0" 325 | } 326 | }, 327 | "has": { 328 | "version": "1.0.3", 329 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", 330 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", 331 | "dev": true, 332 | "requires": { 333 | "function-bind": "^1.1.1" 334 | } 335 | }, 336 | "has-flag": { 337 | "version": "3.0.0", 338 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", 339 | "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", 340 | "dev": true 341 | }, 342 | "inflight": { 343 | "version": "1.0.6", 344 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 345 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 346 | "dev": true, 347 | "requires": { 348 | "once": "^1.3.0", 349 | "wrappy": "1" 350 | } 351 | }, 352 | "inherits": { 353 | "version": "2.0.4", 354 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 355 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", 356 | "dev": true 357 | }, 358 | "is-core-module": { 359 | "version": "2.2.0", 360 | "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.2.0.tgz", 361 | "integrity": "sha512-XRAfAdyyY5F5cOXn7hYQDqh2Xmii+DEfIcQGxK/uNwMHhIkPWO0g8msXcbzLe+MpGoR951MlqM/2iIlU4vKDdQ==", 362 | "dev": true, 363 | "requires": { 364 | "has": "^1.0.3" 365 | } 366 | }, 367 | "is-module": { 368 | "version": "1.0.0", 369 | "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", 370 | "integrity": "sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE=", 371 | "dev": true 372 | }, 373 | "is-reference": { 374 | "version": "1.2.1", 375 | "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", 376 | "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==", 377 | "dev": true, 378 | "requires": { 379 | "@types/estree": "*" 380 | } 381 | }, 382 | "isexe": { 383 | "version": "2.0.0", 384 | "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", 385 | "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", 386 | "dev": true 387 | }, 388 | "jest-worker": { 389 | "version": "26.3.0", 390 | "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.3.0.tgz", 391 | "integrity": "sha512-Vmpn2F6IASefL+DVBhPzI2J9/GJUsqzomdeN+P+dK8/jKxbh8R3BtFnx3FIta7wYlPU62cpJMJQo4kuOowcMnw==", 392 | "dev": true, 393 | "requires": { 394 | "@types/node": "*", 395 | "merge-stream": "^2.0.0", 396 | "supports-color": "^7.0.0" 397 | }, 398 | "dependencies": { 399 | "has-flag": { 400 | "version": "4.0.0", 401 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", 402 | "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", 403 | "dev": true 404 | }, 405 | "supports-color": { 406 | "version": "7.2.0", 407 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", 408 | "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", 409 | "dev": true, 410 | "requires": { 411 | "has-flag": "^4.0.0" 412 | } 413 | } 414 | } 415 | }, 416 | "js-tokens": { 417 | "version": "4.0.0", 418 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", 419 | "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", 420 | "dev": true 421 | }, 422 | "lodash": { 423 | "version": "4.17.20", 424 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", 425 | "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==", 426 | "dev": true 427 | }, 428 | "magic-string": { 429 | "version": "0.25.7", 430 | "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz", 431 | "integrity": "sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==", 432 | "dev": true, 433 | "requires": { 434 | "sourcemap-codec": "^1.4.4" 435 | } 436 | }, 437 | "merge-stream": { 438 | "version": "2.0.0", 439 | "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", 440 | "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", 441 | "dev": true 442 | }, 443 | "minimatch": { 444 | "version": "3.0.4", 445 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 446 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 447 | "dev": true, 448 | "requires": { 449 | "brace-expansion": "^1.1.7" 450 | } 451 | }, 452 | "minimist": { 453 | "version": "1.2.5", 454 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", 455 | "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", 456 | "dev": true 457 | }, 458 | "once": { 459 | "version": "1.4.0", 460 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 461 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 462 | "dev": true, 463 | "requires": { 464 | "wrappy": "1" 465 | } 466 | }, 467 | "path-is-absolute": { 468 | "version": "1.0.1", 469 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 470 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", 471 | "dev": true 472 | }, 473 | "path-key": { 474 | "version": "3.1.1", 475 | "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", 476 | "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", 477 | "dev": true 478 | }, 479 | "path-parse": { 480 | "version": "1.0.6", 481 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", 482 | "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", 483 | "dev": true 484 | }, 485 | "picomatch": { 486 | "version": "2.2.2", 487 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", 488 | "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", 489 | "dev": true 490 | }, 491 | "randombytes": { 492 | "version": "2.1.0", 493 | "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", 494 | "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", 495 | "dev": true, 496 | "requires": { 497 | "safe-buffer": "^5.1.0" 498 | } 499 | }, 500 | "resolve": { 501 | "version": "1.19.0", 502 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.19.0.tgz", 503 | "integrity": "sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg==", 504 | "dev": true, 505 | "requires": { 506 | "is-core-module": "^2.1.0", 507 | "path-parse": "^1.0.6" 508 | } 509 | }, 510 | "rollup": { 511 | "version": "2.38.2", 512 | "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.38.2.tgz", 513 | "integrity": "sha512-3Sg65zfgqsnI2LUFsOmhJDvTWXwio+taySq/dsyvel8+GW+AxeW9V6YZG8BpVGQk/TS4uvGLARRH5T3ygDyyNQ==", 514 | "dev": true, 515 | "requires": { 516 | "fsevents": "~2.1.2" 517 | } 518 | }, 519 | "rollup-plugin-terser": { 520 | "version": "7.0.2", 521 | "resolved": "https://registry.npmjs.org/rollup-plugin-terser/-/rollup-plugin-terser-7.0.2.tgz", 522 | "integrity": "sha512-w3iIaU4OxcF52UUXiZNsNeuXIMDvFrr+ZXK6bFZ0Q60qyVfq4uLptoS4bbq3paG3x216eQllFZX7zt6TIImguQ==", 523 | "dev": true, 524 | "requires": { 525 | "@babel/code-frame": "^7.10.4", 526 | "jest-worker": "^26.2.1", 527 | "serialize-javascript": "^4.0.0", 528 | "terser": "^5.0.0" 529 | } 530 | }, 531 | "safe-buffer": { 532 | "version": "5.2.1", 533 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 534 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", 535 | "dev": true 536 | }, 537 | "serialize-javascript": { 538 | "version": "4.0.0", 539 | "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", 540 | "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==", 541 | "dev": true, 542 | "requires": { 543 | "randombytes": "^2.1.0" 544 | } 545 | }, 546 | "shebang-command": { 547 | "version": "2.0.0", 548 | "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", 549 | "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", 550 | "dev": true, 551 | "requires": { 552 | "shebang-regex": "^3.0.0" 553 | } 554 | }, 555 | "shebang-regex": { 556 | "version": "3.0.0", 557 | "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", 558 | "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", 559 | "dev": true 560 | }, 561 | "source-map": { 562 | "version": "0.7.3", 563 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", 564 | "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", 565 | "dev": true 566 | }, 567 | "source-map-support": { 568 | "version": "0.5.19", 569 | "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", 570 | "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", 571 | "dev": true, 572 | "requires": { 573 | "buffer-from": "^1.0.0", 574 | "source-map": "^0.6.0" 575 | }, 576 | "dependencies": { 577 | "source-map": { 578 | "version": "0.6.1", 579 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", 580 | "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", 581 | "dev": true 582 | } 583 | } 584 | }, 585 | "sourcemap-codec": { 586 | "version": "1.4.8", 587 | "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", 588 | "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", 589 | "dev": true 590 | }, 591 | "supports-color": { 592 | "version": "5.5.0", 593 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", 594 | "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", 595 | "dev": true, 596 | "requires": { 597 | "has-flag": "^3.0.0" 598 | } 599 | }, 600 | "terser": { 601 | "version": "5.3.3", 602 | "resolved": "https://registry.npmjs.org/terser/-/terser-5.3.3.tgz", 603 | "integrity": "sha512-vRQDIlD+2Pg8YMwVK9kMM3yGylG95EIwzBai1Bw7Ot4OBfn3VP1TZn3EWx4ep2jERN/AmnVaTiGuelZSN7ds/A==", 604 | "dev": true, 605 | "requires": { 606 | "commander": "^2.20.0", 607 | "source-map": "~0.7.2", 608 | "source-map-support": "~0.5.19" 609 | } 610 | }, 611 | "to-fast-properties": { 612 | "version": "2.0.0", 613 | "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", 614 | "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", 615 | "dev": true 616 | }, 617 | "tslib": { 618 | "version": "2.1.0", 619 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz", 620 | "integrity": "sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==", 621 | "dev": true 622 | }, 623 | "typescript": { 624 | "version": "4.1.3", 625 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.1.3.tgz", 626 | "integrity": "sha512-B3ZIOf1IKeH2ixgHhj6la6xdwR9QrLC5d1VKeCSY4tvkqhF2eqd9O7txNlS0PO3GrBAFIdr3L1ndNwteUbZLYg==", 627 | "dev": true 628 | }, 629 | "which": { 630 | "version": "2.0.2", 631 | "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", 632 | "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", 633 | "dev": true, 634 | "requires": { 635 | "isexe": "^2.0.0" 636 | } 637 | }, 638 | "wrappy": { 639 | "version": "1.0.2", 640 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 641 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", 642 | "dev": true 643 | } 644 | } 645 | } 646 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "consistent-shading", 3 | "private": false, 4 | "description": "Shade consitency for various hues, based on luminance.", 5 | "keywords": [ 6 | "color", 7 | "consistent", 8 | "shade", 9 | "shading", 10 | "shadow", 11 | "light", 12 | "highlight", 13 | "hcl", 14 | "lch", 15 | "chroma", 16 | "alpha", 17 | "correct" 18 | ], 19 | "version": "1.1.0", 20 | "license": "MIT", 21 | "main": "dist/index.ssr.js", 22 | "browser": "dist/esm/index.js", 23 | "module": "dist/esm/index.js", 24 | "unpkg": "dist/index.min.js", 25 | "files": [ 26 | "dist/*" 27 | ], 28 | "types": "dist/esm/types/index.d.ts", 29 | "scripts": { 30 | "build": "cross-env NODE_ENV=production rollup -c build/rollup.config.js -m", 31 | "build:ssr": "cross-env NODE_ENV=production rollup -c build/rollup.config.js -m --format cjs", 32 | "build:es": "cross-env NODE_ENV=production rollup -c build/rollup.config.js -m --format es", 33 | "build:unpkg": "cross-env NODE_ENV=production rollup -c build/rollup.config.js -m --format iife", 34 | "watch": "rollup -c build/rollup.config.js -w" 35 | }, 36 | "author": { 37 | "name": "Gârleanu Alexandru-Ștefan" 38 | }, 39 | "repository": { 40 | "type": "git", 41 | "url": "https://github.com/ugudango/consistent-shading.git" 42 | }, 43 | "dependencies": { 44 | "color-convert": "^2.0.1" 45 | }, 46 | "devDependencies": { 47 | "@rollup/plugin-babel": "^5.2.3", 48 | "@rollup/plugin-commonjs": "^17.1.0", 49 | "@rollup/plugin-node-resolve": "^11.1.1", 50 | "@rollup/plugin-replace": "^2.3.4", 51 | "@rollup/plugin-typescript": "^8.1.1", 52 | "cross-env": "^7.0.3", 53 | "minimist": "^1.2.5", 54 | "rollup": "^2.38.2", 55 | "rollup-plugin-terser": "^7.0.2", 56 | "tslib": "^2.1.0", 57 | "typescript": "^4.1.3" 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/colors.ts: -------------------------------------------------------------------------------- 1 | export type rgb = [number, number, number]; 2 | export type rgba = [number, number, number, number]; 3 | type hsl = [number, number, number]; 4 | type hsv = [number, number, number]; 5 | type hwb = [number, number, number]; 6 | type cmyk = [number, number, number, number]; 7 | type xyz = [number, number, number]; 8 | type lab = [number, number, number]; 9 | export type lch = [number, number, number]; 10 | type hex = [string]; 11 | type keyword = [string]; 12 | type ansi16 = [string]; 13 | type ansi256 = [string]; 14 | type hcg = [number, number, number]; 15 | type apple = [number, number, number]; 16 | type gray = [number]; 17 | 18 | export type ColorFormat = 19 | rgb 20 | | rgba 21 | | hsl 22 | | hsv 23 | | hwb 24 | | cmyk 25 | | xyz 26 | | lab 27 | | lch 28 | | hex 29 | | keyword 30 | | ansi16 31 | | ansi256 32 | | hcg 33 | | apple 34 | | gray; 35 | 36 | export type ColorFormatLabel = 37 | 'rgb' 38 | | 'rgba' 39 | | 'hsl' 40 | | 'hsv' 41 | | 'hwb' 42 | | 'cmyk' 43 | | 'xyz' 44 | | 'lab' 45 | | 'lch' 46 | | 'hex' 47 | | 'keyword' 48 | | 'ansi16' 49 | | 'ansi256' 50 | | 'hcg' 51 | | 'apple' 52 | | 'gray'; 53 | 54 | export class Color { 55 | public value: ColorFormat; 56 | 57 | public format: ColorFormatLabel; 58 | 59 | public constructor(format: ColorFormatLabel, value: ColorFormat) { 60 | this.value = value; 61 | this.format = format; 62 | } 63 | } -------------------------------------------------------------------------------- /src/generator.ts: -------------------------------------------------------------------------------- 1 | import Util from './util'; 2 | import { Color, ColorFormatLabel, lch, rgb, rgba } from './colors'; 3 | import convert from 'color-convert'; 4 | 5 | export class ConsistentShading { 6 | private _base: lch; 7 | 8 | private _shades!: lch[]; 9 | 10 | private _deltas!: number[]; 11 | 12 | public constructor(base: Color, shades: Color[]) { 13 | this._base = convert[base.format]['lch'].raw(base.value); 14 | this._shades = new Array(); 15 | this._deltas = new Array(); 16 | shades.forEach((shade, index) => { 17 | const converted = shade.format === 'lch' ? [...shade.value] : convert[shade.format]['lch'].raw(shade.value); 18 | this._shades.push(converted); 19 | this._deltas.push(converted[0] - this._base[0]); 20 | }); 21 | } 22 | 23 | public generate(base: Color, exportFormat: ColorFormatLabel = base.format): Color[] { 24 | let exportedColors: Color[] = []; 25 | const lchBase: lch = base.format === 'lch' ? [...base.value] : convert[base.format]['lch'].raw(base.value); 26 | this._deltas.forEach((delta: number) => { 27 | let lchGenerated: lch = [...lchBase]; 28 | lchGenerated[0] += delta; 29 | lchGenerated[0] = Math.min(100, Math.max(0, lchGenerated[0])); 30 | let convertedLchGenerated = exportFormat === 'lch' ? [...lchGenerated] : convert['lch'][exportFormat](lchGenerated); 31 | const clgRounded = convertedLchGenerated.map?.((component: number) => Math.round(component)); 32 | if (typeof clgRounded !== 'undefined') { 33 | convertedLchGenerated = clgRounded; 34 | } 35 | const generatedColor = new Color(exportFormat, convertedLchGenerated); 36 | exportedColors.push(generatedColor); 37 | }); 38 | return exportedColors; 39 | } 40 | 41 | public generateAlpha(base: Color, threshold: number = 1): Color[] { 42 | const lchBase: lch = base.format === 'lch' ? [...base.value] : convert[base.format]['lch'].raw(base.value); 43 | 44 | const rgbBase: rgb = base.format === 'rgb' ? [...base.value] : convert[base.format]['rgb'].raw(base.value); 45 | 46 | const lchResults: lch[] = this.generate(base, 'lch').map((color) => color.value as lch); 47 | 48 | const rgbResults: rgb[] = lchResults.map((color) => convert['lch']['rgb'].raw(color) as rgb); 49 | 50 | const exportedColors: Color[] = []; 51 | 52 | rgbResults.forEach((result: rgb, index) => { 53 | const desiredLuminance = lchResults[index][0]; 54 | 55 | let currentColor: rgb; 56 | 57 | let bottom = 0; 58 | let top = 1; 59 | 60 | let currentOverlay: rgba = [255, 255, 255, 0]; 61 | let overlayIsBlack = 0; 62 | 63 | if (lchResults[index][0] < lchBase[0]) { 64 | currentOverlay = [0, 0, 0, 0]; 65 | overlayIsBlack = 1; 66 | } 67 | 68 | do { 69 | let middle = (bottom + top) / 2; 70 | currentOverlay[3] = middle; 71 | currentColor = Util.applyAlpha(rgbBase, currentOverlay); 72 | let currentLuminance = convert.rgb.lch.raw(currentColor)[0]; 73 | 74 | if (currentLuminance > desiredLuminance ? !overlayIsBlack : overlayIsBlack) 75 | top = middle - 0.01; 76 | else 77 | bottom = middle + 0.01; 78 | 79 | } while(!Util.checkLuminance(currentColor, desiredLuminance, threshold)); 80 | 81 | currentOverlay[3] = Math.round(currentOverlay[3] * 100) / 100; 82 | 83 | exportedColors.push(new Color('rgba', [...currentOverlay])); 84 | }); 85 | 86 | return exportedColors; 87 | } 88 | } -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export { ConsistentShading } from './generator'; 2 | export { Color } from './colors'; -------------------------------------------------------------------------------- /src/types.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'color-convert'; -------------------------------------------------------------------------------- /src/util.ts: -------------------------------------------------------------------------------- 1 | import { Color, ColorFormatLabel, lch, rgb, rgba } from './colors'; 2 | import convert from 'color-convert'; 3 | 4 | export default { 5 | applyAlpha(base: rgb, overlay: rgba): rgb { 6 | const result: rgb = [...base]; 7 | result.forEach((channel, index) => { 8 | result[index] = overlay[index] * overlay[3] + channel * (1 - overlay[3]); 9 | }); 10 | return result; 11 | }, 12 | checkLuminance(color: rgb, desiredLuminance: number, threshold: number = 1): boolean { 13 | const lchColor: lch = convert.rgb.lch.raw(color); 14 | const upperBound = desiredLuminance + threshold; 15 | const lowerBound = desiredLuminance - threshold; 16 | return lowerBound < lchColor[0] && lchColor[0] < upperBound; 17 | } 18 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "module": "ESNext", 5 | "strict": true, 6 | "importHelpers": true, 7 | "moduleResolution": "node", 8 | "experimentalDecorators": true, 9 | "skipLibCheck": true, 10 | "esModuleInterop": true, 11 | "allowSyntheticDefaultImports": true, 12 | "sourceMap": true, 13 | "baseUrl": ".", 14 | "noImplicitAny": true, 15 | "lib": [ 16 | "ESNext", 17 | ] 18 | }, 19 | "include": [ 20 | "src/**/*" 21 | ], 22 | "exclude": ["node_modules"] 23 | } 24 | --------------------------------------------------------------------------------