├── .circleci └── config.yml ├── .coveralls.yml ├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .github └── ISSUE_TEMPLATE.md ├── .gitignore ├── .husky └── pre-commit ├── .lintstagedrc ├── .npmignore ├── .nvmrc ├── .prettierrc ├── CHANGELOG.md ├── LICENSE ├── README.md ├── bundlesize.config.json ├── contributing.md ├── jest.config.js ├── jest.setup.js ├── package.json ├── rollup.config.js ├── src ├── __tests__ │ └── hex-to-css-filter.ts ├── color.ts ├── hex-to-css-filter.ts ├── index.ts └── solver.ts ├── tsconfig.eslint.json ├── tsconfig.json ├── tsconfig.spec.json └── yarn.lock /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | docker_defaults: &docker_defaults 4 | docker: 5 | - image: circleci/node:14.15.5-browsers 6 | working_directory: ~/project/repo 7 | 8 | attach_workspace: &attach_workspace 9 | attach_workspace: 10 | at: ~/project 11 | 12 | install_steps: &install_steps 13 | steps: 14 | - checkout 15 | - restore_cache: 16 | name: Restore node_modules cache 17 | keys: 18 | - dependency-cache-{{ .Branch }}-{{ checksum "package.json" }} 19 | - dependency-cache-{{ .Branch }}- 20 | - dependency-cache- 21 | - run: 22 | name: Installing Dependencies 23 | command: | 24 | yarn install --silent --frozen-lockfile 25 | - save_cache: 26 | name: Save node_modules cache 27 | key: dependency-cache-{{ .Branch }}-{{ checksum "yarn.lock" }} 28 | paths: 29 | - node_modules/ 30 | - persist_to_workspace: 31 | root: ~/project 32 | paths: 33 | - repo 34 | 35 | workflows: 36 | version: 2 37 | build_pipeline: 38 | jobs: 39 | - build 40 | - unit_test: 41 | requires: 42 | - build 43 | - bundle_size: 44 | requires: 45 | - build 46 | jobs: 47 | build: 48 | <<: *docker_defaults 49 | <<: *install_steps 50 | unit_test: 51 | <<: *docker_defaults 52 | steps: 53 | - *attach_workspace 54 | - run: 55 | name: Running unit tests 56 | command: | 57 | sudo yarn test:ci 58 | bundle_size: 59 | <<: *docker_defaults 60 | steps: 61 | - *attach_workspace 62 | - run: 63 | name: Checking bundle size 64 | command: | 65 | sudo yarn build 66 | sudo yarn bundlesize 67 | -------------------------------------------------------------------------------- /.coveralls.yml: -------------------------------------------------------------------------------- 1 | repo_token: t3pzmJK46PDJB3gcU9Q6Zk1iRZ7FYkZG5 2 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # http://editorconfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | # Change these settings to your own preference 9 | indent_style = space 10 | indent_size = 2 11 | 12 | # We recommend you to keep these unchanged 13 | end_of_line = lf 14 | charset = utf-8 15 | trim_trailing_whitespace = true 16 | insert_final_newline = true 17 | 18 | [*.md] 19 | trim_trailing_whitespace = false 20 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | .github 2 | .jest 3 | coverage 4 | lib 5 | node_modules 6 | flow-typed 7 | .yarn 8 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | es6: true, 5 | node: true, 6 | jest: true, 7 | }, 8 | extends: [ 9 | 'plugin:prettier/recommended', 10 | 'plugin:@typescript-eslint/eslint-recommended', 11 | 'plugin:@typescript-eslint/recommended', 12 | ], 13 | globals: { 14 | Atomics: 'readonly', 15 | SharedArrayBuffer: 'readonly', 16 | }, 17 | parser: '@typescript-eslint/parser', 18 | parserOptions: { 19 | ecmaVersion: 2018, 20 | sourceType: 'module', 21 | }, 22 | plugins: ['@typescript-eslint', 'prettier'], 23 | rules: { 24 | '@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }], 25 | }, 26 | }; 27 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Minimal example 2 | 3 | Add here an online minimal example and share the link here, if the case. 4 | 5 | ### Expected behaviour 6 | 7 | I thought that by going to the page '...' and do the action '...' then '...' would happen. 8 | 9 | ### Actual behaviour 10 | 11 | Instead of '...', what I saw was that '...' happened instead. 12 | 13 | ### How to simulate the behaviour (if applicable) 14 | 15 | This is the example of the current behaviour https://stackblitz.com/edit/hex-to-css-filter-playground 16 | 17 | ### Browsers/OS/Platforms affected 18 | 19 | Please specify the affected browsers/OS/platforms. 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | lib 2 | npm-debug.log 3 | .DS_Store 4 | node_modules 5 | coverage 6 | package 7 | .nyc_output 8 | dist 9 | .jest/ 10 | out-tsc/ 11 | *.js 12 | !rollup.config.js 13 | *.d.ts 14 | !jest.*.js 15 | !scripts/* 16 | *.tgz 17 | !.eslintrc.js 18 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | yarn lint-staged 5 | -------------------------------------------------------------------------------- /.lintstagedrc: -------------------------------------------------------------------------------- 1 | { 2 | "*.{js,ts}": [ 3 | "prettier --no-editorconfig --write" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /.*/ 2 | /demo/ 3 | .* 4 | bower.json 5 | karma.conf.js 6 | src 7 | coverage 8 | .nyc_output 9 | .jest 10 | **/__tests__/ 11 | out-tsc 12 | images 13 | scripts 14 | jest*.js 15 | tsconfig.*.json 16 | tsconfig.json 17 | .coveralls.yml 18 | .banner 19 | package 20 | *.tgz 21 | 22 | # ignore OS generated files 23 | **/.DS_Store 24 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v14.15.5 2 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": true, 3 | "printWidth": 120, 4 | "tabWidth": 2, 5 | "useTabs": false, 6 | "singleQuote": true, 7 | "trailingComma": "all", 8 | "bracketSpacing": true, 9 | "jsxBracketSameLine": false, 10 | "arrowParens": "avoid" 11 | } 12 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](http://keepachangelog.com/) 6 | and this project adheres to [Semantic Versioning](http://semver.org/). 7 | 8 | ## [Unreleased][] 9 | 10 | ## [6.0.0][] - 2025-03-11 11 | 12 | ### Fixed 13 | 14 | - Removing semicolon at the end of filter values. So that, consumers will be able to use its value in any order. E.G: 15 | 16 | After changes 17 | 18 | ```css 19 | /* ✅ Both approaches will work as expected 🎉 */ 20 | filter: var(--hex-to-css-filter) var(--blur-filter); 21 | filter: var(--blur-filter) var(--hex-to-css-filter); 22 | ``` 23 | 24 | Before changes 25 | 26 | ```css 27 | /* ❌ This order was NOT working before changes */ 28 | filter: var(--hex-to-css-filter) var(--blur-filter); 29 | /* ✅ This order was the only way of applying that */ 30 | filter: var(--blur-filter) var(--hex-to-css-filter); 31 | ``` 32 | 33 | 34 | 35 | ## [5.4.0][] - 2022-03-29 36 | 37 | ### Fixed 38 | 39 | - Fixing type distribution issue 40 | 41 | ## [5.3.0][] - 2022-03-03 42 | 43 | ### Fixed 44 | 45 | - Fixing build distribution issue during install 46 | 47 | ## [5.2.0][] - 2021-09-16 48 | 49 | ### Fixed 50 | 51 | - Fixing install command package calling `husky install` 52 | 53 | ## [5.1.0][] - 2021-08-08 54 | 55 | ### Fixed 56 | 57 | - Fixing issue when passing a key to be removed from cache that doesn't exist. Before the fix, if the consumer was passing a non-existent key, all the cached values were removed. Now, if a non-existent key is passed through the cache won't be changed 58 | 59 | ### Updated 60 | 61 | - Adding JSDocs for `clearCache` method 62 | - Adding `.lintstagedrc` configuration file 63 | - Applying changes to decrese bundle size to 2.2KB 🎉 64 | 65 | ## [5.0.0][] - 2021-08-07 66 | 67 | ### Updated 68 | 69 | - Updating NodeJS version to v14.15.5 70 | - Upgrading dependencies and devDependencies 71 | 72 | ### Added 73 | 74 | - Adding `clearCache` function to removed values from memory cache. It also gives the option of clear all cached values from memory. 75 | 76 | ```ts 77 | // Creating CSS filters for `#24639C` and `#FF0000` 78 | // They memory cache stored is based on the received hex value 79 | const [firstResult, secondResult, thirdResult, forthResult] = [ 80 | hexToCSSFilter('#24639C', { forceFilterRecalculation: false } as HexToCssConfiguration), 81 | hexToCSSFilter('#24639C', { forceFilterRecalculation: false } as HexToCssConfiguration), 82 | hexToCSSFilter('#FF0000', { forceFilterRecalculation: false } as HexToCssConfiguration), 83 | hexToCSSFilter('#FF0000', { forceFilterRecalculation: false } as HexToCssConfiguration), 84 | ]; 85 | 86 | // ... 87 | // ✨ Here is the place where the magic happens in your App ✨ 88 | // ... 89 | 90 | // Removing the memory cache only for `#24639C` 91 | // It means that `#FF0000` is still cached. 92 | // It's quite handy in scenarios of colors that are called for several times, 93 | // Having other ones called twice or thrice 94 | clearCache('#24639C'); 95 | 96 | // Or you can just remove all cached values from memory 97 | // by calling the function with no arguments 98 | clearCache(); 99 | 100 | // `fifthResult` and `sixthResult` will/won't be computed again based on `clearCache` usage 101 | const [fifthResult, sixthResult] = [ 102 | hexToCSSFilter('#24639C', { forceFilterRecalculation: false } as HexToCssConfiguration), 103 | hexToCSSFilter('#FF0000', { forceFilterRecalculation: false } as HexToCssConfiguration), 104 | ]; 105 | ``` 106 | 107 | ## [4.0.0][] - 2021-05-09 108 | 109 | ### Updated 110 | 111 | - Updating the project dependencies and devDependencies to the latest version 112 | - Decreasing package bundle size 113 | 114 | ## [3.1.2][] - 2020-07-24 115 | 116 | ### Fixed 117 | 118 | - Fixing UMD bundle by using Rollup. Typescript was required in the package and one of the TS functions is required in the bundle. 119 | 120 | ## [3.1.1][] - 2020-06-29 121 | 122 | ### Updated 123 | 124 | - Updating package dependencies and devDependencies to the latest 125 | 126 | ## [3.1.0][] - 2020-06-29 127 | 128 | ### Added 129 | 130 | - Adding boolean `cache` field into the `hexToCSSFilter()` payload; 131 | - Adding 132 | ynew configuration option: `forceFilterRecalculation`. It's a boolean value that forces recalculation for CSS filter generation. Default: `false`; 133 | 134 | ### Fixed 135 | 136 | - Fixing color generation using `maxTriesInLoop` to get the optimal color for the CSS filter 137 | 138 | ## [3.0.1][] - 2020-06-28 139 | 140 | ### Updated 141 | 142 | - Increasing `maxChecks` from 15 to 30 143 | - Adding private methods in classes 144 | - Improving internal types 145 | - Removing `any` from codebase 146 | 147 | ## [3.0.0][] - 2020-06-25 148 | 149 | ### Fixed 150 | 151 | - CSS Filter working properly when receives `#FFF` color; 152 | - Fixed internal issue on `hexToRgb` method when receiving `#FFF` and `#000` colors 153 | 154 | ### Updated 155 | 156 | - Breaking change: `HexToCssConfiguration` type now is using `acceptanceLossPercentage` instead of `acceptableLossPercentage` 157 | 158 | ``` 159 | - acceptableLossPercentage?: number; 160 | + acceptanceLossPercentage?: number; 161 | ``` 162 | 163 | - Better types for internal methods 164 | - Improving package documentation 165 | - Adding documentation for consumers to use `#000` as a container background on `README.md` 166 | 167 | ## [2.0.4][] - 2020-04-24 168 | 169 | ### Fixed 170 | 171 | - `Solver`: Changing default target color to be white or black, based on the 172 | given color. It solves the issue when a color is darker and the returned CSS filter resolutions is incorrect. 173 | 174 | E.G. https://codepen.io/willmendesneto/pen/pOVGVe 175 | 176 | With the issue 177 | Screen Shot 2020-04-24 at 3 30 48 pm 178 | 179 | Without the issue 180 | Screen Shot 2020-04-24 at 3 31 42 pm 181 | 182 | ## [2.0.3][] - 2020-04-24 183 | 184 | ### Fixed 185 | 186 | - Fixing bundle size 187 | - Setting the filter to white to take effect properly. Closes https://github.com/willmendesneto/hex-to-css-filter/issues/7 188 | 189 | Since `Solver` is forcing the stored instance of `color` to be white in rgb, the brightness should be white as well. That 190 | means the filter is based on white, so it needs to set the filter to white to take effect. 191 | 192 | https://github.com/willmendesneto/hex-to-css-filter/blob/996d0c78ba275b7c16ae3d87821dd044276db563/src/solver.ts#L136 193 | 194 | E.G. 195 | 196 | ```diff 197 | - filter: invert(39%) sepia(91%) saturate(4225%) hue-rotate(162deg) brightness(95%) contrast(101%); 198 | + filter: brightness(0) invert(1) invert(39%) sepia(91%) saturate(4225%) hue-rotate(162deg) brightness(95%) contrast(101%); 199 | ``` 200 | 201 | ## [2.0.2][] - 2020-04-09 202 | 203 | ### Updated 204 | 205 | - Updating description 206 | - Removing broken link 207 | 208 | ## [2.0.1][] - 2020-04-09 209 | 210 | ### Updated 211 | 212 | - Bumped dependencies 213 | - Upgraded NodeJS to 12.14.1 214 | - Updated README.md with proper docs 215 | 216 | ### Fixed 217 | 218 | - Fixed CircleCI pipeline 219 | - Fixed Uglify issue on build task 220 | - Fixed bundlesize task 221 | - Fixed ESLint issue after upgrade 222 | 223 | ## [2.0.0][] - 2020-01-09 224 | 225 | ### Updated 226 | 227 | - Migrating package to Typescript 228 | 229 | BREAKING CHANGE: 230 | 231 | To improve readability, these type definitions were renamed 232 | 233 | - `Option` was renamed to `HexToCssConfiguration`; 234 | - `ReturnValue` was renamed to `HexToCssResult`; 235 | 236 | ## [1.0.3][] - 2019-12-18 237 | 238 | ### Added 239 | 240 | - Adding typescript types for package 241 | 242 | ## [1.0.2][] - 2018-09-14 243 | 244 | ### Updated 245 | 246 | - Returning RGB as an array with red, green and blue values 247 | 248 | ## [1.0.1][] - 2018-09-13 249 | 250 | ### Added 251 | 252 | - First version of the package 253 | - Adding memory cache to store the computed result 254 | 255 | ### Updated 256 | 257 | - Minor code changes to decrese bundle size to 2.2KB 🎉 258 | - Using `Object.assign()` to return the best match object 259 | - Changing the color to check: from white to black 260 | 261 | ### Fixed 262 | 263 | - Fixing editorconfig code style 264 | 265 | [unreleased]: https://github.com/willmendesneto/hex-to-css-filter/compare/v1.0.2...HEAD 266 | [1.0.2]: https://github.com/willmendesneto/hex-to-css-filter/compare/v1.0.1...v1.0.2 267 | [1.0.1]: https://github.com/willmendesneto/hex-to-css-filter/tree/v1.0.1 268 | [unreleased]: https://github.com/willmendesneto/hex-to-css-filter/compare/v1.0.3...HEAD 269 | [1.0.3]: https://github.com/willmendesneto/hex-to-css-filter/tree/v1.0.3 270 | [unreleased]: https://github.com/willmendesneto/hex-to-css-filter/compare/v2.0.0...HEAD 271 | [2.0.0]: https://github.com/willmendesneto/hex-to-css-filter/tree/v2.0.0 272 | [unreleased]: https://github.com/willmendesneto/hex-to-css-filter/compare/v2.0.2...HEAD 273 | [2.0.2]: https://github.com/willmendesneto/hex-to-css-filter/compare/v2.0.1...v2.0.2 274 | [2.0.1]: https://github.com/willmendesneto/hex-to-css-filter/tree/v2.0.1 275 | [unreleased]: https://github.com/willmendesneto/hex-to-css-filter/compare/v2.0.3...HEAD 276 | [2.0.3]: https://github.com/willmendesneto/hex-to-css-filter/tree/v2.0.3 277 | [unreleased]: https://github.com/willmendesneto/hex-to-css-filter/compare/v2.0.4...HEAD 278 | [2.0.4]: https://github.com/willmendesneto/hex-to-css-filter/tree/v2.0.4 279 | [unreleased]: https://github.com/willmendesneto/hex-to-css-filter/compare/v3.0.0...HEAD 280 | [3.0.0]: https://github.com/willmendesneto/hex-to-css-filter/tree/v3.0.0 281 | [unreleased]: https://github.com/willmendesneto/hex-to-css-filter/compare/v3.0.1...HEAD 282 | [3.0.1]: https://github.com/willmendesneto/hex-to-css-filter/tree/v3.0.1 283 | [unreleased]: https://github.com/willmendesneto/hex-to-css-filter/compare/v3.1.0...HEAD 284 | [3.1.0]: https://github.com/willmendesneto/hex-to-css-filter/tree/v3.1.0 285 | [unreleased]: https://github.com/willmendesneto/hex-to-css-filter/compare/v3.1.1...HEAD 286 | [3.1.1]: https://github.com/willmendesneto/hex-to-css-filter/tree/v3.1.1 287 | [unreleased]: https://github.com/willmendesneto/hex-to-css-filter/compare/v3.1.2...HEAD 288 | [3.1.2]: https://github.com/willmendesneto/hex-to-css-filter/tree/v3.1.2 289 | [unreleased]: https://github.com/willmendesneto/hex-to-css-filter/compare/v4.0.0...HEAD 290 | [4.0.0]: https://github.com/willmendesneto/hex-to-css-filter/tree/v4.0.0 291 | [unreleased]: https://github.com/willmendesneto/hex-to-css-filter/compare/v5.0.0...HEAD 292 | [5.0.0]: https://github.com/willmendesneto/hex-to-css-filter/tree/v5.0.0 293 | [unreleased]: https://github.com/willmendesneto/hex-to-css-filter/compare/v5.1.0...HEAD 294 | [5.1.0]: https://github.com/willmendesneto/hex-to-css-filter/tree/v5.1.0 295 | [unreleased]: https://github.com/willmendesneto/hex-to-css-filter/compare/v5.2.0...HEAD 296 | [5.2.0]: https://github.com/willmendesneto/hex-to-css-filter/tree/v5.2.0 297 | [unreleased]: https://github.com/willmendesneto/hex-to-css-filter/compare/v5.3.0...HEAD 298 | [5.3.0]: https://github.com/willmendesneto/hex-to-css-filter/tree/v5.3.0 299 | 300 | 301 | [Unreleased]: https://github.com/willmendesneto/hex-to-css-filter/compare/v6.0.0...HEAD 302 | [6.0.0]: https://github.com/willmendesneto/hex-to-css-filter/compare/v5.4.0...v6.0.0 303 | [5.4.0]: https://github.com/willmendesneto/hex-to-css-filter/tree/v5.4.0 -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Will Mendes 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # hex-to-css-filter 2 | 3 | [![Greenkeeper badge](https://badges.greenkeeper.io/willmendesneto/hex-to-css-filter.svg)](https://greenkeeper.io/) 4 | [![npm](https://img.shields.io/badge/stackblitz-online-orange.svg)](https://stackblitz.com/edit/hex-to-css-filter-playground) 5 | 6 | [![npm version](https://badge.fury.io/js/hex-to-css-filter.svg)](http://badge.fury.io/js/hex-to-css-filter) [![npm downloads](https://img.shields.io/npm/dm/hex-to-css-filter.svg)](https://npmjs.org/hex-to-css-filter) 7 | [![MIT License](https://img.shields.io/badge/license-MIT%20License-blue.svg?style=flat-square)](LICENSE) 8 | 9 | [![Build Status](https://circleci.com/gh/willmendesneto/hex-to-css-filter.svg?style=shield)](https://circleci.com/gh/willmendesneto/hex-to-css-filter) 10 | [![Coverage Status](https://coveralls.io/repos/willmendesneto/hex-to-css-filter/badge.svg?branch=master)](https://coveralls.io/r/willmendesneto/hex-to-css-filter?branch=master) 11 | [![Dependency Status](https://david-dm.org/willmendesneto/hex-to-css-filter.svg)](https://david-dm.org/willmendesneto/hex-to-css-filter) 12 | 13 | [![NPM](https://nodei.co/npm/hex-to-css-filter.png?downloads=true&downloadRank=true&stars=true)](https://npmjs.org/hex-to-css-filter) 14 | [![NPM](https://nodei.co/npm-dl/hex-to-css-filter.png?height=3&months=3)](https://npmjs.org/hex-to-css-filter) 15 | 16 | > Easy way to generate colors from HEX to CSS Filters 😎 17 | 18 | ## Contributing 19 | 20 | Please check our [contributing.md](https://github.com/willmendesneto/hex-to-css-filter/blob/master/contributing.md) to know more about setup and how to contribute. 21 | 22 | ## Setup and installation 23 | 24 | Make sure that you are using the NodeJS version is the same as `.nvmrc` file version. If you don't have this version please use a version manager such as `nvm` or `n` to manage your local nodejs versions. 25 | 26 | > Please make sure that you are using NodeJS version 6.10.2 27 | 28 | Assuming that you are using `nvm`, please run the commands inside this folder: 29 | 30 | ```bash 31 | $ nvm install $(cat .nvmrc); # install required nodejs version 32 | $ nvm use $(cat .nvmrc); # use nodejs version 33 | ``` 34 | 35 | In Windows, please install NodeJS using one of these options: 36 | 37 | Via `NVM Windows` package: Dowload via [this link](https://github.com/coreybutler/nvm-windows). After that, run the commands: 38 | 39 | ```bash 40 | $ nvm install $(cat .nvmrc); # install required nodejs version 41 | $ nvm use $(cat .nvmrc); # use nodejs version 42 | ``` 43 | 44 | Via Chocolatey: 45 | 46 | ```bash 47 | $ choco install nodejs.install -version 6.10.2 48 | ``` 49 | 50 | ### Install yarn 51 | 52 | We use `yarn` as our package manager instead of `npm` 53 | 54 | [Install it following these steps](https://yarnpkg.com/lang/en/docs/install/#mac-tab) 55 | 56 | After that, just navigate to your local repository and run 57 | 58 | ```bash 59 | $ yarn install && yarn husky:install 60 | ``` 61 | 62 | ## Demo 63 | 64 | Try out our [demo on Stackblitz](https://hex-to-css-filter-playground.stackblitz.io)! 65 | 66 | ### Run the tests 67 | 68 | ```bash 69 | $ yarn test # run the tests 70 | ``` 71 | 72 | ### Run the build 73 | 74 | ```bash 75 | $ yarn build # run the tests 76 | ``` 77 | 78 | ### Run the bundlesize check 79 | 80 | ```bash 81 | $ yarn bundlesize # run the tests 82 | ``` 83 | 84 | ### Run the code lint 85 | 86 | ```bash 87 | $ yarn lint # run the tests 88 | ``` 89 | 90 | ## Usage 91 | 92 | ### Important!!!! 93 | 94 | _Please make sure the background of the element is `#000` for better performance and color similarity_. 95 | 96 | The reason for this is because all the calcs done by the library to generate a CSS Filter are based on the color `#000` 97 | 98 | ### Using default options 99 | 100 | ```js 101 | import { hexToCSSFilter } from 'hex-to-css-filter'; 102 | 103 | const cssFilter = hexToCSSFilter('#00a4d6'); 104 | console.log(cssFilter); 105 | ``` 106 | 107 | ### Overriding default options 108 | 109 | You can override the default options by passing a second parameter into `hexToCSSFilter` method. You can also use `HexToCssConfiguration` for type support on it. 110 | 111 | ```ts 112 | import { hexToCSSFilter, HexToCssConfiguration } from 'hex-to-css-filter'; 113 | 114 | const config: HexToCssConfiguration = { 115 | acceptanceLossPercentage: 1, 116 | maxChecks: 10, 117 | }; 118 | 119 | const cssFilter = hexToCSSFilter('#00a4d6', config); 120 | console.log(cssFilter); 121 | 122 | // Calling different colors to create CSS Filters 123 | [ 124 | hexToCSSFilter('#FFF'), 125 | hexToCSSFilter('#000'), 126 | hexToCSSFilter('#802e1c'), 127 | hexToCSSFilter('#00a4d6'), 128 | hexToCSSFilter('#FF0000'), 129 | hexToCSSFilter('#173F5E'), 130 | hexToCSSFilter('#24639C'), 131 | hexToCSSFilter('#3CAEA4'), 132 | hexToCSSFilter('#F6D55C'), 133 | hexToCSSFilter('#ED553C'), 134 | ].forEach(cssFilter => { 135 | console.log(`\n${cssFilter.hex}-[${cssFilter.rgb}]: ${cssFilter.filter}`); 136 | }); 137 | ``` 138 | 139 | It returns an object with the values: 140 | 141 | - `cache`: returns a boolean to confirm if value was previously computed and is coming from local memory cache or not; 142 | - `called`: how many times the script was called to solve the color; 143 | - `filter`: CSS filter generated based on the HEX color; 144 | - `hex`: the received color; 145 | - `loss`: percentage loss value for the generated filter; 146 | - `rgb`: HEX color in RGB; 147 | - `values`: percentage loss per each color type organized in RGB: `red`, `green`, `blue`, `h`, `s`, `l`. Used for debug purposes - if needed; 148 | 149 | ### Options 150 | 151 | - `acceptanceLossPercentage`: Acceptable color percentage to be lost during wide search. Does not guarantee `loss`. Default: `5`; 152 | - `maxChecks`: Maximum checks that needs to be done to return the best value. Default: `10`; 153 | - `forceFilterRecalculation`: Boolean value that forces recalculation for CSS filter generation. Default: `false`; 154 | 155 | ### Removing memory cache 156 | 157 | In some cases the memory cache is quite handy. However, it doesn't need to stored after called in some cases. If you're using it in some frontend libraries/frameworks, have that in memory can become an issue. 158 | 159 | In order to solve that, you can now use the function `clearCache` to remove the memory cache. The method can receive the stored hex color. In this case, _only the received key will be removed_. E.G. 160 | 161 | ```ts 162 | // Creating CSS filters for `#24639C` and `#FF0000` 163 | // They memory cache stored is based on the received hex value 164 | const [firstResult, secondResult, thirdResult, forthResult] = [ 165 | hexToCSSFilter('#24639C', { forceFilterRecalculation: false } as HexToCssConfiguration), 166 | hexToCSSFilter('#24639C', { forceFilterRecalculation: false } as HexToCssConfiguration), 167 | hexToCSSFilter('#FF0000', { forceFilterRecalculation: false } as HexToCssConfiguration), 168 | hexToCSSFilter('#FF0000', { forceFilterRecalculation: false } as HexToCssConfiguration), 169 | ]; 170 | 171 | // ... 172 | // ✨ Here is the place where the magic happens in your App ✨ 173 | // ... 174 | 175 | // Removing the memory cache only for `#24639C` 176 | // It means that `#FF0000` is still cached. 177 | // It's quite handy in scenarios of colors that are called for several times, 178 | // Having other ones called twice or thrice 179 | clearCache('#24639C'); 180 | 181 | // `fifthResult` will be computed again, since there's no cache 182 | // `sixthResult` won't be computed because of the existent memory cache for the value 183 | const [fifthResult, sixthResult] = [ 184 | hexToCSSFilter('#24639C', { forceFilterRecalculation: false } as HexToCssConfiguration), 185 | hexToCSSFilter('#FF0000', { forceFilterRecalculation: false } as HexToCssConfiguration), 186 | ]; 187 | ``` 188 | 189 | Also, it covers the scenario of removing all the cache by calling the function with no arguments. E.G. 190 | 191 | ```ts 192 | // Creating CSS filters for `#24639C` and `#FF0000` 193 | // They memory cache stored is based on the received hex value 194 | const [firstResult, secondResult, thirdResult, forthResult] = [ 195 | hexToCSSFilter('#24639C', { forceFilterRecalculation: false } as HexToCssConfiguration), 196 | hexToCSSFilter('#24639C', { forceFilterRecalculation: false } as HexToCssConfiguration), 197 | hexToCSSFilter('#FF0000', { forceFilterRecalculation: false } as HexToCssConfiguration), 198 | hexToCSSFilter('#FF0000', { forceFilterRecalculation: false } as HexToCssConfiguration), 199 | ]; 200 | 201 | // ... 202 | // ✨ Here is the place where the magic happens in your App ✨ 203 | // ... 204 | 205 | // Removing all cached values from memory 206 | clearCache(); 207 | 208 | // `fifthResult` and `sixthResult` will be computed again, since there's no cache 209 | const [fifthResult, sixthResult] = [ 210 | hexToCSSFilter('#24639C', { forceFilterRecalculation: false } as HexToCssConfiguration), 211 | hexToCSSFilter('#FF0000', { forceFilterRecalculation: false } as HexToCssConfiguration), 212 | ]; 213 | ``` 214 | 215 | ## Publish 216 | 217 | this project is using `np` package to publish, which makes things straightforward. EX: `np ` 218 | 219 | > For more details, [please check np package on npmjs.com](https://www.npmjs.com/package/np) 220 | 221 | ## Author 222 | 223 | **Wilson Mendes (willmendesneto)** 224 | 225 | - 226 | - 227 | -------------------------------------------------------------------------------- /bundlesize.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | { 4 | "path": "./dist/esm/index.js", 5 | "maxSize": "57B" 6 | }, 7 | { 8 | "path": "./dist/es2015/index.js", 9 | "maxSize": "57B" 10 | }, 11 | { 12 | "path": "./dist/cjs/index.js", 13 | "maxSize": "154B" 14 | }, 15 | { 16 | "path": "./dist/umd/hex-to-css-filter.js", 17 | "maxSize": "5.2KB" 18 | }, 19 | { 20 | "path": "./dist/umd/hex-to-css-filter.min.js", 21 | "maxSize": "2.3KB" 22 | } 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /contributing.md: -------------------------------------------------------------------------------- 1 | # Contributing guide 2 | 3 | Want to contribute to hex-to-css-filter package? Awesome! 4 | There are many ways you can contribute, see below. 5 | 6 | ## Opening issues 7 | 8 | Open an issue to report bugs or to propose new features. 9 | 10 | - Reporting bugs: describe the bug as clearly as you can, including steps to reproduce, what happened and what you were expecting to happen. Also include browser version, OS and other related software's (npm, Node.js, etc) versions when applicable. 11 | 12 | - Proposing features: explain the proposed feature, what it should do, why it is useful, how users should use it. Give us as much info as possible so it will be easier to discuss, access and implement the proposed feature. When you're unsure about a certain aspect of the feature, feel free to leave it open for others to discuss and find an appropriate solution. In case of a specific scenario, please recreate that in our Stackblitz demo (http://stackblitz.com/edit/hex-to-css-filter-playground) and share the link. This will help in make the fix as quick as possible 13 | 14 | ## Proposing pull requests 15 | 16 | Pull requests are very welcome. Note that if you are going to propose drastic changes, be sure to open an issue for discussion first, to make sure that your PR will be accepted before you spend effort coding it. 17 | 18 | Fork the hex-to-css-filter repository, clone it locally and create a branch for your proposed bug fix or new feature. Avoid working directly on the master branch. 19 | 20 | Implement your bug fix or feature, write tests to cover it and make sure all tests are passing (run a final `npm test` to make sure everything is correct). Then commit your changes, push your bug fix/feature branch to the origin (your forked repo) and open a pull request to the upstream (the repository you originally forked)'s master branch. 21 | 22 | ## Documentation 23 | 24 | Documentation is extremely important and takes a fair deal of time and effort to write and keep updated. Please submit any and all improvements you can make to the repository's docs. 25 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | globals: { 3 | 'ts-jest': { 4 | tsConfig: './tsconfig.spec.json', 5 | }, 6 | }, 7 | automock: false, 8 | cacheDirectory: '/.jest', 9 | collectCoverage: true, 10 | roots: ['src'], 11 | collectCoverageFrom: ['**/src/*.ts', '!src/index.ts'], 12 | coverageThreshold: { 13 | global: { 14 | branches: 80, 15 | functions: 92, 16 | lines: 96, 17 | statements: 94, 18 | }, 19 | }, 20 | preset: 'ts-jest', 21 | testEnvironment: 'node', 22 | testPathIgnorePatterns: ['/node_modules/', '/scripts/', '/dist/'], 23 | setupFilesAfterEnv: ['/jest.setup.js'], 24 | }; 25 | -------------------------------------------------------------------------------- /jest.setup.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line no-undef 2 | const { JSDOM } = require('jsdom'); 3 | 4 | const jsdom = new JSDOM(''); 5 | const { window } = jsdom; 6 | 7 | global.window = window; 8 | global.document = window.document; 9 | global.navigator = { 10 | userAgent: 'node.js', 11 | }; 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hex-to-css-filter", 3 | "version": "6.0.0", 4 | "description": "hex-to-css-filter - Easy way to generate colors from HEX to CSS Filters", 5 | "author": "Will Mendes ", 6 | "repository": { 7 | "type": "git", 8 | "url": "git+https://github.com/willmendesneto/hex-to-css-filter.git" 9 | }, 10 | "bugs": { 11 | "url": "https://github.com/willmendesneto/hex-to-css-filter/issues" 12 | }, 13 | "homepage": "https://github.com/willmendesneto/hex-to-css-filter#readme", 14 | "sideEffects": false, 15 | "license": "MIT", 16 | "browser": "dist/umd/hex-to-css-filter.js", 17 | "jsnext:main": "dist/esm/index.js", 18 | "module": "dist/esm/index.js", 19 | "main": "dist/cjs/index.js", 20 | "es2015": "dist/cjs/index.js", 21 | "cjs": "dist/cjs/index.js", 22 | "types": "dist/umd/index.d.ts", 23 | "keywords": [ 24 | "hex-to-css-filter", 25 | "css", 26 | "filter", 27 | "hex", 28 | "color" 29 | ], 30 | "dependencies": { 31 | "tslib": "^2.3.0" 32 | }, 33 | "devDependencies": { 34 | "@types/jest": "^26.0.24", 35 | "@types/node": "^14.0.27", 36 | "@typescript-eslint/eslint-plugin": "^4.29.0", 37 | "@typescript-eslint/parser": "^4.29.0", 38 | "bundlesize": "^0.18.0", 39 | "changelog-verify": "^1.1.0", 40 | "coveralls": "^3.1.1", 41 | "eslint": "^7.32.0", 42 | "eslint-config-prettier": "^8.3.0", 43 | "eslint-plugin-compat": "^3.11.1", 44 | "eslint-plugin-prettier": "^3.1.1", 45 | "husky": "^7.0.1", 46 | "jest": "^27.0.6", 47 | "jsdom": "^16.7.0", 48 | "lint-staged": "^11.1.2", 49 | "prettier": "^2.3.2", 50 | "rollup": "^2.56.0", 51 | "rollup-plugin-dts": "^4.2.0", 52 | "rollup-plugin-node-resolve": "^5.2.0", 53 | "ts-jest": "^27.0.4", 54 | "ts-node": "^10.1.0", 55 | "typescript": "^4.3.5", 56 | "typings": "^2.1.1", 57 | "uglify-js": "^3.14.1", 58 | "version-changelog": "^3.1.0" 59 | }, 60 | "engines": { 61 | "node": ">=6.10.2" 62 | }, 63 | "scripts": { 64 | "prepare": "husky install", 65 | "compile": "tsc", 66 | "clean": "rm -rf ./dist ./.jest ./coverage ./lib", 67 | "build": "yarn build:es2015 && yarn build:cjs && yarn build:esm && yarn build:umd", 68 | "build:umd": "rollup --config && yarn build:umd:min", 69 | "build:umd:min": "uglifyjs --compress --mangle --comments -o dist/umd/hex-to-css-filter.min.js -- dist/umd/hex-to-css-filter.js && gzip dist/umd/hex-to-css-filter.min.js -c > dist/umd/hex-to-css-filter.min.js.gz", 70 | "build:es2015": "tsc --module es2015 --target es2015 --outDir dist/es2015", 71 | "build:esm": "tsc --module esnext --target es5 --outDir dist/esm", 72 | "build:cjs": "tsc --module commonjs --target es5 --outDir dist/cjs", 73 | "test": "jest", 74 | "pretest:ci": "yarn lint", 75 | "test:ci": "jest --coverage --coverageReporters=text-lcov | coveralls", 76 | "check-coverage": "cat ./coverage/lcov.info | ./node_modules/.bin/coveralls && rm -rf coverage", 77 | "bundlesize": "bundlesize --config bundlesize.config.json", 78 | "lint": "eslint '{scripts,src}/**/*.[tj]s'", 79 | "lint:fix": "prettier --no-editorconfig --write", 80 | "version": "version-changelog CHANGELOG.md && changelog-verify CHANGELOG.md && git add CHANGELOG.md" 81 | }, 82 | "browserslist": [ 83 | "last 1 chrome versions", 84 | "last 1 edge versions", 85 | "last 1 firefox versions", 86 | "last 1 safari versions", 87 | "last 1 and_chr versions", 88 | "last 1 ios_saf versions" 89 | ] 90 | } 91 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import resolve from 'rollup-plugin-node-resolve'; 2 | import dts from 'rollup-plugin-dts'; 3 | 4 | import { browser, module } from './package.json'; 5 | 6 | const config = [ 7 | { 8 | input: module, 9 | output: { 10 | file: browser, 11 | format: 'umd', 12 | name: 'HexToCSSFilter', 13 | }, 14 | plugins: [resolve()], 15 | }, 16 | { 17 | input: './dist/esm/index.d.ts', 18 | output: [{ file: 'dist/umd/index.d.ts', format: 'es' }], 19 | plugins: [dts()], 20 | }, 21 | ]; 22 | 23 | export default config; 24 | -------------------------------------------------------------------------------- /src/__tests__/hex-to-css-filter.ts: -------------------------------------------------------------------------------- 1 | import { clearCache } from '../hex-to-css-filter'; 2 | import { hexToCSSFilter, HexToCssConfiguration } from '../index'; 3 | 4 | describe('hexToCSSFilter', () => { 5 | beforeEach(() => clearCache()); 6 | 7 | it('loss should NOT be more than the default acceptance loss percentage', () => { 8 | expect(hexToCSSFilter('#00a4d6').loss <= 10).toBe(true); 9 | }); 10 | 11 | it('should clear all memory cache if `clearCache` is called with no arguments', () => { 12 | const [firstResult, secondResult, thirdResult, forthResult] = [ 13 | hexToCSSFilter('#24639C', { forceFilterRecalculation: false } as HexToCssConfiguration), 14 | hexToCSSFilter('#24639C', { forceFilterRecalculation: false } as HexToCssConfiguration), 15 | hexToCSSFilter('#FF0000', { forceFilterRecalculation: false } as HexToCssConfiguration), 16 | hexToCSSFilter('#FF0000', { forceFilterRecalculation: false } as HexToCssConfiguration), 17 | ].map(({ cache: _cache, ...rest }) => rest); 18 | 19 | expect(firstResult).toEqual(secondResult); 20 | expect(thirdResult).toEqual(forthResult); 21 | 22 | clearCache(); 23 | 24 | const [fifthResult, sixthResult] = [ 25 | hexToCSSFilter('#24639C', { forceFilterRecalculation: false } as HexToCssConfiguration), 26 | hexToCSSFilter('#FF0000', { forceFilterRecalculation: false } as HexToCssConfiguration), 27 | ].map(({ cache: _cache, ...rest }) => rest); 28 | 29 | expect(fifthResult).not.toEqual(firstResult); 30 | expect(fifthResult).not.toEqual(secondResult); 31 | expect(sixthResult).not.toEqual(thirdResult); 32 | expect(sixthResult).not.toEqual(forthResult); 33 | }); 34 | 35 | it('should keep memory cache as it is if `clearCache` receives value that does NOT exist in the cache', () => { 36 | const [firstResult, secondResult] = [ 37 | hexToCSSFilter('#24639C', { forceFilterRecalculation: false } as HexToCssConfiguration), 38 | hexToCSSFilter('#24639C', { forceFilterRecalculation: false } as HexToCssConfiguration), 39 | ].map(({ cache: _cache, ...rest }) => rest); 40 | 41 | expect(firstResult).toEqual(secondResult); 42 | 43 | clearCache('#FF0000'); 44 | 45 | const [thirdResult] = [hexToCSSFilter('#24639C', { forceFilterRecalculation: false } as HexToCssConfiguration)].map( 46 | ({ cache: _cache, ...rest }) => rest, 47 | ); 48 | 49 | expect(firstResult).toEqual(secondResult); 50 | expect(firstResult).toEqual(thirdResult); 51 | }); 52 | 53 | it('should clear memory cache only for received argument if `clearCache` is called with arguments', () => { 54 | const [firstResult, secondResult, thirdResult, forthResult] = [ 55 | hexToCSSFilter('#24639C', { forceFilterRecalculation: false } as HexToCssConfiguration), 56 | hexToCSSFilter('#24639C', { forceFilterRecalculation: false } as HexToCssConfiguration), 57 | hexToCSSFilter('#FF0000', { forceFilterRecalculation: false } as HexToCssConfiguration), 58 | hexToCSSFilter('#FF0000', { forceFilterRecalculation: false } as HexToCssConfiguration), 59 | ].map(({ cache: _cache, ...rest }) => rest); 60 | 61 | expect(firstResult).toEqual(secondResult); 62 | expect(thirdResult).toEqual(forthResult); 63 | 64 | clearCache('#24639C'); 65 | 66 | const [fifthResult, sixthResult] = [ 67 | hexToCSSFilter('#24639C', { forceFilterRecalculation: false } as HexToCssConfiguration), 68 | hexToCSSFilter('#FF0000', { forceFilterRecalculation: false } as HexToCssConfiguration), 69 | ].map(({ cache: _cache, ...rest }) => rest); 70 | 71 | expect(fifthResult).not.toEqual(firstResult); 72 | expect(fifthResult).not.toEqual(secondResult); 73 | expect(sixthResult).toEqual(thirdResult); 74 | expect(sixthResult).toEqual(forthResult); 75 | }); 76 | 77 | it('should use cache if `forceFilterRecalculation` is falsy via method configuration or is not configured', () => { 78 | expect(hexToCSSFilter('#24639C').cache).toBe(false); 79 | expect(hexToCSSFilter('#24639C', { forceFilterRecalculation: false } as HexToCssConfiguration).cache).toBe(true); 80 | expect(hexToCSSFilter('#24639C').cache).toBe(true); 81 | }); 82 | 83 | it('should NOT use cache if `forceFilterRecalculation` is passed as `true` via method configuration', () => { 84 | expect(hexToCSSFilter('#F6D55C', { forceFilterRecalculation: true } as HexToCssConfiguration).cache).toBe(false); 85 | expect(hexToCSSFilter('#F6D55C', { forceFilterRecalculation: true } as HexToCssConfiguration).cache).toBe(false); 86 | expect(hexToCSSFilter('#F6D55C', { forceFilterRecalculation: true } as HexToCssConfiguration).cache).toBe(false); 87 | }); 88 | 89 | it('loss should NOT check more than the default maximum value to check', () => { 90 | expect(hexToCSSFilter('#00a4d6').called <= 10).toBe(true); 91 | }); 92 | 93 | it('should return RGB colors as list of values', () => { 94 | expect(hexToCSSFilter('#FF0000').rgb).toEqual([255, 0, 0]); 95 | }); 96 | 97 | it('should work if receives a short HEX color', () => { 98 | expect(hexToCSSFilter('#000').hex).toBe('#000'); 99 | }); 100 | 101 | it('should return the same value as received in `hex` attribute', () => { 102 | expect(hexToCSSFilter('#FF0000').hex).toBe('#FF0000'); 103 | }); 104 | 105 | it('should throw an error if it receives an invalid color', () => { 106 | expect(() => hexToCSSFilter('invalid')).toThrowError(/Color value should be in HEX format/); 107 | // invalid color with more than 7 characters (one of the rules to get full HEX colors #000000) 108 | expect(() => hexToCSSFilter('invalid-value')).toThrowError(/Color value should be in HEX format/); 109 | }); 110 | 111 | it('should return an object with the given values', () => { 112 | expect(Object.keys(hexToCSSFilter('#00a4d6')).sort()).toEqual([ 113 | 'cache', 114 | 'called', 115 | 'filter', 116 | 'hex', 117 | 'loss', 118 | 'rgb', 119 | 'values', 120 | ]); 121 | }); 122 | 123 | it('should return an object with the given CSS Filter values', () => { 124 | const { filter } = hexToCSSFilter('#00a4d6'); 125 | expect(filter.split(' ').length).toEqual(6); 126 | expect(filter.includes('invert')).toBe(true); 127 | expect(filter.includes('sepia')).toBe(true); 128 | expect(filter.includes('saturate')).toBe(true); 129 | expect(filter.includes('hue-rotate')).toBe(true); 130 | expect(filter.includes('brightness')).toBe(true); 131 | expect(filter.includes('contrast')).toBe(true); 132 | expect(filter.includes(';')).toBe(false); 133 | }); 134 | 135 | describe('When it receives options', () => { 136 | it('loss should NOT be more than the given acceptance loss percentage OR should be more AND was called at allowed maxChecks', () => { 137 | const res = hexToCSSFilter('#ED553C', { acceptanceLossPercentage: 1, maxChecks: 5 } as HexToCssConfiguration); 138 | expect(res.loss <= 1 || (res.loss > 1 && res.called === 5)).toBe(true); 139 | }); 140 | 141 | it('loss should NOT check more than the given maximum value to check', () => { 142 | expect( 143 | hexToCSSFilter('#F6C6CE', { acceptanceLossPercentage: 0.01, maxChecks: 1 } as HexToCssConfiguration).called < 2, 144 | ).toBe(true); 145 | }); 146 | }); 147 | }); 148 | -------------------------------------------------------------------------------- /src/color.ts: -------------------------------------------------------------------------------- 1 | interface HSLData { 2 | h: number; 3 | s: number; 4 | l: number; 5 | } 6 | 7 | class Color { 8 | r = 0; 9 | g = 0; 10 | b = 0; 11 | 12 | constructor(r: number, g: number, b: number) { 13 | this.set(r, g, b); 14 | } 15 | 16 | set(r: number, g: number, b: number): void { 17 | this.r = this.clamp(r); 18 | this.g = this.clamp(g); 19 | this.b = this.clamp(b); 20 | } 21 | 22 | /** 23 | * Applying cals to get CSS filter for hue-rotate 24 | * 25 | * @param {number} [angle=0] 26 | * @memberof Color 27 | */ 28 | hueRotate(angle = 0): void { 29 | angle = (angle / 180) * Math.PI; 30 | const sin = Math.sin(angle); 31 | const cos = Math.cos(angle); 32 | 33 | this.multiply([ 34 | 0.213 + cos * 0.787 - sin * 0.213, 35 | 0.715 - cos * 0.715 - sin * 0.715, 36 | 0.072 - cos * 0.072 + sin * 0.928, 37 | 0.213 - cos * 0.213 + sin * 0.143, 38 | 0.715 + cos * 0.285 + sin * 0.14, 39 | 0.072 - cos * 0.072 - sin * 0.283, 40 | 0.213 - cos * 0.213 - sin * 0.787, 41 | 0.715 - cos * 0.715 + sin * 0.715, 42 | 0.072 + cos * 0.928 + sin * 0.072, 43 | ]); 44 | } 45 | 46 | /** 47 | * Applying cals to get CSS filter for grayscale 48 | * 49 | * @param {number} [value=1] 50 | * @memberof Color 51 | */ 52 | grayscale(value = 1): void { 53 | this.multiply([ 54 | 0.2126 + 0.7874 * (1 - value), 55 | 0.7152 - 0.7152 * (1 - value), 56 | 0.0722 - 0.0722 * (1 - value), 57 | 0.2126 - 0.2126 * (1 - value), 58 | 0.7152 + 0.2848 * (1 - value), 59 | 0.0722 - 0.0722 * (1 - value), 60 | 0.2126 - 0.2126 * (1 - value), 61 | 0.7152 - 0.7152 * (1 - value), 62 | 0.0722 + 0.9278 * (1 - value), 63 | ]); 64 | } 65 | 66 | /** 67 | * Applying cals to get CSS filter for sepia 68 | * 69 | * @param {number} [value=1] 70 | * @memberof Color 71 | */ 72 | sepia(value = 1): void { 73 | this.multiply([ 74 | 0.393 + 0.607 * (1 - value), 75 | 0.769 - 0.769 * (1 - value), 76 | 0.189 - 0.189 * (1 - value), 77 | 0.349 - 0.349 * (1 - value), 78 | 0.686 + 0.314 * (1 - value), 79 | 0.168 - 0.168 * (1 - value), 80 | 0.272 - 0.272 * (1 - value), 81 | 0.534 - 0.534 * (1 - value), 82 | 0.131 + 0.869 * (1 - value), 83 | ]); 84 | } 85 | 86 | /** 87 | * Applying cals to get CSS filter for saturate 88 | * 89 | * @param {number} [value=1] 90 | * @memberof Color 91 | */ 92 | saturate(value = 1): void { 93 | this.multiply([ 94 | 0.213 + 0.787 * value, 95 | 0.715 - 0.715 * value, 96 | 0.072 - 0.072 * value, 97 | 0.213 - 0.213 * value, 98 | 0.715 + 0.285 * value, 99 | 0.072 - 0.072 * value, 100 | 0.213 - 0.213 * value, 101 | 0.715 - 0.715 * value, 102 | 0.072 + 0.928 * value, 103 | ]); 104 | } 105 | 106 | private multiply(matrix: number[]): void { 107 | // These values are needed. It's correct because the returned values will change 108 | const newR = this.clamp(this.r * matrix[0] + this.g * matrix[1] + this.b * matrix[2]); 109 | const newG = this.clamp(this.r * matrix[3] + this.g * matrix[4] + this.b * matrix[5]); 110 | const newB = this.clamp(this.r * matrix[6] + this.g * matrix[7] + this.b * matrix[8]); 111 | this.r = newR; 112 | this.g = newG; 113 | this.b = newB; 114 | } 115 | 116 | /** 117 | * Applying cals to get CSS filter for brightness 118 | * 119 | * @param {number} [value=1] 120 | * @memberof Color 121 | */ 122 | brightness(value = 1): void { 123 | this.linear(value); 124 | } 125 | 126 | /** 127 | * Applying cals to get CSS filter for contrast 128 | * 129 | * @param {number} [value=1] 130 | * @memberof Color 131 | */ 132 | contrast(value = 1): void { 133 | this.linear(value, -(0.5 * value) + 0.5); 134 | } 135 | 136 | private linear(slope = 1, intercept = 0) { 137 | this.r = this.clamp(this.r * slope + intercept * 255); 138 | this.g = this.clamp(this.g * slope + intercept * 255); 139 | this.b = this.clamp(this.b * slope + intercept * 255); 140 | } 141 | 142 | /** 143 | * Applying cals to get CSS filter for invert 144 | * 145 | * @param {number} [value=1] 146 | * @memberof Color 147 | */ 148 | invert(value = 1): void { 149 | this.r = this.clamp((value + (this.r / 255) * (1 - 2 * value)) * 255); 150 | this.g = this.clamp((value + (this.g / 255) * (1 - 2 * value)) * 255); 151 | this.b = this.clamp((value + (this.b / 255) * (1 - 2 * value)) * 255); 152 | } 153 | 154 | /** 155 | * transform RGB into HSL values 156 | * 157 | * @returns {HSLData} 158 | * @memberof Color 159 | */ 160 | hsl(): HSLData { 161 | const red = this.r / 255; 162 | const green = this.g / 255; 163 | const blue = this.b / 255; 164 | 165 | // find greatest and smallest channel values 166 | const max = Math.max(red, green, blue); 167 | const min = Math.min(red, green, blue); 168 | 169 | let hue = 0; 170 | let saturation = 0; 171 | const lightness = (max + min) / 2; 172 | 173 | // If min and max have the same values, it means 174 | // the given color is achromatic 175 | if (max === min) { 176 | return { 177 | h: 0, 178 | s: 0, 179 | l: lightness * 100, 180 | }; 181 | } 182 | 183 | // Adding delta value of greatest and smallest channel values 184 | const delta = max - min; 185 | 186 | saturation = lightness > 0.5 ? delta / (2 - max - min) : delta / (max + min); 187 | 188 | if (max === red) { 189 | hue = (green - blue) / delta + (green < blue ? 6 : 0); 190 | } else if (max === green) { 191 | hue = (blue - red) / delta + 2; 192 | } else if (max === blue) { 193 | hue = (red - green) / delta + 4; 194 | } 195 | 196 | hue /= 6; 197 | 198 | return { 199 | h: hue * 100, 200 | s: saturation * 100, 201 | l: lightness * 100, 202 | }; 203 | } 204 | 205 | /** 206 | * Normalize the value to follow the min and max for RGB colors 207 | * min: 0 208 | * max: 255 209 | * 210 | * @private 211 | * @param {number} value 212 | * @returns {number} 213 | * @memberof Color 214 | */ 215 | private clamp(value: number): number { 216 | // Minimum RGB Value = 0; 217 | // Maximum RGB Value = 255; 218 | return Math.min(Math.max(value, 0), 255); 219 | } 220 | } 221 | 222 | export { Color }; 223 | -------------------------------------------------------------------------------- /src/hex-to-css-filter.ts: -------------------------------------------------------------------------------- 1 | import { Solver } from './solver'; 2 | import { Color } from './color'; 3 | 4 | /** 5 | * Transform a CSS Color from Hexadecimal to RGB color 6 | * 7 | * @param {string} hex hexadecimal color 8 | * @returns {([number, number, number] | [])} array with the RGB colors or empty array 9 | */ 10 | const hexToRgb = (hex: string): [number, number, number] | [] => { 11 | if (hex.length === 4) { 12 | return [parseInt(`0x${hex[1]}${hex[1]}`), parseInt(`0x${hex[2]}${hex[2]}`), parseInt(`0x${hex[3]}${hex[3]}`)] as [ 13 | number, 14 | number, 15 | number, 16 | ]; 17 | } 18 | 19 | if (hex.length === 7) { 20 | return [parseInt(`0x${hex[1]}${hex[2]}`), parseInt(`0x${hex[3]}${hex[4]}`), parseInt(`0x${hex[5]}${hex[6]}`)] as [ 21 | number, 22 | number, 23 | number, 24 | ]; 25 | } 26 | 27 | return []; 28 | }; 29 | 30 | const isNumeric = (n: unknown): boolean => !isNaN(parseFloat(n as string)) && isFinite(n as number); 31 | 32 | // Memory cache for the computed results to avoid multiple 33 | // calculations for the same color 34 | let results: { 35 | [k: string]: HexToCssResult; 36 | } = {} as const; 37 | 38 | export interface HexToCssResult { 39 | /** How many times the script was called to solve the color */ 40 | called: number; 41 | /** CSS filter generated based on the Hex color */ 42 | filter: string; 43 | /** The received color */ 44 | hex: string; 45 | /** Percentage loss value for the generated filter */ 46 | loss: number; 47 | /** Hex color in RGB */ 48 | rgb: [number, number, number]; 49 | /** Percentage loss per each color type organized in RGB: red, green, blue, h, s, l. */ 50 | values: [number, number, number, number, number, number]; 51 | /** Boolean that returns true if value was previously computed. 52 | * So that means the returned value is coming from the in-memory cached */ 53 | cache: boolean; 54 | } 55 | 56 | export interface HexToCssConfiguration { 57 | /** 58 | * Acceptable color percentage to be lost. 59 | * @default 5 60 | */ 61 | acceptanceLossPercentage?: number; 62 | /** 63 | * Maximum checks that needs to be done to return the best value. 64 | * @default 10 65 | */ 66 | maxChecks?: number; 67 | /** 68 | * Boolean value that forces recalculation for CSS filter generation. 69 | * @default false 70 | */ 71 | forceFilterRecalculation?: boolean; 72 | } 73 | 74 | /** 75 | * A function that transforms a HEX color into CSS filters 76 | * 77 | * @param colorValue string hexadecimal color 78 | * @param opts HexToCssConfiguration function configuration 79 | * 80 | */ 81 | export const hexToCSSFilter = (colorValue: string, opts: HexToCssConfiguration = {}): HexToCssResult => { 82 | let red; 83 | let green; 84 | let blue; 85 | 86 | if (results[colorValue] && !opts.forceFilterRecalculation) { 87 | return Object.assign({}, results[colorValue], { cache: true }) as HexToCssResult; 88 | } 89 | 90 | let color: Color; 91 | try { 92 | [red, green, blue] = hexToRgb(colorValue); 93 | if (!isNumeric(red) || !isNumeric(green) || !isNumeric(blue)) { 94 | throw new Error(`hextToRgb returned an invalid value for '${colorValue}'`); 95 | } 96 | 97 | color = new Color(Number(red), Number(green), Number(blue)); 98 | } catch (error) { 99 | throw new Error(`Color value should be in HEX format. ${error}`); 100 | } 101 | 102 | const solver = new Solver( 103 | color, 104 | Object.assign( 105 | {}, 106 | // `HexToCssConfiguration` Defaults 107 | { 108 | acceptanceLossPercentage: 5, 109 | maxChecks: 30, 110 | forceFilterRecalculation: false, 111 | }, 112 | opts, 113 | ) as HexToCssConfiguration, 114 | ); 115 | 116 | return (results[colorValue] = Object.assign({}, solver.solve(), { 117 | hex: colorValue, 118 | rgb: [red, green, blue], 119 | cache: false, 120 | }) as HexToCssResult); 121 | }; 122 | 123 | /** 124 | * A function that clears cached results 125 | * 126 | * @param {string} key? HEX string value passed previously `#24639C`. If not passed, it clears all cached results 127 | * @returns void 128 | */ 129 | export const clearCache = (key?: string): void => { 130 | if (!key) { 131 | results = {}; 132 | } else if (results[key]) { 133 | delete results[key]; 134 | } 135 | }; 136 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './hex-to-css-filter'; 2 | -------------------------------------------------------------------------------- /src/solver.ts: -------------------------------------------------------------------------------- 1 | import { Color } from './color'; 2 | import { HexToCssConfiguration } from './hex-to-css-filter'; 3 | 4 | interface SPSAPayload { 5 | /** How many times the script was called to solve the color */ 6 | called?: number; 7 | /** Percentage loss value for the generated filter */ 8 | loss: number; 9 | /** Percentage loss per each color type organized in RGB: red, green, blue, h, s, l. */ 10 | values: [number, number, number, number, number, number]; 11 | } 12 | 13 | class Solver { 14 | private target: Color; 15 | private targetHSL: { h: number; s: number; l: number }; 16 | private reusedColor: Color; 17 | private options: { acceptanceLossPercentage: number; maxChecks: number } & HexToCssConfiguration; 18 | 19 | constructor(target: Color, options: HexToCssConfiguration) { 20 | this.target = target; 21 | this.targetHSL = target.hsl(); 22 | 23 | this.options = Object.assign( 24 | {}, 25 | // Adding default values for options 26 | { 27 | acceptanceLossPercentage: 5, 28 | maxChecks: 15, 29 | }, 30 | options, 31 | ); 32 | 33 | // All the calcs done by the library to generate 34 | // a CSS Filter are based on the color `#000` 35 | // in this case, `rgb(0, 0, 0)` 36 | // Please make sure the background of the element 37 | // is `#000` for better performance 38 | // and color similarity. 39 | this.reusedColor = new Color(0, 0, 0); 40 | } 41 | 42 | /** 43 | * Returns the solved values for the 44 | * 45 | * @returns {(SPSAPayload & { filter: string; })} 46 | * @memberof Solver 47 | */ 48 | solve(): SPSAPayload & { 49 | /** CSS filter generated based on the Hex color */ 50 | filter: string; 51 | } { 52 | const result = this.solveNarrow(this.solveWide()); 53 | return { 54 | values: result.values, 55 | called: result.called, 56 | loss: result.loss, 57 | filter: this.css(result.values), 58 | }; 59 | } 60 | 61 | /** 62 | * Solve wide values based on the wide values for RGB and HSL values 63 | * 64 | * @private 65 | * @returns {SPSAPayload} 66 | * @memberof Solver 67 | */ 68 | private solveWide(): SPSAPayload { 69 | const A = 5; 70 | const c = 15; 71 | // Wide values for RGB and HSL values 72 | // the values in the order: [`r`, `g`, `b`, `h`, `s`, `l`] 73 | const a = [60, 180, 18000, 600, 1.2, 1.2]; 74 | 75 | let best = { loss: Infinity }; 76 | let counter = 0; 77 | while (best.loss > this.options.acceptanceLossPercentage) { 78 | const initialFilterValues: SPSAPayload['values'] = [50, 20, 3750, 50, 100, 100]; 79 | const result: SPSAPayload = this.spsa({ 80 | A, 81 | a, 82 | c, 83 | values: initialFilterValues, 84 | // for wide values we should use the double of tries in 85 | // comparison of `solveNarrow()` method 86 | maxTriesInLoop: 1000, 87 | }); 88 | 89 | if (result.loss < best.loss) { 90 | best = result; 91 | } 92 | 93 | counter += 1; 94 | if (counter >= this.options.maxChecks) { 95 | break; 96 | } 97 | } 98 | 99 | return Object.assign({}, best, { called: counter }) as SPSAPayload; 100 | } 101 | 102 | /** 103 | * Solve narrow values based on the wide values for the filter 104 | * 105 | * @private 106 | * @param {SPSAPayload} wide 107 | * @returns {SPSAPayload} 108 | * @memberof Solver 109 | */ 110 | private solveNarrow(wide: SPSAPayload): SPSAPayload { 111 | const A = wide.loss; 112 | const c = 2; 113 | const A1 = A + 1; 114 | // Narrow values for RGB and HSL values 115 | // the values in the order: [`r`, `g`, `b`, `h`, `s`, `l`] 116 | const a = [0.25 * A1, 0.25 * A1, A1, 0.25 * A1, 0.2 * A1, 0.2 * A1]; 117 | return this.spsa({ 118 | A, 119 | a, 120 | c, 121 | values: wide.values, 122 | maxTriesInLoop: 500, 123 | called: wide.called, 124 | }); 125 | } 126 | 127 | /** 128 | * Returns final value based on the current filter order 129 | * to get the order, please check the returned value 130 | * in `css()` method 131 | * 132 | * @private 133 | * @param {number} value 134 | * @param {number} idx 135 | * @returns {number} 136 | * @memberof Solver 137 | */ 138 | private fixValueByFilterIDX(value: number, idx: number): number { 139 | let max = 100; 140 | 141 | // Fixing max, minimum and value by filter 142 | if (idx === 2 /* saturate */) { 143 | max = 7500; 144 | } else if (idx === 4 /* brightness */ || idx === 5 /* contrast */) { 145 | max = 200; 146 | } 147 | 148 | if (idx === 3 /* hue-rotate */) { 149 | if (value > max) { 150 | value %= max; 151 | } else if (value < 0) { 152 | value = max + (value % max); 153 | } 154 | } 155 | // Checking if value is below the minimum or above 156 | // the maximum allowed by filter 157 | else if (value < 0) { 158 | value = 0; 159 | } else if (value > max) { 160 | value = max; 161 | } 162 | return value; 163 | } 164 | 165 | private spsa({ 166 | A, 167 | a, 168 | c, 169 | values, 170 | maxTriesInLoop = 500, 171 | called = 0, 172 | }: { 173 | A: number; 174 | a: number[]; 175 | c: number; 176 | values: SPSAPayload['values']; 177 | maxTriesInLoop: number; 178 | called?: number; 179 | }): SPSAPayload { 180 | const alpha = 1; 181 | const gamma = 0.16666666666666666; 182 | 183 | let best = null; 184 | let bestLoss = Infinity; 185 | 186 | const deltas = new Array(6) as SPSAPayload['values']; 187 | const highArgs = new Array(6) as SPSAPayload['values']; 188 | const lowArgs = new Array(6) as SPSAPayload['values']; 189 | 190 | // Size of all CSS filters to be applied to get the correct color 191 | const filtersToBeAppliedSize = 6; 192 | 193 | for (let key = 0; key < maxTriesInLoop; key++) { 194 | const ck = c / Math.pow(key + 1, gamma); 195 | for (let i = 0; i < filtersToBeAppliedSize; i++) { 196 | deltas[i] = Math.random() > 0.5 ? 1 : -1; 197 | highArgs[i] = values[i] + ck * deltas[i]; 198 | lowArgs[i] = values[i] - ck * deltas[i]; 199 | } 200 | 201 | const lossDiff = this.loss(highArgs) - this.loss(lowArgs); 202 | for (let i = 0; i < filtersToBeAppliedSize; i++) { 203 | const g = (lossDiff / (2 * ck)) * deltas[i]; 204 | const ak = a[i] / Math.pow(A + key + 1, alpha); 205 | values[i] = this.fixValueByFilterIDX(values[i] - ak * g, i); 206 | } 207 | 208 | const loss = this.loss(values); 209 | if (loss < bestLoss) { 210 | best = values.slice(0); 211 | bestLoss = loss; 212 | } 213 | } 214 | 215 | return { values: best, loss: bestLoss, called } as SPSAPayload; 216 | } 217 | 218 | /** 219 | * Checks how much is the loss for the filter in RGB and HSL colors 220 | * 221 | * @private 222 | * @param {SPSAPayload['values']} filters 223 | * @returns {number} 224 | * @memberof Solver 225 | */ 226 | private loss(filters: SPSAPayload['values']): number { 227 | // Argument as an Array of percentages. 228 | const color = this.reusedColor; 229 | 230 | // Resetting the color to black in case 231 | // it was called more than once 232 | color.set(0, 0, 0); 233 | 234 | color.invert(filters[0] / 100); 235 | color.sepia(filters[1] / 100); 236 | color.saturate(filters[2] / 100); 237 | color.hueRotate(filters[3] * 3.6); 238 | color.brightness(filters[4] / 100); 239 | color.contrast(filters[5] / 100); 240 | 241 | const colorHSL = color.hsl(); 242 | 243 | return ( 244 | Math.abs(color.r - this.target.r) + 245 | Math.abs(color.g - this.target.g) + 246 | Math.abs(color.b - this.target.b) + 247 | Math.abs(colorHSL.h - this.targetHSL.h) + 248 | Math.abs(colorHSL.s - this.targetHSL.s) + 249 | Math.abs(colorHSL.l - this.targetHSL.l) 250 | ); 251 | } 252 | 253 | /** 254 | * Returns the CSS filter list for the received HEX color 255 | * 256 | * @private 257 | * @param {number[]} filters 258 | * @returns {string} 259 | * @memberof Solver 260 | */ 261 | private css(filters: number[]): string { 262 | const formatCssFilterValueByMultiplier = (idx: number, multiplier = 1): number => 263 | Math.round(filters[idx] * multiplier); 264 | 265 | return [ 266 | `invert(${formatCssFilterValueByMultiplier(0)}%)`, 267 | `sepia(${formatCssFilterValueByMultiplier(1)}%)`, 268 | `saturate(${formatCssFilterValueByMultiplier(2)}%)`, 269 | `hue-rotate(${formatCssFilterValueByMultiplier(3, 3.6)}deg)`, 270 | `brightness(${formatCssFilterValueByMultiplier(4)}%)`, 271 | `contrast(${formatCssFilterValueByMultiplier(5)}%)`, 272 | ].join(' '); 273 | } 274 | } 275 | 276 | export { Solver }; 277 | -------------------------------------------------------------------------------- /tsconfig.eslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./out-tsc", 5 | "types": ["jest", "node"], 6 | "module": "esnext", 7 | "target": "es2015", 8 | "sourceMap": true /* Generates corresponding '.map' file. */ 9 | }, 10 | "files": [], 11 | "include": ["scripts/**/*", "src/**/*", "src/__tests__/**/**/*.ts", "src/**/**/*.d.ts"], 12 | "exclude": ["node_modules"] 13 | } 14 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "allowJs": false, 5 | // "strict": true, 6 | "allowSyntheticDefaultImports": true, 7 | // "declaration": true, 8 | "esModuleInterop": true, 9 | "experimentalDecorators": true, 10 | "downlevelIteration": true, 11 | // https://github.com/angular/angular-cli/issues/13886#issuecomment-471927518 12 | "importHelpers": true, 13 | "moduleResolution": "node", 14 | "noFallthroughCasesInSwitch": true, 15 | "noUnusedLocals": true, 16 | // "sourceMap": true, 17 | // "esModuleInterop": true, 18 | "baseUrl": "./", 19 | /* Basic Options */ 20 | "module": "commonjs", 21 | // "module": "esnext", 22 | "lib": ["es2018", "dom", "es5", "scripthost"], 23 | "emitDecoratorMetadata": true, 24 | "typeRoots": ["node_modules/@types"], 25 | // "lib": ["es2018", "dom"], 26 | "target": "ES5" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', or 'ESNEXT'. */, 27 | "declaration": true /* Generates corresponding '.d.ts' file. */, 28 | "sourceMap": false /* Generates corresponding '.map' file. */, 29 | "outDir": "lib" /* Redirect output structure to the directory. */, 30 | "rootDir": "src" /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */, 31 | "removeComments": false /* Do not emit comments to output. */, 32 | /* Strict Type-Checking Options */ 33 | "strict": true /* Enable all strict type-checking options. */, 34 | "noImplicitAny": true /* Raise error on expressions and declarations with an implied 'any' type. */, 35 | "paths": { 36 | "*": ["src/entrypoints/*"] 37 | } 38 | }, 39 | "include": ["src/**/*"], 40 | "exclude": ["node_modules", "src/__tests__/**/**/*.ts"] 41 | } 42 | -------------------------------------------------------------------------------- /tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./out-tsc", 5 | "types": ["jest", "node"], 6 | "module": "esnext", 7 | "target": "es2015", 8 | "sourceMap": true /* Generates corresponding '.map' file. */ 9 | }, 10 | "files": [], 11 | "include": ["src/__tests__/**/**/*.ts", "src/**/**/*.d.ts"], 12 | "exclude": ["node_modules"] 13 | } 14 | --------------------------------------------------------------------------------