├── projects ├── app │ ├── src │ │ ├── polyfills.ts │ │ ├── environments │ │ │ ├── environment.prod.ts │ │ │ └── environment.ts │ │ ├── favicon.ico │ │ ├── styles.css │ │ ├── main.ts │ │ ├── app │ │ │ ├── app.component.css │ │ │ ├── app.component.ts │ │ │ └── app.component.html │ │ └── index.html │ ├── tsconfig.json │ ├── browserslist │ └── stylelint.json └── lib │ ├── ng-package.json │ ├── src │ ├── public-api.ts │ └── lib │ │ ├── formats.ts │ │ ├── helpers.ts │ │ ├── color-picker.component.html │ │ ├── color-picker.service.ts │ │ ├── color-picker.directive.ts │ │ ├── color-picker.component.css │ │ └── color-picker.component.ts │ ├── package.json │ ├── tsconfig.json │ ├── README.md │ └── stylelint.json ├── .prettierrc.json ├── .npmignore ├── .gitignore ├── tailwind.config.js ├── CONTRIBUTING.md ├── tsconfig.json ├── .eslintrc.json ├── LICENSE.md ├── package.json ├── angular.json ├── README.md └── .stylelintrc.json /projects/app/src/polyfills.ts: -------------------------------------------------------------------------------- 1 | import 'zone.js'; 2 | -------------------------------------------------------------------------------- /projects/app/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /projects/app/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zefoy/ngx-color-picker/HEAD/projects/app/src/favicon.ico -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "tabWidth": 2, 4 | "singleQuote": true, 5 | "trailingComma": "none" 6 | } 7 | -------------------------------------------------------------------------------- /projects/app/src/styles.css: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | 3 | @tailwind base; 4 | @tailwind utilities; 5 | @tailwind components; 6 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .* 2 | *.tgz 3 | 4 | src/ 5 | config/ 6 | example/ 7 | 8 | package/ 9 | npm-debug.log 10 | node_modules/ 11 | 12 | tslint.json 13 | tsconfig.json 14 | stylelint.json 15 | -------------------------------------------------------------------------------- /projects/app/src/main.ts: -------------------------------------------------------------------------------- 1 | import { bootstrapApplication } from '@angular/platform-browser'; 2 | 3 | import { AppComponent } from './app/app.component'; 4 | 5 | bootstrapApplication(AppComponent); 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | bundles/ 3 | 4 | package/ 5 | npm-debug.log 6 | node_modules/ 7 | .angular/ 8 | .vs/ 9 | 10 | example/dist/ 11 | example/npm-debug.log 12 | example/node_modules/ 13 | .idea 14 | -------------------------------------------------------------------------------- /projects/lib/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", 3 | "dest": "../../dist/lib", 4 | "lib": { 5 | "entryFile": "src/public-api.ts" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: [ 4 | "./projects/app/src/**/*.{html,ts}", 5 | ], 6 | theme: { 7 | extend: {}, 8 | }, 9 | plugins: [], 10 | } 11 | 12 | -------------------------------------------------------------------------------- /projects/app/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../out-tsc/app", 5 | "types": [] 6 | }, 7 | "files": [ 8 | "src/main.ts", 9 | "src/polyfills.ts" 10 | ], 11 | "include": [ 12 | "src/**/*.d.ts" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /projects/lib/src/public-api.ts: -------------------------------------------------------------------------------- 1 | export { Cmyk, Hsla, Hsva, Rgba } from "./lib/formats"; 2 | export { 3 | AlphaChannel, 4 | ColorMode, 5 | OutputFormat, 6 | TextDirective, 7 | SliderDirective, 8 | } from "./lib/helpers"; 9 | 10 | export { ColorPickerComponent } from "./lib/color-picker.component"; 11 | export { ColorPickerDirective } from "./lib/color-picker.directive"; 12 | export { ColorPickerService } from "./lib/color-picker.service"; 13 | -------------------------------------------------------------------------------- /projects/app/browserslist: -------------------------------------------------------------------------------- 1 | # This file is used by the build system to adjust CSS and JS output to support the specified browsers below. 2 | # For additional information regarding the format and rule options, please see: 3 | # https://github.com/browserslist/browserslist#queries 4 | 5 | # You can see what browsers were selected by your queries by running: 6 | # npx browserslist 7 | 8 | > 0.5% 9 | last 2 versions 10 | Firefox ESR 11 | not dead 12 | not IE 9-11 # For IE 9-11 support, remove 'not'. -------------------------------------------------------------------------------- /projects/app/src/app/app.component.css: -------------------------------------------------------------------------------- 1 | input { 2 | width: 150px; 3 | margin-bottom: 16px; 4 | } 5 | 6 | .cmyk-text { 7 | float: left; 8 | 9 | width: 72px; 10 | height: 72px; 11 | 12 | font-weight: bolder; 13 | line-height: 72px; 14 | text-align: center; 15 | text-shadow: 1px 1px 2px #bbb; 16 | } 17 | 18 | .color-box { 19 | width: 100px; 20 | height: 25px; 21 | margin: 16px auto; 22 | 23 | cursor: pointer; 24 | } 25 | 26 | .change-me { 27 | cursor: pointer; 28 | font-size: 30px; 29 | font-weight: bolder; 30 | } 31 | -------------------------------------------------------------------------------- /projects/lib/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "license": "MIT", 3 | "version": "20.1.1", 4 | "name": "ngx-color-picker", 5 | "description": "Color picker widget for Angular", 6 | "bugs": "https://github.com/zefoy/ngx-color-picker/issues", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/zefoy/ngx-color-picker.git", 10 | "directory": "projects/lib" 11 | }, 12 | "peerDependencies": { 13 | "@angular/common": ">=19.0.0", 14 | "@angular/core": ">=19.0.0", 15 | "@angular/forms": ">=19.0.0" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /projects/lib/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../out-tsc/lib", 5 | "target": "es2022", 6 | "declaration": true, 7 | "inlineSources": true, 8 | "stripInternal": true, 9 | "types": [], 10 | "lib": [ 11 | "dom", 12 | "es2022" 13 | ] 14 | }, 15 | "angularCompilerOptions": { 16 | "skipTemplateCodegen": true, 17 | "strictMetadataEmit": true, 18 | "enableResourceInlining": true, 19 | "compilationMode": "partial" 20 | }, 21 | "exclude": [ 22 | "src/test.ts", 23 | "**/*.spec.ts" 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing guide 2 | 3 | Want to contribute to this project? Awesome! There are many ways you can contribute, see below. 4 | 5 | ### Opening issues 6 | 7 | Open an issue to report bugs or to propose new features. If you have a general usage question please note that this is just a wrapper and most questions should be directed to the actual library in Stack Overflow etc. 8 | 9 | ### Proposing pull requests 10 | 11 | Pull requests are more than welcome. Note that if you are going to propose drastic changes or new features, be sure to open an issue for discussion first, to make sure that your PR will be accepted before you spend effort coding it. 12 | -------------------------------------------------------------------------------- /projects/app/src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // `ng build --prod` replaces `environment.ts` with `environment.prod.ts`. 3 | // The list of file replacements can be found in `angular.json`. 4 | 5 | export const environment = { 6 | production: false 7 | }; 8 | 9 | /* 10 | * For easier debugging in development mode, you can import the following file 11 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. 12 | * 13 | * This import should be commented out in production mode because it will have a negative impact 14 | * on performance if an error is thrown. 15 | */ 16 | // import 'zone.js/dist/zone-error'; // Included with Angular CLI. 17 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "outDir": "./dist", 6 | "sourceMap": true, 7 | "declaration": false, 8 | "downlevelIteration": true, 9 | "experimentalDecorators": true, 10 | "module": "esnext", 11 | "moduleResolution": "node", 12 | "importHelpers": true, 13 | "target": "es2022", 14 | "typeRoots": [ 15 | "node_modules/@types" 16 | ], 17 | "lib": [ 18 | "dom", 19 | "es2022" 20 | ], 21 | "paths": { 22 | "ngx-color-picker": [ 23 | "./projects/lib/src/public-api.ts" 24 | ] 25 | } 26 | }, 27 | "angularCompilerOptions": { 28 | "fullTemplateTypeCheck": true, 29 | "strictInjectionParameters": true 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /projects/lib/src/lib/formats.ts: -------------------------------------------------------------------------------- 1 | export enum ColorFormats { 2 | HEX, 3 | RGBA, 4 | HSLA, 5 | CMYK, 6 | } 7 | 8 | export class Rgba { 9 | constructor( 10 | public r: number, 11 | public g: number, 12 | public b: number, 13 | public a: number 14 | ) {} 15 | } 16 | 17 | export class Hsva { 18 | constructor( 19 | public h: number, 20 | public s: number, 21 | public v: number, 22 | public a: number 23 | ) {} 24 | } 25 | 26 | export class Hsla { 27 | constructor( 28 | public h: number, 29 | public s: number, 30 | public l: number, 31 | public a: number 32 | ) {} 33 | } 34 | 35 | export class Cmyk { 36 | constructor( 37 | public c: number, 38 | public m: number, 39 | public y: number, 40 | public k: number, 41 | public a: number = 1 42 | ) {} 43 | } 44 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "ignorePatterns": [ 4 | ], 5 | "overrides": [ 6 | { 7 | "files": [ 8 | "*.ts" 9 | ], 10 | "parserOptions": { 11 | "project": [ 12 | "tsconfig.json", 13 | "e2e/tsconfig.json" 14 | ], 15 | "createDefaultProgram": true 16 | }, 17 | "extends": [ 18 | "plugin:@angular-eslint/recommended", 19 | "plugin:@angular-eslint/template/process-inline-templates" 20 | ], 21 | "rules": { 22 | "@angular-eslint/component-selector": [ 23 | "off" 24 | ], 25 | "@angular-eslint/directive-selector": [ 26 | "off" 27 | ] 28 | } 29 | }, 30 | { 31 | "files": [ 32 | "*.html" 33 | ], 34 | "extends": [ 35 | "plugin:@angular-eslint/template/recommended" 36 | ], 37 | "rules": {} 38 | } 39 | ] 40 | } 41 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2016 Zef Oy 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /projects/app/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Example app 8 | 9 | 10 | 11 | 12 | 13 | 58 | 59 | 60 | 61 | 62 | 63 |
64 | 65 | 66 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ngx-color-picker", 3 | "description": "Color picker widget for Angular", 4 | "bugs": "https://github.com/zefoy/ngx-color-picker/issues", 5 | "version": "20.1.1", 6 | "license": "MIT", 7 | "private": true, 8 | "scripts": { 9 | "ng": "ng", 10 | "lint": "ng lint", 11 | "start": "ng serve app", 12 | "build": "ng build lib", 13 | "deploy": "deploy-to-git", 14 | "prepare": "ng build lib --configuration production", 15 | "publish": "npm publish ./dist/lib", 16 | "predeploy": "rimraf ./dist/app && mkdirp ./dist/app" 17 | }, 18 | "repository": { 19 | "type": "git", 20 | "url": "https://github.com/zefoy/ngx-color-picker.git" 21 | }, 22 | "config": { 23 | "deployToGit": { 24 | "repository": "git@github.com:zefoy/ngx-color-picker.git", 25 | "branch": "gh-pages", 26 | "folder": "dist/app", 27 | "script": "ng build app --configuration production --base-href=ngx-color-picker --delete-output-path=false", 28 | "commit": "Publishing $npm_package_version", 29 | "user": { 30 | "name": "ZEF Devel", 31 | "email": "devel@zef.fi" 32 | } 33 | } 34 | }, 35 | "dependencies": { 36 | "@angular/cdk": "^20.0.0", 37 | "@angular/common": "^20.0.0", 38 | "@angular/compiler": "^20.0.0", 39 | "@angular/core": "^20.0.0", 40 | "@angular/forms": "^20.0.0", 41 | "@angular/platform-browser": "^20.0.0", 42 | "@angular/platform-browser-dynamic": "^20.0.0", 43 | "rxjs": "^7.8.0", 44 | "tailwindcss": "~3.4.0", 45 | "zone.js": "^0.15.0" 46 | }, 47 | "devDependencies": { 48 | "@angular-devkit/build-angular": "^20.0.0", 49 | "@angular-eslint/builder": "^20.0.0", 50 | "@angular-eslint/eslint-plugin": "^20.0.0", 51 | "@angular-eslint/eslint-plugin-template": "^20.0.0", 52 | "@angular-eslint/schematics": "^20.0.0", 53 | "@angular-eslint/template-parser": "^20.0.0", 54 | "@angular/cli": "^20.0.0", 55 | "@angular/compiler-cli": "^20.0.0", 56 | "@typescript-eslint/eslint-plugin": "^8.26.0", 57 | "@typescript-eslint/parser": "^8.26.0", 58 | "cpx": "^1.5.0", 59 | "deploy-to-git": "^0.4.0", 60 | "eslint": "^8.57.0", 61 | "mkdirp": "^3.0.0", 62 | "ng-packagr": "^20.0.0", 63 | "rimraf": "^6.0.0", 64 | "stylelint": "^16.15.0", 65 | "stylelint-config-standard": "^37.0.0", 66 | "stylelint-order": "^6.0.0", 67 | "terser": "^5.43.0", 68 | "tslib": "^2.8.0", 69 | "typescript": "~5.8.0", 70 | "watch": "^1.0.0" 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /projects/app/src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, inject } from '@angular/core' 2 | import { FormsModule } from '@angular/forms' 3 | 4 | import { 5 | ColorPickerService, 6 | Cmyk, 7 | ColorPickerDirective, 8 | } from 'ngx-color-picker' 9 | 10 | @Component({ 11 | selector: 'app', 12 | styleUrls: ['app.component.css'], 13 | templateUrl: 'app.component.html', 14 | imports: [FormsModule, ColorPickerDirective], 15 | }) 16 | export class AppComponent { 17 | private cpService = inject(ColorPickerService) 18 | 19 | public toggle: boolean = false 20 | 21 | public rgbaText: string = 'rgba(165, 26, 214, 0.2)' 22 | 23 | public arrayColors: any = { 24 | color1: '#2883e9', 25 | color2: '#e920e9', 26 | color3: 'rgb(255,245,0)', 27 | color4: 'rgb(236,64,64)', 28 | color5: 'rgba(45,208,45,1)', 29 | } 30 | 31 | public selectedColor: string = 'color1' 32 | 33 | public color1: string = '#2889e9' 34 | public color2: string = '#e920e9' 35 | public color3: string = '#fff500' 36 | public color4: string = 'rgb(236,64,64)' 37 | public color5: string = 'rgba(45,208,45,1)' 38 | public color6: string = '#1973c0' 39 | public color7: string = '#f200bd' 40 | public color8: string = '#a8ff00' 41 | public color9: string = '#278ce2' 42 | public color10: string = '#0a6211' 43 | public color11: string = '#f2ff00' 44 | public color12: string = '#f200bd' 45 | public color13: string = 'rgba(0,255,0,0.5)' 46 | public color14: string = 'rgb(0,255,255)' 47 | public color15: string = 'rgb(255,0,0)' 48 | public color16: string = '#a51ad633' 49 | public color17: string = '#666666' 50 | public color18: string = '#fa8072' 51 | public color19: string = '#f88888' 52 | public color20: string = '#ff0000' 53 | 54 | public cmykValue: string = '' 55 | 56 | public cmykColor: Cmyk = new Cmyk(0, 0, 0, 0) 57 | 58 | public alphaEnabled = false 59 | 60 | public onEventLog(event: string, data: any): void { 61 | console.log(event, data) 62 | } 63 | 64 | public onChangeColor(color: string): void { 65 | console.log('Color changed:', color) 66 | } 67 | 68 | public onChangeColorCmyk(color: string): Cmyk { 69 | const hsva = this.cpService.stringToHsva(color) 70 | 71 | if (hsva) { 72 | const rgba = this.cpService.hsvaToRgba(hsva) 73 | 74 | return this.cpService.rgbaToCmyk(rgba) 75 | } 76 | 77 | return new Cmyk(0, 0, 0, 0) 78 | } 79 | 80 | public onChangeColorHex8(color: string): string { 81 | const hsva = this.cpService.stringToHsva(color, true) 82 | 83 | if (hsva) { 84 | return this.cpService.outputFormat(hsva, 'rgba', null) 85 | } 86 | 87 | return '' 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "cli": { 4 | "analytics": false 5 | }, 6 | "version": 1, 7 | "newProjectRoot": "projects", 8 | "projects": { 9 | "app": { 10 | "projectType": "application", 11 | "schematics": {}, 12 | "root": "", 13 | "sourceRoot": "projects/app/src", 14 | "prefix": "app", 15 | "architect": { 16 | "build": { 17 | "builder": "@angular-devkit/build-angular:application", 18 | "options": { 19 | "outputPath": "dist/app", 20 | "index": "projects/app/src/index.html", 21 | "browser": "projects/app/src/main.ts", 22 | "polyfills": ["projects/app/src/polyfills.ts"], 23 | "tsConfig": "projects/app/tsconfig.json", 24 | "assets": [ 25 | "projects/app/src/favicon.ico", 26 | "projects/app/src/assets" 27 | ], 28 | "styles": [ 29 | "projects/app/src/styles.css" 30 | ], 31 | "scripts": [] 32 | }, 33 | "configurations": { 34 | "production": { 35 | "fileReplacements": [ 36 | { 37 | "replace": "projects/app/src/environments/environment.ts", 38 | "with": "projects/app/src/environments/environment.prod.ts" 39 | } 40 | ], 41 | "budgets": [ 42 | { 43 | "type": "initial", 44 | "maximumWarning": "2mb", 45 | "maximumError": "5mb" 46 | }, 47 | { 48 | "type": "anyComponentStyle", 49 | "maximumWarning": "30kb", 50 | "maximumError": "50kb" 51 | } 52 | ] 53 | }, 54 | "development": { 55 | "optimization": false, 56 | "extractLicenses": false, 57 | "sourceMap": true, 58 | "namedChunks": true 59 | } 60 | } 61 | }, 62 | "serve": { 63 | "builder": "@angular-devkit/build-angular:dev-server", 64 | "options": { 65 | "buildTarget": "app:build" 66 | }, 67 | "configurations": { 68 | "production": { 69 | "buildTarget": "app:build:production" 70 | }, 71 | "development": { 72 | "buildTarget": "app:build:development" 73 | } 74 | }, 75 | "defaultConfiguration": "development" 76 | }, 77 | "extract-i18n": { 78 | "builder": "@angular-devkit/build-angular:extract-i18n", 79 | "options": { 80 | "buildTarget": "app:build" 81 | } 82 | }, 83 | "lint": { 84 | "builder": "@angular-eslint/builder:lint", 85 | "options": { 86 | "lintFilePatterns": [ 87 | "projects/app/src/**/*.ts", 88 | "projects/app/src/**/*.html" 89 | ] 90 | } 91 | } 92 | } 93 | }, 94 | "lib": { 95 | "projectType": "library", 96 | "root": "projects/lib", 97 | "sourceRoot": "projects/lib/src", 98 | "prefix": "lib", 99 | "architect": { 100 | "build": { 101 | "builder": "@angular-devkit/build-angular:ng-packagr", 102 | "options": { 103 | "tsConfig": "projects/lib/tsconfig.json", 104 | "project": "projects/lib/ng-package.json" 105 | }, 106 | "configurations": { 107 | "production": {} 108 | } 109 | }, 110 | "lint": { 111 | "builder": "@angular-eslint/builder:lint", 112 | "options": { 113 | "lintFilePatterns": [ 114 | "projects/lib/src/**/*.ts", 115 | "projects/lib/src/**/*.html" 116 | ] 117 | } 118 | } 119 | } 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /projects/lib/src/lib/helpers.ts: -------------------------------------------------------------------------------- 1 | import { DOCUMENT } from '@angular/common' 2 | import { 3 | Directive, 4 | Input, 5 | Output, 6 | EventEmitter, 7 | HostListener, 8 | ElementRef, 9 | inject, 10 | } from '@angular/core' 11 | 12 | export type ColorMode = 13 | | 'color' 14 | | 'c' 15 | | '1' 16 | | 'grayscale' 17 | | 'g' 18 | | '2' 19 | | 'presets' 20 | | 'p' 21 | | '3' 22 | 23 | export type AlphaChannel = 'enabled' | 'disabled' | 'always' | 'forced' 24 | 25 | export type BoundingRectangle = { 26 | top: number 27 | bottom: number 28 | left: number 29 | right: number 30 | height: number 31 | width: number 32 | } 33 | 34 | export type OutputFormat = 'auto' | 'hex' | 'rgba' | 'hsla' 35 | 36 | export function calculateAutoPositioning( 37 | elBounds: BoundingRectangle, 38 | triggerElBounds: BoundingRectangle, 39 | window: Window 40 | ): string { 41 | // Defaults 42 | let usePositionX = 'right' 43 | let usePositionY = 'bottom' 44 | // Calculate collisions 45 | const { height, width } = elBounds 46 | const { top, left } = triggerElBounds 47 | const bottom = top + triggerElBounds.height 48 | const right = left + triggerElBounds.width 49 | 50 | const collisionTop = top - height < 0 51 | const collisionBottom = 52 | bottom + height > 53 | (window.innerHeight || document.documentElement.clientHeight) 54 | const collisionLeft = left - width < 0 55 | const collisionRight = 56 | right + width > (window.innerWidth || document.documentElement.clientWidth) 57 | const collisionAll = 58 | collisionTop && collisionBottom && collisionLeft && collisionRight 59 | 60 | // Generate X & Y position values 61 | if (collisionBottom) { 62 | usePositionY = 'top' 63 | } 64 | 65 | if (collisionTop) { 66 | usePositionY = 'bottom' 67 | } 68 | 69 | if (collisionLeft) { 70 | usePositionX = 'right' 71 | } 72 | 73 | if (collisionRight) { 74 | usePositionX = 'left' 75 | } 76 | 77 | // Choose the largest gap available 78 | if (collisionAll) { 79 | const postions = ['left', 'right', 'top', 'bottom'] 80 | return postions.reduce((prev, next) => 81 | elBounds[prev] > elBounds[next] ? prev : next 82 | ) 83 | } 84 | 85 | if (collisionLeft && collisionRight) { 86 | if (collisionTop) { 87 | return 'bottom' 88 | } 89 | if (collisionBottom) { 90 | return 'top' 91 | } 92 | return top > bottom ? 'top' : 'bottom' 93 | } 94 | 95 | if (collisionTop && collisionBottom) { 96 | if (collisionLeft) { 97 | return 'right' 98 | } 99 | if (collisionRight) { 100 | return 'left' 101 | } 102 | return left > right ? 'left' : 'right' 103 | } 104 | 105 | return `${usePositionY}-${usePositionX}` 106 | } 107 | 108 | @Directive({ 109 | selector: '[text]', 110 | }) 111 | export class TextDirective { 112 | @Input() rg: number 113 | @Input() text: any 114 | 115 | @Output() newValue = new EventEmitter() 116 | 117 | @HostListener('input', ['$event']) inputChange(event: any): void { 118 | const value = event.target.value 119 | 120 | if (this.rg === undefined) { 121 | this.newValue.emit(value) 122 | } else { 123 | const numeric = parseFloat(value) 124 | 125 | this.newValue.emit({ v: numeric, rg: this.rg }) 126 | } 127 | } 128 | } 129 | 130 | @Directive({ 131 | selector: '[slider]', 132 | }) 133 | export class SliderDirective { 134 | private elRef = inject(ElementRef) 135 | private document = inject(DOCUMENT) 136 | 137 | private readonly listenerMove: (event: Event) => void 138 | private readonly listenerStop: () => void 139 | 140 | @Input() rgX: number 141 | @Input() rgY: number 142 | 143 | @Output() dragEnd = new EventEmitter() 144 | @Output() dragStart = new EventEmitter() 145 | 146 | @Output() newValue = new EventEmitter() 147 | 148 | @HostListener('mousedown', ['$event']) mouseDown(event: any): void { 149 | this.start(event) 150 | } 151 | 152 | @HostListener('touchstart', ['$event']) touchStart(event: any): void { 153 | this.start(event) 154 | } 155 | 156 | constructor() { 157 | this.listenerMove = (event: Event) => this.move(event) 158 | 159 | this.listenerStop = () => this.stop() 160 | } 161 | 162 | private move(event: Event): void { 163 | event.preventDefault() 164 | 165 | this.setCursor(event) 166 | } 167 | 168 | private start(event: Event): void { 169 | this.setCursor(event) 170 | 171 | event.stopPropagation() 172 | 173 | this.document.addEventListener('mouseup', this.listenerStop) 174 | this.document.addEventListener('touchend', this.listenerStop) 175 | this.document.addEventListener('mousemove', this.listenerMove) 176 | this.document.addEventListener('touchmove', this.listenerMove) 177 | 178 | this.dragStart.emit() 179 | } 180 | 181 | private stop(): void { 182 | this.document.removeEventListener('mouseup', this.listenerStop) 183 | this.document.removeEventListener('touchend', this.listenerStop) 184 | this.document.removeEventListener('mousemove', this.listenerMove) 185 | this.document.removeEventListener('touchmove', this.listenerMove) 186 | 187 | this.dragEnd.emit() 188 | } 189 | 190 | private getX(event: any): number { 191 | const position = this.elRef.nativeElement.getBoundingClientRect() 192 | 193 | const pageX = 194 | event.pageX !== undefined ? event.pageX : event.touches[0].pageX 195 | 196 | return pageX - position.left - window.pageXOffset 197 | } 198 | 199 | private getY(event: any): number { 200 | const position = this.elRef.nativeElement.getBoundingClientRect() 201 | 202 | const pageY = 203 | event.pageY !== undefined ? event.pageY : event.touches[0].pageY 204 | 205 | return pageY - position.top - window.pageYOffset 206 | } 207 | 208 | private setCursor(event: any): void { 209 | const width = this.elRef.nativeElement.offsetWidth 210 | const height = this.elRef.nativeElement.offsetHeight 211 | 212 | const x = Math.max(0, Math.min(this.getX(event), width)) 213 | const y = Math.max(0, Math.min(this.getY(event), height)) 214 | 215 | if (this.rgX !== undefined && this.rgY !== undefined) { 216 | this.newValue.emit({ 217 | s: x / width, 218 | v: 1 - y / height, 219 | rgX: this.rgX, 220 | rgY: this.rgY, 221 | }) 222 | } else if (this.rgX === undefined && this.rgY !== undefined) { 223 | this.newValue.emit({ v: y / height, rgY: this.rgY }) 224 | } else if (this.rgX !== undefined && this.rgY === undefined) { 225 | this.newValue.emit({ v: x / width, rgX: this.rgX }) 226 | } 227 | } 228 | } 229 | 230 | export class SliderPosition { 231 | constructor( 232 | public h: number, 233 | public s: number, 234 | public v: number, 235 | public a: number 236 | ) {} 237 | } 238 | 239 | export class SliderDimension { 240 | constructor( 241 | public h: number, 242 | public s: number, 243 | public v: number, 244 | public a: number 245 | ) {} 246 | } 247 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Angular Color Picker 2 | 3 | npm version 4 | 5 | This is a simple color picker based on the cool angular2-color-picker by Alberplz. 6 | 7 | This documentation is for the latest version which requires Angular version newer than 2 major versions prior to the current latest version. For older Angular versions you need to use an older version of this library. 8 | 9 | ### Quick links 10 | 11 | [Example application](https://zefoy.github.io/ngx-color-picker/) 12 | | 13 | [StackBlitz example](https://stackblitz.com/github/zefoy/ngx-color-picker/tree/main) 14 | 15 | ### Building the library 16 | 17 | ```bash 18 | npm install 19 | npm run build 20 | ``` 21 | 22 | ### Running the example 23 | 24 | ```bash 25 | npm install 26 | npm run start 27 | ``` 28 | 29 | ### Installing and usage 30 | 31 | ```bash 32 | npm install ngx-color-picker --save 33 | ``` 34 | 35 | ##### Import the library into your component: 36 | 37 | ```javascript 38 | import { ColorPickerDirective } from 'ngx-color-picker'; 39 | 40 | @Component({ 41 | // ... 42 | imports: [ 43 | // ... 44 | ColorPickerDirective, 45 | // ... 46 | ] 47 | }) 48 | ``` 49 | 50 | ##### Use it in your HTML template: 51 | 52 | ```html 53 | 54 | ``` 55 | 56 | ```javascript 57 | [colorPicker] // The color to show in the color picker dialog. 58 | 59 | [cpWidth] // Use this option to set color picker dialog width ('230px'). 60 | [cpHeight] // Use this option to force color picker dialog height ('auto'). 61 | 62 | [cpToggle] // Sets the default open / close state of the color picker (false). 63 | [cpDisabled] // Disables opening of the color picker dialog via toggle / events. 64 | 65 | [cpColorMode] // Dialog color mode: 'color', 'grayscale', 'presets' ('color'). 66 | 67 | [cpCmykEnabled] // Enables CMYK input format and color change event (false). 68 | 69 | [cpOutputFormat] // Output color format: 'auto', 'hex', 'rgba', 'hsla' ('auto'). 70 | [cpAlphaChannel] // Alpha mode: 'enabled', 'disabled', 'always', 'forced' ('enabled'). 71 | 72 | [cpFallbackColor] // Used when the color is not well-formed or is undefined ('#000'). 73 | 74 | [cpPosition] // Dialog position: 'auto', 'top', 'bottom', 'left', 'right', 75 | // 'top-left', 'top-right', 'bottom-left', 'bottom-right' ('auto'). 76 | [cpPositionOffset] // Dialog offset percentage relative to the directive element (0%). 77 | [cpPositionRelativeToArrow] // Dialog position is calculated relative to dialog arrow (false). 78 | 79 | [cpPresetLabel] // Label text for the preset colors if any provided ('Preset colors'). 80 | [cpPresetColors] // Array of preset colors to show in the color picker dialog ([]). 81 | 82 | [cpDisableInput] // Disables / hides the color input field from the dialog (false). 83 | 84 | [cpDialogDisplay] // Dialog positioning mode: 'popup', 'inline' ('popup'). 85 | // popup: dialog is shown as popup (fixed positioning). 86 | // inline: dialog is shown permanently (static positioning). 87 | 88 | [cpIgnoredElements] // Array of HTML elements that will be ignored when clicked ([]). 89 | 90 | [cpSaveClickOutside] // Save currently selected color when user clicks outside (true). 91 | [cpCloseClickOutside] // Close the color picker dialog when user clicks outside (true). 92 | 93 | [cpOKButton] // Show an OK / Apply button which saves the color (false). 94 | [cpOKButtonText] // Button label text shown inside the OK / Apply button ('OK'). 95 | [cpOKButtonClass] // Additional class for customizing the OK / Apply button (''). 96 | 97 | [cpCancelButton] // Show a Cancel / Reset button which resets the color (false). 98 | [cpCancelButtonText] // Button label text shown inside the Cancel / Reset button ('Cancel'). 99 | [cpCancelButtonClass] // Additional class for customizing the Cancel / Reset button (''). 100 | 101 | [cpAddColorButton] // Show an Add Color button which add the color into preset (false). 102 | [cpAddColorButtonText] // Button label text shown inside the Add Color button ('Add color'). 103 | [cpAddColorButtonClass] // Additional class for customizing the Add Color button (''). 104 | 105 | [cpRemoveColorButtonClass] // Additional class for customizing the Remove Color button (''). 106 | 107 | [cpPresetColorsClass] // Additional class for customizing the Preset Colors container (''). 108 | 109 | [cpMaxPresetColorsLength] // Use this option to set the max colors allowed in presets (null). 110 | 111 | [cpPresetEmptyMessage] // Message for empty colors if any provided used ('No colors added'). 112 | [cpPresetEmptyMessageClass] // Additional class for customizing the empty colors message (''). 113 | 114 | [cpUseRootViewContainer] // Create dialog component in the root view container (false). 115 | // Note: The root component needs to have public viewContainerRef. 116 | 117 | (colorPickerOpen) // Current color value, send when dialog is opened (value: string). 118 | (colorPickerClose) // Current color value, send when dialog is closed (value: string). 119 | 120 | (colorPickerChange) // Changed color value, send when color is changed (value: string). 121 | (colorPickerCancel) // Color select canceled, send when Cancel button is pressed (void). 122 | (colorPickerSelect) // Selected color value, send when OK button is pressed (value: string). 123 | 124 | (cpToggleChange) // Status of the dialog, send when dialog is opened / closed (open: boolean). 125 | 126 | (cpInputChange) // Input name and its value, send when user changes color through inputs 127 | // ({input: string, value: number | string, color: string}) 128 | 129 | (cpSliderChange) // Slider name and its value, send when user changes color through slider 130 | // ({slider: string, value: number | string, color: string}) 131 | (cpSliderDragStart) // Slider name and current color, send when slider dragging starts (mousedown,touchstart) 132 | // ({slider: string, color: string}) 133 | (cpSliderDragEnd) // Slider name and current color, send when slider dragging ends (mouseup,touchend) 134 | // ({slider: string, color: string}) 135 | 136 | (cpCmykColorChange) // Outputs the color as CMYK string if CMYK is enabled (value: string). 137 | 138 | (cpPresetColorsChange) // Preset colors, send when 'Add Color' button is pressed (value: array). 139 | ``` 140 | 141 | ##### Available control / helper functions (provided by the directive): 142 | 143 | ```javascript 144 | openDialog() // Opens the color picker dialog if not already open. 145 | closeDialog() // Closes the color picker dialog if not already closed. 146 | ``` 147 | -------------------------------------------------------------------------------- /projects/lib/README.md: -------------------------------------------------------------------------------- 1 | # Angular Color Picker 2 | 3 | npm version 4 | 5 | This is a simple color picker based on the cool angular2-color-picker by Alberplz. 6 | 7 | This documentation is for the latest 5/6.x.x version which requires Angular 5 or newer. For Angular 4 you need to use the latest 4.x.x version. Documentation for the 4.x.x can be found from here. 8 | 9 | ### Quick links 10 | 11 | [Example application](https://zefoy.github.io/ngx-color-picker/) 12 | | 13 | [StackBlitz example](https://stackblitz.com/github/zefoy/ngx-color-picker/tree/master) 14 | 15 | ### Building the library 16 | 17 | ```bash 18 | npm install 19 | npm run build 20 | ``` 21 | 22 | ### Running the example 23 | 24 | ```bash 25 | npm install 26 | npm run start 27 | ``` 28 | 29 | ### Installing and usage 30 | 31 | ```bash 32 | npm install ngx-color-picker --save 33 | ``` 34 | 35 | ##### Load the module for your app: 36 | 37 | ```javascript 38 | import { ColorPickerModule } from 'ngx-color-picker'; 39 | 40 | @NgModule({ 41 | ... 42 | imports: [ 43 | ... 44 | ColorPickerModule 45 | ] 46 | }) 47 | ``` 48 | 49 | ##### Use it in your HTML template: 50 | 51 | ```html 52 | 53 | ``` 54 | 55 | ```javascript 56 | [colorPicker] // The color to show in the color picker dialog. 57 | 58 | [cpWidth] // Use this option to set color picker dialog width ('230px'). 59 | [cpHeight] // Use this option to force color picker dialog height ('auto'). 60 | 61 | [cpToggle] // Sets the default open / close state of the color picker (false). 62 | [cpDisabled] // Disables opening of the color picker dialog via toggle / events. 63 | 64 | [cpColorMode] // Dialog color mode: 'color', 'grayscale', 'presets' ('color'). 65 | 66 | [cpCmykEnabled] // Enables CMYK input format and color change event (false). 67 | 68 | [cpOutputFormat] // Output color format: 'auto', 'hex', 'rgba', 'hsla' ('auto'). 69 | [cpAlphaChannel] // Alpha mode: 'enabled', 'disabled', 'always', 'forced' ('enabled'). 70 | 71 | [cpFallbackColor] // Used when the color is not well-formed or is undefined ('#000'). 72 | 73 | [cpPosition] // Dialog position: 'auto', 'top', 'bottom', 'left', 'right', 74 | // 'top-left', 'top-right', 'bottom-left', 'bottom-right' ('auto'). 75 | [cpPositionOffset] // Dialog offset percentage relative to the directive element (0%). 76 | [cpPositionRelativeToArrow] // Dialog position is calculated relative to dialog arrow (false). 77 | 78 | [cpPresetLabel] // Label text for the preset colors if any provided ('Preset colors'). 79 | [cpPresetColors] // Array of preset colors to show in the color picker dialog ([]). 80 | 81 | [cpDisableInput] // Disables / hides the color input field from the dialog (false). 82 | 83 | [cpDialogDisplay] // Dialog positioning mode: 'popup', 'inline' ('popup'). 84 | // popup: dialog is shown as popup (fixed positioning). 85 | // inline: dialog is shown permanently (static positioning). 86 | 87 | [cpIgnoredElements] // Array of HTML elements that will be ignored when clicked ([]). 88 | 89 | [cpSaveClickOutside] // Save currently selected color when user clicks outside (true). 90 | [cpCloseClickOutside] // Close the color picker dialog when user clicks outside (true). 91 | 92 | [cpOKButton] // Show an OK / Apply button which saves the color (false). 93 | [cpOKButtonText] // Button label text shown inside the OK / Apply button ('OK'). 94 | [cpOKButtonClass] // Additional class for customizing the OK / Apply button (''). 95 | 96 | [cpCancelButton] // Show a Cancel / Reset button which resets the color (false). 97 | [cpCancelButtonText] // Button label text shown inside the Cancel / Reset button ('Cancel'). 98 | [cpCancelButtonClass] // Additional class for customizing the Cancel / Reset button (''). 99 | 100 | [cpAddColorButton] // Show an Add Color button which add the color into preset (false). 101 | [cpAddColorButtonText] // Button label text shown inside the Add Color button ('Add color'). 102 | [cpAddColorButtonClass] // Additional class for customizing the Add Color button (''). 103 | 104 | [cpRemoveColorButtonClass] // Additional class for customizing the Remove Color button (''). 105 | 106 | [cpPresetColorsClass] // Additional class for customizing the Preset Colors container (''). 107 | 108 | [cpMaxPresetColorsLength] // Use this option to set the max colors allowed in presets (null). 109 | 110 | [cpPresetEmptyMessage] // Message for empty colors if any provided used ('No colors added'). 111 | [cpPresetEmptyMessageClass] // Additional class for customizing the empty colors message (''). 112 | 113 | [cpUseRootViewContainer] // Create dialog component in the root view container (false). 114 | // Note: The root component needs to have public viewContainerRef. 115 | 116 | (colorPickerOpen) // Current color value, send when dialog is opened (value: string). 117 | (colorPickerClose) // Current color value, send when dialog is closed (value: string). 118 | 119 | (colorPickerChange) // Changed color value, send when color is changed (value: string). 120 | (colorPickerCancel) // Color select canceled, send when Cancel button is pressed (void). 121 | (colorPickerSelect) // Selected color value, send when OK button is pressed (value: string). 122 | 123 | (cpToggleChange) // Status of the dialog, send when dialog is opened / closed (open: boolean). 124 | 125 | (cpInputChange) // Input name and its value, send when user changes color through inputs 126 | // ({input: string, value: number | string, color: string}) 127 | 128 | (cpSliderChange) // Slider name and its value, send when user changes color through slider 129 | // ({slider: string, value: number | string, color: string}) 130 | (cpSliderDragStart) // Slider name and current color, send when slider dragging starts (mousedown,touchstart) 131 | // ({slider: string, color: string}) 132 | (cpSliderDragEnd) // Slider name and current color, send when slider dragging ends (mouseup,touchend) 133 | // ({slider: string, color: string}) 134 | 135 | (cpCmykColorChange) // Outputs the color as CMYK string if CMYK is enabled (value: string). 136 | 137 | (cpPresetColorsChange) // Preset colors, send when 'Add Color' button is pressed (value: array). 138 | ``` 139 | 140 | ##### Available control / helper functions (provided by the directive): 141 | 142 | ```javascript 143 | openDialog() // Opens the color picker dialog if not already open. 144 | closeDialog() // Closes the color picker dialog if not already closed. 145 | ``` 146 | -------------------------------------------------------------------------------- /.stylelintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | "stylelint-order" 4 | ], 5 | "extends": [ 6 | "stylelint-config-standard" 7 | ], 8 | "rules": { 9 | "color-hex-case": "lower", 10 | "color-no-invalid-hex": true, 11 | 12 | "font-family-no-missing-generic-family-keyword": null, 13 | "function-calc-no-unspaced-operator": true, 14 | "function-comma-space-after": "always-single-line", 15 | "function-comma-space-before": "never", 16 | "function-name-case": "lower", 17 | "function-url-quotes": "always", 18 | "function-whitespace-after": "always", 19 | 20 | "number-leading-zero": "always", 21 | "number-no-trailing-zeros": true, 22 | "length-zero-no-unit": true, 23 | 24 | "string-no-newline": true, 25 | "string-quotes": "single", 26 | 27 | "unit-case": "lower", 28 | "unit-no-unknown": true, 29 | "unit-whitelist": [ 30 | "px", 31 | "%", 32 | "deg", 33 | "ms", 34 | "em" 35 | ], 36 | 37 | "value-list-comma-space-after": "always-single-line", 38 | "value-list-comma-space-before": "never", 39 | 40 | "shorthand-property-no-redundant-values": true, 41 | 42 | "property-case": "lower", 43 | 44 | "at-rule-empty-line-before": [ 45 | "always", 46 | { 47 | "ignore": [ 48 | "blockless-after-blockless" 49 | ] 50 | } 51 | ], 52 | 53 | "at-rule-no-unknown": null, 54 | 55 | "declaration-block-no-duplicate-properties": true, 56 | "declaration-block-trailing-semicolon": "always", 57 | "declaration-block-single-line-max-declarations": 1, 58 | "declaration-block-semicolon-space-before": "never", 59 | "declaration-block-semicolon-space-after": "always-single-line", 60 | "declaration-block-semicolon-newline-before": "never-multi-line", 61 | "declaration-block-semicolon-newline-after": "always-multi-line", 62 | 63 | "block-closing-brace-newline-after": "always", 64 | "block-closing-brace-newline-before": "always-multi-line", 65 | "block-no-empty": true, 66 | "block-opening-brace-newline-after": "always-multi-line", 67 | "block-opening-brace-space-before": "always-multi-line", 68 | 69 | "selector-attribute-brackets-space-inside": "never", 70 | "selector-attribute-operator-space-after": "never", 71 | "selector-attribute-operator-space-before": "never", 72 | "selector-combinator-space-after": "always", 73 | "selector-combinator-space-before": "always", 74 | "selector-pseudo-class-case": "lower", 75 | "selector-pseudo-class-parentheses-space-inside": "never", 76 | "selector-pseudo-element-case": "lower", 77 | "selector-pseudo-element-colon-notation": "double", 78 | "selector-pseudo-element-no-unknown": true, 79 | "selector-type-no-unknown": null, 80 | "selector-type-case": "lower", 81 | "selector-max-id": 0, 82 | 83 | "declaration-empty-line-before": null, 84 | 85 | "no-descending-specificity": null, 86 | 87 | "order/properties-order": [ 88 | [ 89 | { 90 | "properties": [ 91 | "content", 92 | "direction" 93 | ] 94 | }, { 95 | "emptyLineBefore": "always", 96 | "properties": [ 97 | "float", 98 | "position", 99 | "z-index", 100 | "top", 101 | "right", 102 | "bottom", 103 | "left" 104 | ] 105 | }, { 106 | "emptyLineBefore": "always", 107 | "properties": [ 108 | "visibility", 109 | "opacity", 110 | "display", 111 | "overflow", 112 | "box-sizing", 113 | "flex", 114 | "flex-basis", 115 | "flex-direction", 116 | "flex-flow", 117 | "flex-grow", 118 | "flex-shrink", 119 | "flex-wrap", 120 | "align-self", 121 | "align-items", 122 | "align-content", 123 | "justify-content", 124 | "order", 125 | "width", 126 | "height", 127 | "min-width", 128 | "min-height", 129 | "max-width", 130 | "max-height", 131 | "padding", 132 | "padding-top", 133 | "padding-right", 134 | "padding-bottom", 135 | "padding-left", 136 | "margin", 137 | "margin-top", 138 | "margin-right", 139 | "margin-bottom", 140 | "margin-left", 141 | "border", 142 | "border-top", 143 | "border-right", 144 | "border-bottom", 145 | "border-left", 146 | "border-width", 147 | "border-top-width", 148 | "border-right-width", 149 | "border-bottom-width", 150 | "border-left-width", 151 | "border-style", 152 | "border-top-style", 153 | "border-right-style", 154 | "border-bottom-style", 155 | "border-left-style", 156 | "border-color", 157 | "border-top-color", 158 | "border-right-color", 159 | "border-bottom-color", 160 | "border-left-color", 161 | "border-radius", 162 | "border-top-left-radius", 163 | "border-top-right-radius", 164 | "border-bottom-right-radius", 165 | "border-bottom-left-radius", 166 | "outline" 167 | ] 168 | }, { 169 | "emptyLineBefore": "always", 170 | "properties": [ 171 | "cursor", 172 | "resize", 173 | "user-select", 174 | "touch-action", 175 | "pointer-events", 176 | "font-size", 177 | "font-style", 178 | "font-weight", 179 | "font-family", 180 | "line-height", 181 | "white-space", 182 | "text-align", 183 | "text-shadow", 184 | "text-decoration", 185 | "text-transform", 186 | "text-overflow", 187 | "letter-spacing", 188 | "list-style-type", 189 | "object-fit", 190 | "vertical-align", 191 | "color", 192 | "background", 193 | "background-size", 194 | "background-color", 195 | "background-image", 196 | "background-repeat", 197 | "background-position", 198 | "background-attachment", 199 | "box-shadow" 200 | ] 201 | }, { 202 | "emptyLineBefore": "always", 203 | "properties": [ 204 | "filter", 205 | "animation", 206 | "animation-name", 207 | "animation-delay", 208 | "animation-duration", 209 | "animation-direction", 210 | "animation-fill-mode", 211 | "animation-play-state", 212 | "animation-iteration-count", 213 | "animation-timing-function", 214 | "transform", 215 | "transform-style", 216 | "transform-origin", 217 | "transition", 218 | "transition-delay", 219 | "transition-duration", 220 | "transition-property", 221 | "transition-timing-function" 222 | ] 223 | } 224 | ], 225 | { 226 | "unspecified": "bottomAlphabetical" 227 | } 228 | ] 229 | } 230 | } 231 | -------------------------------------------------------------------------------- /projects/app/stylelint.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | "stylelint-order" 4 | ], 5 | "extends": [ 6 | "stylelint-config-standard" 7 | ], 8 | "rules": { 9 | "color-hex-case": "lower", 10 | "color-no-invalid-hex": true, 11 | 12 | "font-family-no-missing-generic-family-keyword": null, 13 | "function-calc-no-unspaced-operator": true, 14 | "function-comma-space-after": "always-single-line", 15 | "function-comma-space-before": "never", 16 | "function-name-case": "lower", 17 | "function-url-quotes": "always", 18 | "function-whitespace-after": "always", 19 | 20 | "number-leading-zero": "always", 21 | "number-no-trailing-zeros": true, 22 | "length-zero-no-unit": true, 23 | 24 | "string-no-newline": true, 25 | "string-quotes": "single", 26 | 27 | "unit-case": "lower", 28 | "unit-no-unknown": true, 29 | "unit-whitelist": [ 30 | "px", 31 | "%", 32 | "deg", 33 | "ms", 34 | "em" 35 | ], 36 | 37 | "value-list-comma-space-after": "always-single-line", 38 | "value-list-comma-space-before": "never", 39 | 40 | "shorthand-property-no-redundant-values": true, 41 | 42 | "property-case": "lower", 43 | 44 | "at-rule-empty-line-before": [ 45 | "always", 46 | { 47 | "ignore": [ 48 | "blockless-after-blockless" 49 | ] 50 | } 51 | ], 52 | 53 | "at-rule-no-unknown": null, 54 | 55 | "declaration-block-no-duplicate-properties": true, 56 | "declaration-block-trailing-semicolon": "always", 57 | "declaration-block-single-line-max-declarations": 1, 58 | "declaration-block-semicolon-space-before": "never", 59 | "declaration-block-semicolon-space-after": "always-single-line", 60 | "declaration-block-semicolon-newline-before": "never-multi-line", 61 | "declaration-block-semicolon-newline-after": "always-multi-line", 62 | 63 | "block-closing-brace-newline-after": "always", 64 | "block-closing-brace-newline-before": "always-multi-line", 65 | "block-no-empty": true, 66 | "block-opening-brace-newline-after": "always-multi-line", 67 | "block-opening-brace-space-before": "always-multi-line", 68 | 69 | "selector-attribute-brackets-space-inside": "never", 70 | "selector-attribute-operator-space-after": "never", 71 | "selector-attribute-operator-space-before": "never", 72 | "selector-combinator-space-after": "always", 73 | "selector-combinator-space-before": "always", 74 | "selector-pseudo-class-case": "lower", 75 | "selector-pseudo-class-parentheses-space-inside": "never", 76 | "selector-pseudo-element-case": "lower", 77 | "selector-pseudo-element-colon-notation": "double", 78 | "selector-pseudo-element-no-unknown": true, 79 | "selector-type-no-unknown": null, 80 | "selector-type-case": "lower", 81 | "selector-max-id": 0, 82 | 83 | "declaration-empty-line-before": null, 84 | 85 | "no-descending-specificity": null, 86 | 87 | "order/properties-order": [ 88 | [ 89 | { 90 | "properties": [ 91 | "content", 92 | "direction" 93 | ] 94 | }, { 95 | "emptyLineBefore": "always", 96 | "properties": [ 97 | "float", 98 | "position", 99 | "z-index", 100 | "top", 101 | "right", 102 | "bottom", 103 | "left" 104 | ] 105 | }, { 106 | "emptyLineBefore": "always", 107 | "properties": [ 108 | "visibility", 109 | "opacity", 110 | "display", 111 | "overflow", 112 | "box-sizing", 113 | "flex", 114 | "flex-basis", 115 | "flex-direction", 116 | "flex-flow", 117 | "flex-grow", 118 | "flex-shrink", 119 | "flex-wrap", 120 | "align-self", 121 | "align-items", 122 | "align-content", 123 | "justify-content", 124 | "order", 125 | "width", 126 | "height", 127 | "min-width", 128 | "min-height", 129 | "max-width", 130 | "max-height", 131 | "padding", 132 | "padding-top", 133 | "padding-right", 134 | "padding-bottom", 135 | "padding-left", 136 | "margin", 137 | "margin-top", 138 | "margin-right", 139 | "margin-bottom", 140 | "margin-left", 141 | "border", 142 | "border-top", 143 | "border-right", 144 | "border-bottom", 145 | "border-left", 146 | "border-width", 147 | "border-top-width", 148 | "border-right-width", 149 | "border-bottom-width", 150 | "border-left-width", 151 | "border-style", 152 | "border-top-style", 153 | "border-right-style", 154 | "border-bottom-style", 155 | "border-left-style", 156 | "border-color", 157 | "border-top-color", 158 | "border-right-color", 159 | "border-bottom-color", 160 | "border-left-color", 161 | "border-radius", 162 | "border-top-left-radius", 163 | "border-top-right-radius", 164 | "border-bottom-right-radius", 165 | "border-bottom-left-radius", 166 | "outline" 167 | ] 168 | }, { 169 | "emptyLineBefore": "always", 170 | "properties": [ 171 | "cursor", 172 | "resize", 173 | "user-select", 174 | "touch-action", 175 | "pointer-events", 176 | "font-size", 177 | "font-style", 178 | "font-weight", 179 | "font-family", 180 | "line-height", 181 | "white-space", 182 | "text-align", 183 | "text-shadow", 184 | "text-decoration", 185 | "text-transform", 186 | "text-overflow", 187 | "letter-spacing", 188 | "list-style-type", 189 | "object-fit", 190 | "vertical-align", 191 | "color", 192 | "background", 193 | "background-size", 194 | "background-color", 195 | "background-image", 196 | "background-repeat", 197 | "background-position", 198 | "background-attachment", 199 | "box-shadow" 200 | ] 201 | }, { 202 | "emptyLineBefore": "always", 203 | "properties": [ 204 | "filter", 205 | "animation", 206 | "animation-name", 207 | "animation-delay", 208 | "animation-duration", 209 | "animation-direction", 210 | "animation-fill-mode", 211 | "animation-play-state", 212 | "animation-iteration-count", 213 | "animation-timing-function", 214 | "transform", 215 | "transform-style", 216 | "transform-origin", 217 | "transition", 218 | "transition-delay", 219 | "transition-duration", 220 | "transition-property", 221 | "transition-timing-function" 222 | ] 223 | } 224 | ], 225 | { 226 | "unspecified": "bottomAlphabetical" 227 | } 228 | ] 229 | } 230 | } 231 | -------------------------------------------------------------------------------- /projects/lib/stylelint.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | "stylelint-order" 4 | ], 5 | "extends": [ 6 | "stylelint-config-standard" 7 | ], 8 | "rules": { 9 | "color-hex-case": "lower", 10 | "color-no-invalid-hex": true, 11 | 12 | "font-family-no-missing-generic-family-keyword": null, 13 | "function-calc-no-unspaced-operator": true, 14 | "function-comma-space-after": "always-single-line", 15 | "function-comma-space-before": "never", 16 | "function-name-case": "lower", 17 | "function-url-quotes": "always", 18 | "function-whitespace-after": "always", 19 | 20 | "number-leading-zero": "always", 21 | "number-no-trailing-zeros": true, 22 | "length-zero-no-unit": true, 23 | 24 | "string-no-newline": true, 25 | "string-quotes": "single", 26 | 27 | "unit-case": "lower", 28 | "unit-no-unknown": true, 29 | "unit-whitelist": [ 30 | "px", 31 | "%", 32 | "deg", 33 | "ms", 34 | "em" 35 | ], 36 | 37 | "value-list-comma-space-after": "always-single-line", 38 | "value-list-comma-space-before": "never", 39 | 40 | "shorthand-property-no-redundant-values": true, 41 | 42 | "property-case": "lower", 43 | 44 | "at-rule-empty-line-before": [ 45 | "always", 46 | { 47 | "ignore": [ 48 | "blockless-after-blockless" 49 | ] 50 | } 51 | ], 52 | 53 | "at-rule-no-unknown": null, 54 | 55 | "declaration-block-no-duplicate-properties": true, 56 | "declaration-block-trailing-semicolon": "always", 57 | "declaration-block-single-line-max-declarations": 1, 58 | "declaration-block-semicolon-space-before": "never", 59 | "declaration-block-semicolon-space-after": "always-single-line", 60 | "declaration-block-semicolon-newline-before": "never-multi-line", 61 | "declaration-block-semicolon-newline-after": "always-multi-line", 62 | 63 | "block-closing-brace-newline-after": "always", 64 | "block-closing-brace-newline-before": "always-multi-line", 65 | "block-no-empty": true, 66 | "block-opening-brace-newline-after": "always-multi-line", 67 | "block-opening-brace-space-before": "always-multi-line", 68 | 69 | "selector-attribute-brackets-space-inside": "never", 70 | "selector-attribute-operator-space-after": "never", 71 | "selector-attribute-operator-space-before": "never", 72 | "selector-combinator-space-after": "always", 73 | "selector-combinator-space-before": "always", 74 | "selector-pseudo-class-case": "lower", 75 | "selector-pseudo-class-parentheses-space-inside": "never", 76 | "selector-pseudo-element-case": "lower", 77 | "selector-pseudo-element-colon-notation": "double", 78 | "selector-pseudo-element-no-unknown": true, 79 | "selector-type-no-unknown": null, 80 | "selector-type-case": "lower", 81 | "selector-max-id": 0, 82 | 83 | "declaration-empty-line-before": null, 84 | 85 | "no-descending-specificity": null, 86 | 87 | "order/properties-order": [ 88 | [ 89 | { 90 | "properties": [ 91 | "content", 92 | "direction" 93 | ] 94 | }, { 95 | "emptyLineBefore": "always", 96 | "properties": [ 97 | "float", 98 | "position", 99 | "z-index", 100 | "top", 101 | "right", 102 | "bottom", 103 | "left" 104 | ] 105 | }, { 106 | "emptyLineBefore": "always", 107 | "properties": [ 108 | "visibility", 109 | "opacity", 110 | "display", 111 | "overflow", 112 | "box-sizing", 113 | "flex", 114 | "flex-basis", 115 | "flex-direction", 116 | "flex-flow", 117 | "flex-grow", 118 | "flex-shrink", 119 | "flex-wrap", 120 | "align-self", 121 | "align-items", 122 | "align-content", 123 | "justify-content", 124 | "order", 125 | "width", 126 | "height", 127 | "min-width", 128 | "min-height", 129 | "max-width", 130 | "max-height", 131 | "padding", 132 | "padding-top", 133 | "padding-right", 134 | "padding-bottom", 135 | "padding-left", 136 | "margin", 137 | "margin-top", 138 | "margin-right", 139 | "margin-bottom", 140 | "margin-left", 141 | "border", 142 | "border-top", 143 | "border-right", 144 | "border-bottom", 145 | "border-left", 146 | "border-width", 147 | "border-top-width", 148 | "border-right-width", 149 | "border-bottom-width", 150 | "border-left-width", 151 | "border-style", 152 | "border-top-style", 153 | "border-right-style", 154 | "border-bottom-style", 155 | "border-left-style", 156 | "border-color", 157 | "border-top-color", 158 | "border-right-color", 159 | "border-bottom-color", 160 | "border-left-color", 161 | "border-radius", 162 | "border-top-left-radius", 163 | "border-top-right-radius", 164 | "border-bottom-right-radius", 165 | "border-bottom-left-radius", 166 | "outline" 167 | ] 168 | }, { 169 | "emptyLineBefore": "always", 170 | "properties": [ 171 | "cursor", 172 | "resize", 173 | "user-select", 174 | "touch-action", 175 | "pointer-events", 176 | "font-size", 177 | "font-style", 178 | "font-weight", 179 | "font-family", 180 | "line-height", 181 | "white-space", 182 | "text-align", 183 | "text-shadow", 184 | "text-decoration", 185 | "text-transform", 186 | "text-overflow", 187 | "letter-spacing", 188 | "list-style-type", 189 | "object-fit", 190 | "vertical-align", 191 | "color", 192 | "background", 193 | "background-size", 194 | "background-color", 195 | "background-image", 196 | "background-repeat", 197 | "background-position", 198 | "background-attachment", 199 | "box-shadow" 200 | ] 201 | }, { 202 | "emptyLineBefore": "always", 203 | "properties": [ 204 | "filter", 205 | "animation", 206 | "animation-name", 207 | "animation-delay", 208 | "animation-duration", 209 | "animation-direction", 210 | "animation-fill-mode", 211 | "animation-play-state", 212 | "animation-iteration-count", 213 | "animation-timing-function", 214 | "transform", 215 | "transform-style", 216 | "transform-origin", 217 | "transition", 218 | "transition-delay", 219 | "transition-duration", 220 | "transition-property", 221 | "transition-timing-function" 222 | ] 223 | } 224 | ], 225 | { 226 | "unspecified": "bottomAlphabetical" 227 | } 228 | ] 229 | } 230 | } 231 | -------------------------------------------------------------------------------- /projects/lib/src/lib/color-picker.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 |
6 |
7 | 8 |
9 |
10 |
11 | 12 |
13 | 14 |
15 | 16 | 19 |
20 | 21 |
22 |
23 | 24 |
25 |
26 |
27 | 28 |
29 |
30 |
31 | 32 |
33 |
34 |
35 |
36 |
37 | 38 |
39 |
40 | 41 | 42 | 43 | 44 | 45 |
46 | 47 |
48 |
C
M
Y
K
A
49 |
50 |
51 | 52 |
53 |
54 | 55 | 56 | 57 | 58 |
59 | 60 |
61 |
H
S
L
A
62 |
63 |
64 | 65 |
66 |
67 | 68 | 69 | 70 | 71 |
72 | 73 |
74 |
R
G
B
A
75 |
76 |
77 | 78 |
80 |
81 | 82 | 83 |
84 | 85 |
86 |
Hex
87 |
A
88 |
89 |
90 | 91 |
92 |
93 | 94 | 95 |
96 | 97 |
98 |
V
A
99 |
100 |
101 | 102 |
103 | 104 | 105 |
106 | 107 |
108 |
109 | 110 |
{{cpPresetLabel}}
111 | 112 |
113 |
114 | 115 |
116 |
117 | 118 |
{{cpPresetEmptyMessage}}
119 |
120 | 121 |
122 | 123 | 124 | 125 |
126 | 127 |
128 | 129 |
130 |
131 | -------------------------------------------------------------------------------- /projects/lib/src/lib/color-picker.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core' 2 | 3 | import { Cmyk, Rgba, Hsla, Hsva } from './formats' 4 | 5 | import { ColorPickerComponent } from './color-picker.component' 6 | 7 | @Injectable({ 8 | providedIn: 'root', 9 | }) 10 | export class ColorPickerService { 11 | private active: ColorPickerComponent | null = null 12 | 13 | public setActive(active: ColorPickerComponent | null): void { 14 | if ( 15 | this.active && 16 | this.active !== active && 17 | this.active.cpDialogDisplay !== 'inline' 18 | ) { 19 | this.active.closeDialog() 20 | } 21 | 22 | this.active = active 23 | } 24 | 25 | public hsva2hsla(hsva: Hsva): Hsla { 26 | const h = hsva.h, 27 | s = hsva.s, 28 | v = hsva.v, 29 | a = hsva.a 30 | 31 | if (v === 0) { 32 | return new Hsla(h, 0, 0, a) 33 | } else if (s === 0 && v === 1) { 34 | return new Hsla(h, 1, 1, a) 35 | } else { 36 | const l = (v * (2 - s)) / 2 37 | 38 | return new Hsla(h, (v * s) / (1 - Math.abs(2 * l - 1)), l, a) 39 | } 40 | } 41 | 42 | public hsla2hsva(hsla: Hsla): Hsva { 43 | const h = Math.min(hsla.h, 1), 44 | s = Math.min(hsla.s, 1) 45 | const l = Math.min(hsla.l, 1), 46 | a = Math.min(hsla.a, 1) 47 | 48 | if (l === 0) { 49 | return new Hsva(h, 0, 0, a) 50 | } else { 51 | const v = l + (s * (1 - Math.abs(2 * l - 1))) / 2 52 | 53 | return new Hsva(h, (2 * (v - l)) / v, v, a) 54 | } 55 | } 56 | 57 | public hsvaToRgba(hsva: Hsva): Rgba { 58 | let r: number, g: number, b: number 59 | 60 | const h = hsva.h, 61 | s = hsva.s, 62 | v = hsva.v, 63 | a = hsva.a 64 | 65 | const i = Math.floor(h * 6) 66 | const f = h * 6 - i 67 | const p = v * (1 - s) 68 | const q = v * (1 - f * s) 69 | const t = v * (1 - (1 - f) * s) 70 | 71 | switch (i % 6) { 72 | case 0: 73 | ;(r = v), (g = t), (b = p) 74 | break 75 | case 1: 76 | ;(r = q), (g = v), (b = p) 77 | break 78 | case 2: 79 | ;(r = p), (g = v), (b = t) 80 | break 81 | case 3: 82 | ;(r = p), (g = q), (b = v) 83 | break 84 | case 4: 85 | ;(r = t), (g = p), (b = v) 86 | break 87 | case 5: 88 | ;(r = v), (g = p), (b = q) 89 | break 90 | default: 91 | ;(r = 0), (g = 0), (b = 0) 92 | } 93 | 94 | return new Rgba(r, g, b, a) 95 | } 96 | 97 | public cmykToRgb(cmyk: Cmyk): Rgba { 98 | const r = (1 - cmyk.c) * (1 - cmyk.k) 99 | const g = (1 - cmyk.m) * (1 - cmyk.k) 100 | const b = (1 - cmyk.y) * (1 - cmyk.k) 101 | 102 | return new Rgba(r, g, b, cmyk.a) 103 | } 104 | 105 | public rgbaToCmyk(rgba: Rgba): Cmyk { 106 | const k: number = 1 - Math.max(rgba.r, rgba.g, rgba.b) 107 | 108 | if (k === 1) { 109 | return new Cmyk(0, 0, 0, 1, rgba.a) 110 | } else { 111 | const c = (1 - rgba.r - k) / (1 - k) 112 | const m = (1 - rgba.g - k) / (1 - k) 113 | const y = (1 - rgba.b - k) / (1 - k) 114 | 115 | return new Cmyk(c, m, y, k, rgba.a) 116 | } 117 | } 118 | 119 | public rgbaToHsva(rgba: Rgba): Hsva { 120 | let h: number, s: number 121 | 122 | const r = Math.min(rgba.r, 1), 123 | g = Math.min(rgba.g, 1) 124 | const b = Math.min(rgba.b, 1), 125 | a = Math.min(rgba.a, 1) 126 | 127 | const max = Math.max(r, g, b), 128 | min = Math.min(r, g, b) 129 | 130 | const v: number = max, 131 | d = max - min 132 | 133 | s = max === 0 ? 0 : d / max 134 | 135 | if (max === min) { 136 | h = 0 137 | } else { 138 | switch (max) { 139 | case r: 140 | h = (g - b) / d + (g < b ? 6 : 0) 141 | break 142 | case g: 143 | h = (b - r) / d + 2 144 | break 145 | case b: 146 | h = (r - g) / d + 4 147 | break 148 | default: 149 | h = 0 150 | } 151 | 152 | h /= 6 153 | } 154 | 155 | return new Hsva(h, s, v, a) 156 | } 157 | 158 | public rgbaToHex(rgba: Rgba, allowHex8?: boolean): string { 159 | /* eslint-disable no-bitwise */ 160 | let hex = 161 | '#' + 162 | ((1 << 24) | (rgba.r << 16) | (rgba.g << 8) | rgba.b) 163 | .toString(16) 164 | .substr(1) 165 | 166 | if (allowHex8) { 167 | hex += ((1 << 8) | Math.round(rgba.a * 255)).toString(16).substr(1) 168 | } 169 | /* eslint-enable no-bitwise */ 170 | 171 | return hex 172 | } 173 | 174 | public normalizeCMYK(cmyk: Cmyk): Cmyk { 175 | return new Cmyk( 176 | cmyk.c / 100, 177 | cmyk.m / 100, 178 | cmyk.y / 100, 179 | cmyk.k / 100, 180 | cmyk.a 181 | ) 182 | } 183 | 184 | public denormalizeCMYK(cmyk: Cmyk): Cmyk { 185 | return new Cmyk( 186 | Math.floor(cmyk.c * 100), 187 | Math.floor(cmyk.m * 100), 188 | Math.floor(cmyk.y * 100), 189 | Math.floor(cmyk.k * 100), 190 | cmyk.a 191 | ) 192 | } 193 | 194 | public denormalizeRGBA(rgba: Rgba): Rgba { 195 | return new Rgba( 196 | Math.round(rgba.r * 255), 197 | Math.round(rgba.g * 255), 198 | Math.round(rgba.b * 255), 199 | rgba.a 200 | ) 201 | } 202 | 203 | public stringToHsva( 204 | colorString: string = '', 205 | allowHex8: boolean = false 206 | ): Hsva | null { 207 | let hsva: Hsva | null = null 208 | 209 | colorString = (colorString || '').toLowerCase() 210 | 211 | const stringParsers = [ 212 | { 213 | re: /(rgb)a?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*%?,\s*(\d{1,3})\s*%?(?:,\s*(\d+(?:\.\d+)?)\s*)?\)/, 214 | parse: function (execResult: any) { 215 | return new Rgba( 216 | parseInt(execResult[2], 10) / 255, 217 | parseInt(execResult[3], 10) / 255, 218 | parseInt(execResult[4], 10) / 255, 219 | isNaN(parseFloat(execResult[5])) ? 1 : parseFloat(execResult[5]) 220 | ) 221 | }, 222 | }, 223 | { 224 | re: /(hsl)a?\(\s*(\d{1,3})\s*,\s*(\d{1,3})%\s*,\s*(\d{1,3})%\s*(?:,\s*(\d+(?:\.\d+)?)\s*)?\)/, 225 | parse: function (execResult: any) { 226 | return new Hsla( 227 | parseInt(execResult[2], 10) / 360, 228 | parseInt(execResult[3], 10) / 100, 229 | parseInt(execResult[4], 10) / 100, 230 | isNaN(parseFloat(execResult[5])) ? 1 : parseFloat(execResult[5]) 231 | ) 232 | }, 233 | }, 234 | ] 235 | 236 | if (allowHex8) { 237 | stringParsers.push({ 238 | re: /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})?$/, 239 | parse: function (execResult: any) { 240 | return new Rgba( 241 | parseInt(execResult[1], 16) / 255, 242 | parseInt(execResult[2], 16) / 255, 243 | parseInt(execResult[3], 16) / 255, 244 | parseInt(execResult[4] || 'FF', 16) / 255 245 | ) 246 | }, 247 | }) 248 | } else { 249 | stringParsers.push({ 250 | re: /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})$/, 251 | parse: function (execResult: any) { 252 | return new Rgba( 253 | parseInt(execResult[1], 16) / 255, 254 | parseInt(execResult[2], 16) / 255, 255 | parseInt(execResult[3], 16) / 255, 256 | 1 257 | ) 258 | }, 259 | }) 260 | } 261 | 262 | stringParsers.push({ 263 | re: /#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])$/, 264 | parse: function (execResult: any) { 265 | return new Rgba( 266 | parseInt(execResult[1] + execResult[1], 16) / 255, 267 | parseInt(execResult[2] + execResult[2], 16) / 255, 268 | parseInt(execResult[3] + execResult[3], 16) / 255, 269 | 1 270 | ) 271 | }, 272 | }) 273 | 274 | for (const key in stringParsers) { 275 | if (stringParsers.hasOwnProperty(key)) { 276 | const parser = stringParsers[key] 277 | 278 | const match = parser.re.exec(colorString), 279 | color: any = match && parser.parse(match) 280 | 281 | if (color) { 282 | if (color instanceof Rgba) { 283 | hsva = this.rgbaToHsva(color) 284 | } else if (color instanceof Hsla) { 285 | hsva = this.hsla2hsva(color) 286 | } 287 | 288 | return hsva 289 | } 290 | } 291 | } 292 | 293 | return hsva 294 | } 295 | 296 | public outputFormat( 297 | hsva: Hsva, 298 | outputFormat: string, 299 | alphaChannel: string | null 300 | ): string { 301 | if (outputFormat === 'auto') { 302 | outputFormat = hsva.a < 1 ? 'rgba' : 'hex' 303 | } 304 | 305 | switch (outputFormat) { 306 | case 'hsla': 307 | const hsla = this.hsva2hsla(hsva) 308 | 309 | const hslaText = new Hsla( 310 | Math.round(hsla.h * 360), 311 | Math.round(hsla.s * 100), 312 | Math.round(hsla.l * 100), 313 | Math.round(hsla.a * 100) / 100 314 | ) 315 | 316 | if (hsva.a < 1 || alphaChannel === 'always') { 317 | return ( 318 | 'hsla(' + 319 | hslaText.h + 320 | ',' + 321 | hslaText.s + 322 | '%,' + 323 | hslaText.l + 324 | '%,' + 325 | hslaText.a + 326 | ')' 327 | ) 328 | } else { 329 | return ( 330 | 'hsl(' + hslaText.h + ',' + hslaText.s + '%,' + hslaText.l + '%)' 331 | ) 332 | } 333 | 334 | case 'rgba': 335 | const rgba = this.denormalizeRGBA(this.hsvaToRgba(hsva)) 336 | 337 | if (hsva.a < 1 || alphaChannel === 'always') { 338 | return ( 339 | 'rgba(' + 340 | rgba.r + 341 | ',' + 342 | rgba.g + 343 | ',' + 344 | rgba.b + 345 | ',' + 346 | Math.round(rgba.a * 100) / 100 + 347 | ')' 348 | ) 349 | } else { 350 | return 'rgb(' + rgba.r + ',' + rgba.g + ',' + rgba.b + ')' 351 | } 352 | 353 | default: 354 | const allowHex8 = alphaChannel === 'always' || alphaChannel === 'forced' 355 | 356 | return this.rgbaToHex( 357 | this.denormalizeRGBA(this.hsvaToRgba(hsva)), 358 | allowHex8 359 | ) 360 | } 361 | } 362 | } 363 | -------------------------------------------------------------------------------- /projects/lib/src/lib/color-picker.directive.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Directive, 3 | OnChanges, 4 | OnDestroy, 5 | Input, 6 | Output, 7 | EventEmitter, 8 | HostListener, 9 | ApplicationRef, 10 | ComponentRef, 11 | ElementRef, 12 | ViewContainerRef, 13 | Injector, 14 | EmbeddedViewRef, 15 | TemplateRef, 16 | isDevMode, 17 | inject, 18 | } from '@angular/core' 19 | 20 | import { ColorPickerComponent } from './color-picker.component' 21 | 22 | import { AlphaChannel, ColorMode, OutputFormat } from './helpers' 23 | 24 | @Directive({ 25 | selector: '[colorPicker]', 26 | exportAs: 'ngxColorPicker', 27 | }) 28 | export class ColorPickerDirective implements OnChanges, OnDestroy { 29 | private readonly injector = inject(Injector) 30 | private readonly appRef = inject(ApplicationRef) 31 | private readonly vcRef = inject(ViewContainerRef) 32 | private readonly elRef = inject(ElementRef) 33 | 34 | private dialog: any 35 | 36 | private dialogCreated: boolean = false 37 | private ignoreChanges: boolean = false 38 | 39 | private cmpRef: ComponentRef 40 | private viewAttachedToAppRef: boolean = false 41 | 42 | @Input() colorPicker: string 43 | 44 | @Input() cpWidth: string = '230px' 45 | @Input() cpHeight: string = 'auto' 46 | 47 | @Input() cpToggle: boolean = false 48 | @Input() cpDisabled: boolean = false 49 | 50 | @Input() cpIgnoredElements: any = [] 51 | 52 | @Input() cpFallbackColor: string = '' 53 | 54 | @Input() cpColorMode: ColorMode = 'color' 55 | 56 | @Input() cpCmykEnabled: boolean = false 57 | 58 | @Input() cpOutputFormat: OutputFormat = 'auto' 59 | @Input() cpAlphaChannel: AlphaChannel = 'enabled' 60 | 61 | @Input() cpDisableInput: boolean = false 62 | 63 | @Input() cpDialogDisplay: string = 'popup' 64 | 65 | @Input() cpSaveClickOutside: boolean = true 66 | @Input() cpCloseClickOutside: boolean = true 67 | 68 | @Input() cpUseRootViewContainer: boolean = false 69 | 70 | @Input() cpPosition: string = 'auto' 71 | @Input() cpPositionOffset: string = '0%' 72 | @Input() cpPositionRelativeToArrow: boolean = false 73 | 74 | @Input() cpOKButton: boolean = false 75 | @Input() cpOKButtonText: string = 'OK' 76 | @Input() cpOKButtonClass: string = 'cp-ok-button-class' 77 | 78 | @Input() cpCancelButton: boolean = false 79 | @Input() cpCancelButtonText: string = 'Cancel' 80 | @Input() cpCancelButtonClass: string = 'cp-cancel-button-class' 81 | 82 | @Input() cpEyeDropper: boolean = false 83 | 84 | @Input() cpPresetLabel: string = 'Preset colors' 85 | @Input() cpPresetColors: string[] 86 | @Input() cpPresetColorsClass: string = 'cp-preset-colors-class' 87 | @Input() cpMaxPresetColorsLength: number = 6 88 | 89 | @Input() cpPresetEmptyMessage: string = 'No colors added' 90 | @Input() cpPresetEmptyMessageClass: string = 'preset-empty-message' 91 | 92 | @Input() cpAddColorButton: boolean = false 93 | @Input() cpAddColorButtonText: string = 'Add color' 94 | @Input() cpAddColorButtonClass: string = 'cp-add-color-button-class' 95 | 96 | @Input() cpRemoveColorButtonClass: string = 'cp-remove-color-button-class' 97 | @Input() cpArrowPosition: number = 0 98 | 99 | @Input() cpExtraTemplate: TemplateRef 100 | 101 | @Output() cpInputChange = new EventEmitter<{ 102 | input: string 103 | value: number | string 104 | color: string 105 | }>(true) 106 | 107 | @Output() cpToggleChange = new EventEmitter(true) 108 | 109 | @Output() cpSliderChange = new EventEmitter<{ 110 | slider: string 111 | value: string | number 112 | color: string 113 | }>(true) 114 | @Output() cpSliderDragEnd = new EventEmitter<{ 115 | slider: string 116 | color: string 117 | }>(true) 118 | @Output() cpSliderDragStart = new EventEmitter<{ 119 | slider: string 120 | color: string 121 | }>(true) 122 | 123 | @Output() colorPickerOpen = new EventEmitter(true) 124 | @Output() colorPickerClose = new EventEmitter(true) 125 | 126 | @Output() colorPickerCancel = new EventEmitter(true) 127 | @Output() colorPickerSelect = new EventEmitter(true) 128 | @Output() colorPickerChange = new EventEmitter(false) 129 | 130 | @Output() cpCmykColorChange = new EventEmitter(true) 131 | 132 | @Output() cpPresetColorsChange = new EventEmitter(true) 133 | 134 | @HostListener('click') handleClick(): void { 135 | this.inputFocus() 136 | } 137 | 138 | @HostListener('focus') handleFocus(): void { 139 | this.inputFocus() 140 | } 141 | 142 | @HostListener('input', ['$event']) handleInput(event: any): void { 143 | this.inputChange(event) 144 | } 145 | 146 | ngOnDestroy(): void { 147 | if (this.cmpRef != null) { 148 | if (this.viewAttachedToAppRef) { 149 | this.appRef.detachView(this.cmpRef.hostView) 150 | } 151 | 152 | this.cmpRef.destroy() 153 | 154 | this.cmpRef = null 155 | this.dialog = null 156 | } 157 | } 158 | 159 | ngOnChanges(changes: any): void { 160 | if (changes.cpToggle && !this.cpDisabled) { 161 | if (changes.cpToggle.currentValue) { 162 | this.openDialog() 163 | } else if (!changes.cpToggle.currentValue) { 164 | this.closeDialog() 165 | } 166 | } 167 | 168 | if (changes.colorPicker) { 169 | if (this.dialog && !this.ignoreChanges) { 170 | if (this.cpDialogDisplay === 'inline') { 171 | this.dialog.setInitialColor(changes.colorPicker.currentValue) 172 | } 173 | 174 | this.dialog.setColorFromString(changes.colorPicker.currentValue, false) 175 | 176 | if (this.cpUseRootViewContainer && this.cpDialogDisplay !== 'inline') { 177 | this.cmpRef.changeDetectorRef.detectChanges() 178 | } 179 | } 180 | 181 | this.ignoreChanges = false 182 | } 183 | 184 | if (changes.cpPresetLabel || changes.cpPresetColors) { 185 | if (this.dialog) { 186 | this.dialog.setPresetConfig(this.cpPresetLabel, this.cpPresetColors) 187 | } 188 | } 189 | } 190 | 191 | public openDialog(): void { 192 | if (!this.dialogCreated) { 193 | let vcRef = this.vcRef 194 | 195 | this.dialogCreated = true 196 | this.viewAttachedToAppRef = false 197 | 198 | if (this.cpUseRootViewContainer && this.cpDialogDisplay !== 'inline') { 199 | const classOfRootComponent = this.appRef.componentTypes[0] 200 | const appInstance = this.injector.get( 201 | classOfRootComponent, 202 | Injector.NULL 203 | ) 204 | 205 | if (appInstance !== Injector.NULL) { 206 | vcRef = 207 | appInstance.vcRef || appInstance.viewContainerRef || this.vcRef 208 | 209 | if (isDevMode() && vcRef === this.vcRef) { 210 | console.warn( 211 | 'You are using cpUseRootViewContainer, ' + 212 | 'but the root component is not exposing viewContainerRef!' + 213 | "Please expose it by adding 'public vcRef: ViewContainerRef' to the constructor." 214 | ) 215 | } 216 | } else { 217 | this.viewAttachedToAppRef = true 218 | } 219 | } 220 | 221 | if (this.viewAttachedToAppRef) { 222 | this.cmpRef = vcRef.createComponent(ColorPickerComponent, { 223 | injector: this.injector, 224 | }) 225 | document.body.appendChild( 226 | (this.cmpRef.hostView as EmbeddedViewRef) 227 | .rootNodes[0] as HTMLElement 228 | ) 229 | } else { 230 | const injector = Injector.create({ 231 | providers: [], 232 | // We shouldn't use `vcRef.parentInjector` since it's been deprecated long time ago and might be removed 233 | // in newer Angular versions: https://github.com/angular/angular/pull/25174. 234 | parent: vcRef.injector, 235 | }) 236 | 237 | this.cmpRef = vcRef.createComponent(ColorPickerComponent, { 238 | injector, 239 | index: 0, 240 | }) 241 | } 242 | 243 | this.cmpRef.instance.setupDialog( 244 | this, 245 | this.elRef, 246 | this.colorPicker, 247 | this.cpWidth, 248 | this.cpHeight, 249 | this.cpDialogDisplay, 250 | this.cpFallbackColor, 251 | this.cpColorMode, 252 | this.cpCmykEnabled, 253 | this.cpAlphaChannel, 254 | this.cpOutputFormat, 255 | this.cpDisableInput, 256 | this.cpIgnoredElements, 257 | this.cpSaveClickOutside, 258 | this.cpCloseClickOutside, 259 | this.cpUseRootViewContainer, 260 | this.cpPosition, 261 | this.cpPositionOffset, 262 | this.cpPositionRelativeToArrow, 263 | this.cpPresetLabel, 264 | this.cpPresetColors, 265 | this.cpPresetColorsClass, 266 | this.cpMaxPresetColorsLength, 267 | this.cpPresetEmptyMessage, 268 | this.cpPresetEmptyMessageClass, 269 | this.cpOKButton, 270 | this.cpOKButtonClass, 271 | this.cpOKButtonText, 272 | this.cpCancelButton, 273 | this.cpCancelButtonClass, 274 | this.cpCancelButtonText, 275 | this.cpAddColorButton, 276 | this.cpAddColorButtonClass, 277 | this.cpAddColorButtonText, 278 | this.cpRemoveColorButtonClass, 279 | this.cpEyeDropper, 280 | this.elRef, 281 | this.cpExtraTemplate 282 | ) 283 | 284 | this.dialog = this.cmpRef.instance 285 | 286 | if (this.vcRef !== vcRef) { 287 | this.cmpRef.changeDetectorRef.detectChanges() 288 | } 289 | } else if (this.dialog) { 290 | // Update properties. 291 | this.cmpRef.instance.cpAlphaChannel = this.cpAlphaChannel 292 | 293 | // Open dialog. 294 | this.dialog.openDialog(this.colorPicker) 295 | } 296 | } 297 | 298 | public closeDialog(): void { 299 | if (this.dialog && this.cpDialogDisplay === 'popup') { 300 | this.dialog.closeDialog() 301 | } 302 | } 303 | 304 | public cmykChanged(value: string): void { 305 | this.cpCmykColorChange.emit(value) 306 | } 307 | 308 | public stateChanged(state: boolean): void { 309 | this.cpToggleChange.emit(state) 310 | 311 | if (state) { 312 | this.colorPickerOpen.emit(this.colorPicker) 313 | } else { 314 | this.colorPickerClose.emit(this.colorPicker) 315 | } 316 | } 317 | 318 | public colorChanged(value: string, ignore: boolean = true): void { 319 | this.ignoreChanges = ignore 320 | 321 | this.colorPickerChange.emit(value) 322 | } 323 | 324 | public colorSelected(value: string): void { 325 | this.colorPickerSelect.emit(value) 326 | } 327 | 328 | public colorCanceled(): void { 329 | this.colorPickerCancel.emit() 330 | } 331 | 332 | public inputFocus(): void { 333 | const element = this.elRef.nativeElement 334 | 335 | const ignored = this.cpIgnoredElements.filter( 336 | (item: any) => item === element 337 | ) 338 | 339 | if (!this.cpDisabled && !ignored.length) { 340 | if ( 341 | typeof document !== 'undefined' && 342 | element === document.activeElement 343 | ) { 344 | this.openDialog() 345 | } else if (!this.dialog || !this.dialog.show) { 346 | this.openDialog() 347 | } else { 348 | this.closeDialog() 349 | } 350 | } 351 | } 352 | 353 | public inputChange(event: any): void { 354 | if (this.dialog) { 355 | this.dialog.setColorFromString(event.target.value, true) 356 | } else { 357 | this.colorPicker = event.target.value 358 | 359 | this.colorPickerChange.emit(this.colorPicker) 360 | } 361 | } 362 | 363 | public inputChanged(event: any): void { 364 | this.cpInputChange.emit(event) 365 | } 366 | 367 | public sliderChanged(event: any): void { 368 | this.cpSliderChange.emit(event) 369 | } 370 | 371 | public sliderDragEnd(event: { slider: string; color: string }): void { 372 | this.cpSliderDragEnd.emit(event) 373 | } 374 | 375 | public sliderDragStart(event: { slider: string; color: string }): void { 376 | this.cpSliderDragStart.emit(event) 377 | } 378 | 379 | public presetColorsChanged(value: any[]): void { 380 | this.cpPresetColorsChange.emit(value) 381 | } 382 | } 383 | -------------------------------------------------------------------------------- /projects/app/src/app/app.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |

Angular Color Picker Directive

5 |

A Color Picker Directive for Angular with no dependencies.

6 |

based on angular2-color-picker by Alberto Pujante

7 | 8 |
9 |
10 |
11 | 12 |
13 |
14 | 15 |
16 | 17 |
18 |

Usage:

19 |
 20 | <input [(colorPicker)]="color"
 21 |        [style.background]="color"/>
 22 |       
23 |

Or:

24 |
 25 | <input [style.background]="color"
 26 |        [colorPicker]="color"
 27 |        (colorPickerChange)="color=$event"/>
 28 |       
29 |
30 |
31 | 32 |
33 | 34 |
35 |
36 | 37 |
38 | 39 |
40 |

Grayscale color mode:

41 |
 42 | <input [(colorPicker)]="color" [cpColorMode]="'grayscale'"
 43 |        [style.background]="color"/>
 44 |       
45 |
46 |
47 | 48 |
49 | 50 |
51 |
52 | 53 |
54 | 55 |
56 |

Show the color in the input field:

57 |
 58 | <input [value]="color"
 59 |        [style.background]="color"
 60 |        [(colorPicker)]="color"/>
 61 |       
62 |
63 |
64 | 65 |
66 | 67 |
68 |
69 | 70 | 71 |

72 | 73 | 74 |
75 | 76 |
77 |

Output format:

78 |
 79 | <input [value]="color"
 80 |        [style.background]="color"
 81 |        [cpOutputFormat]="'rgba'"
 82 |        [(colorPicker)]="color"/>
 83 |       
84 |
85 |
86 | 87 |
88 | 89 |
90 |
91 | 92 |
93 | 94 |
95 |

Changing dialog position:

96 |
 97 | <input [value]="color"
 98 |        [style.background]="color"
 99 |        [cpPosition]="'top-right'"
100 |        [(colorPicker)]="color"/>
101 |       
102 |
103 |
104 | 105 |
106 | 107 |
108 |
109 | Change me! 110 |
111 | 112 |
113 |

You can introduce a offset of the color picker relative to the html element:

114 |
115 | <span [style.color]="color"
116 |       [cpPosition]="'bottom'"
117 |       [cpPositionOffset]="'50%'"
118 |       [cpPositionRelativeToArrow]="true"
119 |       [(colorPicker)]="color">Change me!</span>
120 |       
121 |
122 |
123 | 124 |
125 | 126 |
127 |
128 | 129 |
130 | 131 |
132 |

Show cancel button:

133 |
134 | <input [value]="color"
135 |        [style.background]="color"
136 |        [cpCancelButton]="true"
137 |        [(colorPicker)]="color"/>
138 |       
139 |
140 |
141 | 142 |
143 | 144 |
145 |
146 | 147 |
148 | 149 |
150 |

Change cancel button class, in this example we are using a bootstrap button:

151 |
152 | <input [value]="color"
153 |        [style.background]="color"
154 |        [cpCancelButton]="true"
155 |        [cpCancelButtonClass]= "'btn btn-primary btn-xs'"
156 |        [(colorPicker)]="color"/>
157 |       
158 |
159 |
160 | 161 |
162 | 163 |
164 |
165 | 166 |
167 | 168 |
169 |

Show OK button:

170 |
171 | <input [value]="color"
172 |        [style.background]="color"
173 |        [cpOKButton]="true"
174 |        [cpSaveClickOutside]="false"
175 |        [cpOKButtonClass]= "'btn btn-primary btn-xs'"
176 |        [(colorPicker)]="color"/>
177 |       
178 |
179 |
180 | 181 |
182 | 183 |
184 |
185 | 186 |
187 | 188 |
189 |

Enable Eye Dropper:

190 |

You can open the eye dropper by clicking the colored circle.

191 |
192 | <input [value]="color"
193 |        [style.background]="color"
194 |        [cpEyeDropper]="true"
195 |        [cpSaveClickOutside]="false"
196 |        [cpOKButtonClass]= "'btn btn-primary btn-xs'"
197 |        [(colorPicker)]="color"/>
198 |       
199 |
200 |
201 | 202 |
203 | 204 |
205 |
206 | 207 | 208 |
209 | 210 |
{{cmykValue}}
211 | 212 |
213 | C 214 | 215 | M 216 |
217 | 218 |
219 | 220 |
221 | Y 222 | 223 | K 224 |
225 | 226 |
227 |
228 | 229 |
230 |

Change event color:

231 |
232 | <input [style.background]="color"
233 |        [colorPicker]="color"
234 |        [cpCmykEnabled]="true"
235 |        (cpCmykColorChange)="cmykValue=$event"
236 |        (colorPickerChange)="cmykColor=onChangeColorCmyk($event);color=$event"/>
237 | 
238 | <span [style.font-size.px]="100 * cmykColor.c"/>C</span/>
239 | <span [style.font-size.px]="100 * cmykColor.m"/>M</span/>
240 | <span [style.font-size.px]="100 * cmykColor.y"/>Y</span/>
241 | <span [style.font-size.px]="100 * cmykColor.k"/>K</span/>
242 |       
243 |
244 |
245 | 246 |
247 | 248 |
249 |
250 | 251 |
252 | 253 |
254 |

With preset colors:

255 |
256 | <input [style.background]="color"
257 |        [cpPresetColors]="['#fff', '#000', '#2889e9', '#e920e9', '#fff500', 'rgb(236,64,64)']"
258 |        [(colorPicker)]="color"/>
259 |       
260 |
261 |
262 | 263 |
264 | 265 |
266 |
267 | 268 |
269 | 270 |
271 |

Add and remove preset colors:

272 |
273 | <input [style.background]="color"
274 |        [cpAlphaChannel]="'always'"
275 |        [cpOutputFormat]="'rgba'"
276 |        [cpPresetColors]="['#fff', '#2889e9']"
277 |        [cpAddColorButton]="true"
278 |        [(colorPicker)]="color"/>
279 |       
280 |
281 |
282 | 283 |
284 | 285 |
286 |
287 | 288 | 289 |
290 | 291 | 292 | 293 |

294 | 295 |
Toggle status: {{toggle}}
296 |
297 | 298 |
299 |

Use cpToggle with cpIgnoredElements:

300 |
301 | <input #ignoredInput
302 |        [style.background]="color"
303 |        [cpIgnoredElements]="[ignoredButton, ignoredInput]"
304 |        [(cpToggle)]="toggle"
305 |        [(colorPicker)]="color"/>
306 | 
307 | <button #ignoredButton (click)="toggle=!toggle"></button>
308 |       
309 |
310 |
311 | 312 |
313 | 314 |
315 |
316 | 317 |
318 | 319 |
320 |

Auto positioning:

321 |
322 | <input [value]="color"
323 |        [style.background]="color"
324 |        cpPosition="auto"
325 |        [(colorPicker)]="color"/>
326 |       
327 |
328 |
329 | 330 |
331 | 332 |
333 |
334 | 335 | 336 |
337 | 338 | 339 | 340 |
341 | 342 | 343 | 344 |
345 | 346 | 347 | 348 |
349 | 350 | 354 | 355 | 356 |
357 | 358 |
359 |

Change alpha channel behaviour:

360 |
361 | <input [value]="color"
362 |        [style.background]="color"
363 |        [cpAlphaChannel]="'always'"
364 |        [cpOutputFormat]="'rgba'"
365 |        [(colorPicker)]="color"/>
366 | 
367 | <input [value]="color"
368 |        [style.background]="color13"
369 |        [cpAlphaChannel]="'disabled'"
370 |        [cpOutputFormat]="'rgba'"
371 |        [(colorPicker)]="color"/>
372 | 
373 | <input [value]="color"
374 |        [style.background]="rgbaText"
375 |        [cpAlphaChannel]="'always'"
376 |        [cpOutputFormat]="'hex'"
377 |        [colorPicker]="color"
378 |        (colorPickerChange)="rgbaText=onChangeColorHex8($event);color16=$event"/>
379 | 
380 | <input [value]="color"
381 |        [style.background]="color"
382 |        [cpAlphaChannel]="'forced'"
383 |        [cpOutputFormat]="'hex'"
384 |        [(colorPicker)]="color"/>
385 | 
386 | <input [value]="color"
387 |        [style.background]="color"
388 |        [cpAlphaChannel]="alphaEnabled ? 'always' : 'disabled'"
389 |        [cpOutputFormat]="'hex'"
390 |        [(colorPicker)]="color"/>
391 |       
392 |
393 |
394 | 395 |
396 | 397 |
398 |
399 | 400 |
401 | 402 |
403 |
404 | 405 |
406 | 407 |
408 | 409 |
410 | 411 |
412 |
413 | 414 |
415 |

Show the dialog permanently:

416 |
417 | <span [style.background]="arrayColors[selectedColor]"
418 |       [cpToggle]="true"
419 |       [cpDialogDisplay]="'inline'"
420 |       [cpCancelButton]="true"
421 |       [(colorPicker)]="arrayColors[selectedColor]"></span>
422 | 
423 | <div [style.background]="arrayColors['color1']"
424 |      (click)="selectedColor='color1'"></div>
425 | 
426 | <div [style.background]="arrayColors['color2']"
427 |      (click)="selectedColor='color2'"></div>
428 |       
429 |
430 |
431 | 432 |

433 | 434 |
435 | 436 |
437 |
438 | 449 | 450 | 451 |
452 |

Custom template content here.

453 |
454 |
455 |
456 | 457 |
458 |

Custom template:

459 |
460 | <input (colorPickerClose)="onEventLog('colorPickerClose', $event)"
461 |        (colorPickerOpen)="onEventLog('colorPickerOpen', $event)"
462 |        (cpInputChange)="onEventLog('cpInputChange', $event)"
463 |        (cpSliderDragEnd)="onEventLog('cpSliderDragEnd', $event)"
464 |        (cpSliderDragStart)="onEventLog('cpSliderDragStart', $event)"
465 |        [cpExtraTemplate]="templateTest"
466 |        [style.background]="color"
467 | />
468 | 
469 | <ng-template #customTemplate>
470 |     <div class="">
471 |         <h3>Custom template.</h3>
472 |     </div>
473 | </ng-template>
474 |             
475 |
476 |

477 | 478 |
479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 493 | 494 | 495 | 496 | 497 | 500 | 501 | 502 | 503 | 504 | 508 | 509 | 510 | 511 | 512 | 516 | 517 | 518 | 519 | 520 | 524 | 525 | 526 | 527 | 528 | 532 | 533 | 534 | 535 | 536 | 540 | 541 | 542 | 543 | 544 | 548 | 549 | 550 | 551 | 552 | 555 | 556 | 557 | 558 | 559 | 562 | 563 | 564 | 565 | 566 | 570 | 571 | 572 | 573 | 574 | 577 | 578 | 579 | 580 | 581 | 584 | 585 | 586 | 587 | 588 | 592 | 593 | 594 | 595 | 596 | 600 | 601 | 602 | 603 | 604 | 608 | 609 | 610 | 611 | 612 | 616 | 617 | 618 | 619 | 620 | 624 | 625 | 626 | 627 | 628 | 633 | 634 | 635 | 636 | 637 | 641 | 642 | 643 | 644 | 645 | 652 | 653 | 654 | 655 | 656 | 660 | 661 | 662 | 663 | 664 | 668 | 669 | 670 | 671 | 672 | 676 | 677 | 678 | 679 | 680 | 683 | 684 | 685 | 686 | 687 | 690 | 691 | 692 | 693 | 694 | 697 | 698 | 699 | 700 | 701 | 704 | 705 | 706 | 707 | 708 | 712 | 713 | 714 | 715 | 716 | 720 | 721 | 722 | 723 | 724 | 727 | 728 | 729 | 730 | 733 | 734 | 735 |
OptionsValues (default values in bold)
cpOutputFormat 491 | 'auto', 'hex', 'rgba', 'hsla' 492 |
cpPosition 498 | 'auto', 'top', 'bottom', 'top-right', 'top-left', 'bottom-left', 'bottom-right' 499 |
cpPositionOffset 505 | '0%'
506 | Dialog offset (percent) relative to the element that contains the directive. 507 |
cpPositionRelativeToArrow 513 | false, true
514 | Dialog position is calculated relative to the dialog (false) or relative to the dialog arrow (true). 515 |
cpWidth 521 | '230px'
522 | Use this option to set color picker dialog width (pixels). 523 |
cpHeight 529 | 'auto'
530 | Use this option to force color picker dialog height (pixels). 531 |
cpSaveClickOutside 537 | true, false
538 | If true the initial color is restored when user clicks outside. 539 |
cpOKButton 545 | false, true
546 | Shows the Ok button. Saves the selected color. 547 |
cpOKButtonText 553 | 'OK' 554 |
cpOKButtonClass 560 | Class to customize the OK button. 561 |
cpCancelButton 567 | false, true
568 | Shows the Cancel button. Cancel the selected color. 569 |
cpCancelButtonText 575 | 'Cancel' 576 |
cpCancelButtonClass 582 | Class to customize the Cancel button. 583 |
cpFallbackColor 589 | '#fff'
590 | Is used when the color is not well-formed or not defined. 591 |
cpPresetLabel 597 | 'Preset colors'
598 | Label for preset colors if any provided used. 599 |
cpPresetColors 605 | []
606 | Array of preset colors to show in the color picker dialog. 607 |
cpToggle 613 | false, true
614 | Input/ouput to open/close the color picker. 615 |
cpIgnoredElements 621 | []
622 | Array of HTML elements that will be ignored by the color picker when they are clicked. 623 |
cpDialogDisplay 629 | 'popup', 'inline'
630 | popup: dialog is showed when user clicks in the directive.
631 | inline: dialog is showed permanently. You can show/hide the dialog with cpToggle.
632 |
cpDisableInput 638 | false, true
639 | Disables / hides the color input field from the dialog.
640 |
cpAlphaChannel 646 | 'enabled', 'disabled', 'always', 'forced'
647 | enabled: alpha channel is not included for hexadecimal (hex6) values or for values without alpha (alpha = 1).
648 | disabled: alpha channel is completely disabled.
649 | always: alpha channel is included for hexadecimal (hex6) values and values without alpha (alpha = 1).
650 | forced: alpha channel field is added for hexadecimal (hex6) values.
651 |
cpCmykEnabled 657 | false, true
658 | Enables CMYK color input and selected CMYK color event sending on color change.
659 |
cpUseRootViewContainer 665 | false, true
666 | Create dialog component in the root view container instead the elements view container.
667 |
cpAddColorButton 673 | false, true
674 | Add or remove colors into your preset panel. The [cpPresetColors] is needed
675 |
cpAddColorButtonText 681 | 'Add color' 682 |
cpAddColorButtonClass 688 | Class to customize the add color button. 689 |
cpRemoveColorButtonClass 695 | Class to customize the remove color button. 696 |
cpPresetColorsClass 702 | Class to customize the preset colors container. 703 |
cpMaxPresetColorsLength 709 | 8 (number)
710 | Use this option to set the max colors allowed into preset panel. 711 |
cpPresetEmptyMessage 717 | 'No colors added'
718 | Message for empty colors if any provided used. 719 |
cpPresetEmptyMessageClass 725 | Class to customize the empty colors message. 726 |
cpEyeDropper 731 | Enable eye dropper on click of colored circle. Click again to pick a color. 732 |
736 |
737 |
738 | 739 |

740 | 741 |
742 |
743 | 744 | 745 | 746 | 747 | 748 | 749 | 750 | 751 | 752 | 753 | 754 | 757 | 758 | 759 | 760 | 761 | 764 | 765 | 766 | 767 | 768 | 771 | 772 | 773 | 774 | 775 | 778 | 779 | 780 | 781 | 782 | 785 | 786 | 787 | 788 | 789 | 792 | 793 | 794 | 795 | 796 | 799 | 800 | 801 |
EventsDescription (data format in bold)
colorPickerChange 755 | Changed color value, send when color is changed. (value: string) 756 |
colorPickerSelect 762 | Selected color value, send when user presses the OK button. (value: string) 763 |
cpToggleChange 769 | Status of the dialog, send when dialog is opened / closed. (open: boolean) 770 |
cpInputChange 776 | Input name and its value, send when user changes color through inputs. ({{ '{' }}input: string, value: string{{ '}' }}) 777 |
cpSliderChange 783 | Slider name and its value, send when user changes color through slider. ({{ '{' }}slider: string, value: Object{{ '}' }}) 784 |
cpCmykColorChange 790 | CMYK color value, send when on color change if cpCmykEnabled is true. (value: string) 791 |
cpPresetColorsChange 797 | Preset colors value, send when Add Color button is pressed. (value: array) 798 |
802 |
803 |
804 | 805 |
806 |
807 |
808 | -------------------------------------------------------------------------------- /projects/lib/src/lib/color-picker.component.css: -------------------------------------------------------------------------------- 1 | .color-picker { 2 | position: absolute; 3 | z-index: 1000; 4 | 5 | width: 230px; 6 | height: auto; 7 | border: #777 solid 1px; 8 | 9 | cursor: default; 10 | 11 | -webkit-user-select: none; 12 | -khtml-user-select: none; 13 | -moz-user-select: none; 14 | -ms-user-select: none; 15 | 16 | user-select: none; 17 | background-color: #fff; 18 | } 19 | 20 | .color-picker * { 21 | -webkit-box-sizing: border-box; 22 | -moz-box-sizing: border-box; 23 | 24 | box-sizing: border-box; 25 | margin: 0; 26 | 27 | font-size: 11px; 28 | } 29 | 30 | .color-picker input { 31 | width: 0; 32 | height: 26px; 33 | min-width: 0; 34 | 35 | font-size: 13px; 36 | text-align: center; 37 | color: #000; 38 | } 39 | 40 | .color-picker input:invalid, 41 | .color-picker input:-moz-ui-invalid, 42 | .color-picker input:-moz-submit-invalid { 43 | box-shadow: none; 44 | } 45 | 46 | .color-picker input::-webkit-inner-spin-button, 47 | .color-picker input::-webkit-outer-spin-button { 48 | margin: 0; 49 | 50 | -webkit-appearance: none; 51 | } 52 | 53 | .color-picker .arrow { 54 | position: absolute; 55 | z-index: 999999; 56 | 57 | width: 0; 58 | height: 0; 59 | border-style: solid; 60 | } 61 | 62 | .color-picker .arrow.arrow-top { 63 | left: 8px; 64 | 65 | border-width: 10px 5px; 66 | border-color: #777 rgba(0, 0, 0, 0) rgba(0, 0, 0, 0) rgba(0, 0, 0, 0); 67 | } 68 | 69 | .color-picker .arrow.arrow-bottom { 70 | top: -20px; 71 | left: 8px; 72 | 73 | border-width: 10px 5px; 74 | border-color: rgba(0, 0, 0, 0) rgba(0, 0, 0, 0) #777 rgba(0, 0, 0, 0); 75 | } 76 | 77 | .color-picker .arrow.arrow-top-left, 78 | .color-picker .arrow.arrow-left-top { 79 | right: -21px; 80 | bottom: 8px; 81 | 82 | border-width: 5px 10px; 83 | border-color: rgba(0, 0, 0, 0) rgba(0, 0, 0, 0) rgba(0, 0, 0, 0) #777; 84 | } 85 | 86 | .color-picker .arrow.arrow-top-right, 87 | .color-picker .arrow.arrow-right-top { 88 | bottom: 8px; 89 | left: -20px; 90 | 91 | border-width: 5px 10px; 92 | border-color: rgba(0, 0, 0, 0) #777 rgba(0, 0, 0, 0) rgba(0, 0, 0, 0); 93 | } 94 | 95 | .color-picker .arrow.arrow-left, 96 | .color-picker .arrow.arrow-left-bottom, 97 | .color-picker .arrow.arrow-bottom-left { 98 | top: 8px; 99 | right: -21px; 100 | 101 | border-width: 5px 10px; 102 | border-color: rgba(0, 0, 0, 0) rgba(0, 0, 0, 0) rgba(0, 0, 0, 0) #777; 103 | } 104 | 105 | .color-picker .arrow.arrow-right, 106 | .color-picker .arrow.arrow-right-bottom, 107 | .color-picker .arrow.arrow-bottom-right { 108 | top: 8px; 109 | left: -20px; 110 | 111 | border-width: 5px 10px; 112 | border-color: rgba(0, 0, 0, 0) #777 rgba(0, 0, 0, 0) rgba(0, 0, 0, 0); 113 | } 114 | 115 | .color-picker .cursor { 116 | position: relative; 117 | 118 | width: 16px; 119 | height: 16px; 120 | border: #222 solid 2px; 121 | border-radius: 50%; 122 | 123 | cursor: default; 124 | } 125 | 126 | .color-picker .box { 127 | display: flex; 128 | padding: 4px 8px; 129 | } 130 | 131 | .color-picker .left { 132 | position: relative; 133 | 134 | padding: 16px 8px; 135 | } 136 | 137 | .color-picker .right { 138 | -webkit-flex: 1 1 auto; 139 | -ms-flex: 1 1 auto; 140 | 141 | flex: 1 1 auto; 142 | 143 | padding: 12px 8px; 144 | } 145 | 146 | .color-picker .button-area { 147 | padding: 0 16px 16px; 148 | 149 | text-align: right; 150 | } 151 | 152 | .color-picker .button-area button { 153 | margin-left: 8px; 154 | } 155 | 156 | .color-picker .preset-area { 157 | padding: 4px 15px; 158 | } 159 | 160 | .color-picker .preset-area .preset-label { 161 | overflow: hidden; 162 | width: 100%; 163 | padding: 4px; 164 | 165 | font-size: 11px; 166 | white-space: nowrap; 167 | text-align: left; 168 | text-overflow: ellipsis; 169 | color: #555; 170 | } 171 | 172 | .color-picker .preset-area .preset-color { 173 | position: relative; 174 | 175 | display: inline-block; 176 | width: 18px; 177 | height: 18px; 178 | margin: 4px 6px 8px; 179 | border: #a9a9a9 solid 1px; 180 | border-radius: 25%; 181 | 182 | cursor: pointer; 183 | } 184 | 185 | .color-picker .preset-area .preset-empty-message { 186 | min-height: 18px; 187 | margin-top: 4px; 188 | margin-bottom: 8px; 189 | 190 | font-style: italic; 191 | text-align: center; 192 | } 193 | 194 | .color-picker .hex-text { 195 | width: 100%; 196 | padding: 4px 8px; 197 | 198 | font-size: 11px; 199 | } 200 | 201 | .color-picker .hex-text .box { 202 | padding: 0 24px 8px 8px; 203 | } 204 | 205 | .color-picker .hex-text .box div { 206 | float: left; 207 | 208 | -webkit-flex: 1 1 auto; 209 | -ms-flex: 1 1 auto; 210 | 211 | flex: 1 1 auto; 212 | 213 | text-align: center; 214 | color: #555; 215 | clear: left; 216 | } 217 | 218 | .color-picker .hex-text .box input { 219 | -webkit-flex: 1 1 auto; 220 | -ms-flex: 1 1 auto; 221 | 222 | flex: 1 1 auto; 223 | padding: 1px; 224 | border: #a9a9a9 solid 1px; 225 | } 226 | 227 | .color-picker .hex-alpha .box div:first-child, 228 | .color-picker .hex-alpha .box input:first-child { 229 | flex-grow: 3; 230 | margin-right: 8px; 231 | } 232 | 233 | .color-picker .cmyk-text, 234 | .color-picker .hsla-text, 235 | .color-picker .rgba-text, 236 | .color-picker .value-text { 237 | width: 100%; 238 | padding: 4px 8px; 239 | 240 | font-size: 11px; 241 | } 242 | 243 | .color-picker .cmyk-text .box, 244 | .color-picker .hsla-text .box, 245 | .color-picker .rgba-text .box { 246 | padding: 0 24px 8px 8px; 247 | } 248 | 249 | .color-picker .value-text .box { 250 | padding: 0 8px 8px; 251 | } 252 | 253 | .color-picker .cmyk-text .box div, 254 | .color-picker .hsla-text .box div, 255 | .color-picker .rgba-text .box div, 256 | .color-picker .value-text .box div { 257 | -webkit-flex: 1 1 auto; 258 | -ms-flex: 1 1 auto; 259 | 260 | flex: 1 1 auto; 261 | margin-right: 8px; 262 | 263 | text-align: center; 264 | color: #555; 265 | } 266 | 267 | .color-picker .cmyk-text .box div:last-child, 268 | .color-picker .hsla-text .box div:last-child, 269 | .color-picker .rgba-text .box div:last-child, 270 | .color-picker .value-text .box div:last-child { 271 | margin-right: 0; 272 | } 273 | 274 | .color-picker .cmyk-text .box input, 275 | .color-picker .hsla-text .box input, 276 | .color-picker .rgba-text .box input, 277 | .color-picker .value-text .box input { 278 | float: left; 279 | 280 | -webkit-flex: 1; 281 | -ms-flex: 1; 282 | 283 | flex: 1; 284 | padding: 1px; 285 | margin: 0 8px 0 0; 286 | border: #a9a9a9 solid 1px; 287 | } 288 | 289 | .color-picker .cmyk-text .box input:last-child, 290 | .color-picker .hsla-text .box input:last-child, 291 | .color-picker .rgba-text .box input:last-child, 292 | .color-picker .value-text .box input:last-child { 293 | margin-right: 0; 294 | } 295 | 296 | .color-picker .hue-alpha { 297 | align-items: center; 298 | margin-bottom: 3px; 299 | } 300 | 301 | .color-picker .hue { 302 | direction: ltr; 303 | 304 | width: 100%; 305 | height: 16px; 306 | margin-bottom: 16px; 307 | border: none; 308 | 309 | cursor: pointer; 310 | background-size: 100% 100%; 311 | background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAJYAAAAQCAYAAAD06IYnAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4AIWDwkUFWbCCAAAAFxJREFUaN7t0kEKg0AQAME2x83/n2qu5qCgD1iDhCoYdpnbQC9bbY1qVO/jvc6k3ad91s7/7F1/csgPrujuQ17BDYSFsBAWwgJhISyEBcJCWAgLhIWwEBYIi2f7Ar/1TCgFH2X9AAAAAElFTkSuQmCC'); 312 | } 313 | 314 | .color-picker .value { 315 | direction: rtl; 316 | 317 | width: 100%; 318 | height: 16px; 319 | margin-bottom: 16px; 320 | border: none; 321 | 322 | cursor: pointer; 323 | background-size: 100% 100%; 324 | background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAJYAAAAQCAYAAAD06IYnAAACTklEQVR42u3SYUcrABhA4U2SkmRJMmWSJklKJiWZZpKUJJskKUmaTFImKZOUzMySpGRmliRNJilJSpKSJEtmSpIpmWmSdO736/6D+x7OP3gUCoWCv1cqlSQlJZGcnExKSgqpqamkpaWRnp5ORkYGmZmZqFQqsrKyyM7OJicnh9zcXNRqNXl5eeTn56PRaCgoKKCwsJCioiK0Wi3FxcWUlJRQWlpKWVkZ5eXlVFRUUFlZiU6no6qqiurqampqaqitraWurg69Xk99fT0GgwGj0UhDQwONjY00NTXR3NxMS0sLra2ttLW10d7ejslkwmw209HRQWdnJ11dXXR3d9PT00Nvby99fX309/czMDDA4OAgFouFoaEhrFYrw8PDjIyMMDo6ytjYGDabjfHxcSYmJpicnGRqagq73c709DQzMzPMzs4yNzfH/Pw8DocDp9OJy+XC7XazsLDA4uIiS0tLLC8vs7KywurqKmtra3g8HrxeLz6fD7/fz/r6OhsbG2xubrK1tcX29jaBQICdnR2CwSC7u7vs7e2xv7/PwcEBh4eHHB0dcXx8zMnJCaenp5ydnXF+fs7FxQWXl5dcXV1xfX3Nzc0Nt7e33N3dEQqFuL+/5+HhgXA4TCQS4fHxkaenJ56fn3l5eeH19ZVoNMrb2xvv7+98fHwQi8WIx+N8fn6SSCT4+vri+/ubn58ffn9/+VcKgSWwBJbAElgCS2AJLIElsASWwBJYAktgCSyBJbAElsASWAJLYAksgSWwBJbAElgCS2AJLIElsP4/WH8AmJ5Z6jHS4h8AAAAASUVORK5CYII='); 325 | } 326 | 327 | .color-picker .alpha { 328 | direction: ltr; 329 | 330 | width: 100%; 331 | height: 16px; 332 | border: none; 333 | 334 | cursor: pointer; 335 | background-size: 100% 100%; 336 | background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAJYAAAAQCAYAAAD06IYnAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4AIWDwYQlZMa3gAAAWVJREFUaN7tmEGO6jAQRCsOArHgBpyAJYGjcGocxAm4A2IHpmoWE0eBH+ezmFlNvU06shJ3W6VEelWMUQAIIF9f6qZpimsA1LYtS2uF51/u27YVAFZVRUkEoGHdPV/sIcbIEIIkUdI/9Xa7neyv61+SWFUVAVCSct00TWn2fv6u3+Ecfd3tXzy/0+nEUu+SPjo/kqzrmiQpScN6v98XewfA8/lMkiLJ2WxGSUopcT6fM6U0NX9/frfbjev1WtfrlZfLhYfDQQHG/AIOlnGwjINlHCxjHCzjYJm/TJWdCwquJXseFFzGwDNNeiKMOJTO8xQdDQaeB29+K9efeLaBo9J7vdvtJj1RjFFjfiv7qv95tjx/7leSQgh93e1ffMeIp6O+YQjho/N791t1XVOSSI7N//K+4/GoxWLBx+PB5/Op5XLJ+/3OlJJWqxU3m83ovv5iGf8KjYNlHCxjHCzjYBkHy5gf5gusvQU7U37jTAAAAABJRU5ErkJggg=='); 337 | } 338 | 339 | .color-picker .type-policy { 340 | position: absolute; 341 | top: 218px; 342 | right: 12px; 343 | 344 | width: 16px; 345 | height: 24px; 346 | 347 | background-size: 8px 16px; 348 | background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAAgCAYAAAAffCjxAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAACewAAAnsB01CO3AAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAIASURBVEiJ7ZY9axRRFIafsxMStrLQJpAgpBFhi+C9w1YSo00I6RZ/g9vZpBf/QOr4GyRgkSKNSrAadsZqQGwCkuAWyRZJsySwvhZ7N/vhzrgbLH3Ld8597jlzz50zJokyxXH8DqDVar0qi6v8BbItqSGpEcfxdlmsFWXkvX8AfAVWg3UKPEnT9GKujMzsAFgZsVaCN1VTQd77XUnrgE1kv+6935268WRpzrnHZvYRWC7YvC3pRZZl3wozqtVqiyH9IgjAspkd1Gq1xUJQtVrdB9ZKIAOthdg/Qc65LUk7wNIMoCVJO865rYFhkqjX6/d7vV4GPJwBMqofURS5JEk6FYBer/eeYb/Mo9WwFnPOvQbeAvfuAAK4BN4sAJtAG/gJIElmNuiJyba3EGNmZiPeZuEVmVell/Y/6N+CzDn3AXhEOOo7Hv/3BeAz8IzQkMPnJbuPx1wC+yYJ7/0nYIP5S/0FHKdp+rwCEEXRS/rf5Hl1Gtb2M0iSpCOpCZzPATmX1EySpHMLAsiy7MjMDoHrGSDXZnaYZdnRwBh7J91utwmczAA6CbG3GgPleX4jqUH/a1CktqRGnuc3hSCAMB32gKspkCtgb3KCQMmkjeP4WNJThrNNZval1WptTIsv7JtQ4tmIdRa8qSoEpWl6YWZNoAN0zKxZNPehpLSBZv2t+Q0CJ9lLnARQLAAAAABJRU5ErkJggg=='); 349 | background-repeat: no-repeat; 350 | background-position: center; 351 | } 352 | 353 | .color-picker .type-policy .type-policy-arrow { 354 | display: block; 355 | 356 | width: 100%; 357 | height: 50%; 358 | } 359 | 360 | .color-picker .selected-color { 361 | position: absolute; 362 | top: 16px; 363 | left: 8px; 364 | 365 | width: 40px; 366 | height: 40px; 367 | border: 1px solid #a9a9a9; 368 | border-radius: 50%; 369 | } 370 | 371 | .color-picker .selected-color-background { 372 | width: 40px; 373 | height: 40px; 374 | border-radius: 50%; 375 | 376 | background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAACM/rhtAAAAh0lEQVRYR+2W0QlAMQgD60zdfwOdqa8TmI/wQMr5K0I5bZLIzLOa2nt37VVVbd+dDx5obgCC3KBLwJ2ff4PnVidkf+ucIhw80HQaCLo3DMH3CRK3iFsmAWVl6hPNDwt8EvNE5q+YuEXcMgkonVM6SdyCoEvAnZ8v1Hjx817MilmxSUB5rdLJDycZgUAZUch/AAAAAElFTkSuQmCC'); 377 | } 378 | 379 | .color-picker .saturation-lightness { 380 | direction: ltr; 381 | 382 | width: 100%; 383 | height: 130px; 384 | border: none; 385 | 386 | cursor: pointer; 387 | touch-action: manipulation; 388 | background-size: 100% 100%; 389 | background-image: url('data:image/png;base64,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'); 390 | } 391 | 392 | .color-picker .cp-add-color-button-class { 393 | position: absolute; 394 | 395 | display: inline; 396 | padding: 0; 397 | margin: 3px -3px; 398 | border: 0; 399 | 400 | cursor: pointer; 401 | background: transparent; 402 | } 403 | 404 | .color-picker .cp-add-color-button-class:hover { 405 | text-decoration: underline; 406 | } 407 | 408 | .color-picker .cp-add-color-button-class:disabled { 409 | cursor: not-allowed; 410 | color: #999; 411 | } 412 | 413 | .color-picker .cp-add-color-button-class:disabled:hover { 414 | text-decoration: none; 415 | } 416 | 417 | .color-picker .cp-remove-color-button-class { 418 | position: absolute; 419 | top: -5px; 420 | right: -5px; 421 | 422 | display: block; 423 | width: 10px; 424 | height: 10px; 425 | border-radius: 50%; 426 | 427 | cursor: pointer; 428 | text-align: center; 429 | background: #fff; 430 | 431 | box-shadow: 1px 1px 5px #333; 432 | } 433 | 434 | .color-picker .cp-remove-color-button-class::before { 435 | content: 'x'; 436 | 437 | position: relative; 438 | bottom: 3.5px; 439 | 440 | display: inline-block; 441 | 442 | font-size: 10px; 443 | } 444 | 445 | .color-picker .eyedropper-icon { 446 | position: absolute; 447 | top: 50%; 448 | left: 50%; 449 | transform: translate(-50%, -50%); 450 | fill: white;mix-blend-mode: exclusion; 451 | } 452 | -------------------------------------------------------------------------------- /projects/lib/src/lib/color-picker.component.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Component, 3 | OnInit, 4 | OnDestroy, 5 | AfterViewInit, 6 | ViewChild, 7 | HostListener, 8 | ViewEncapsulation, 9 | ElementRef, 10 | ChangeDetectorRef, 11 | TemplateRef, 12 | NgZone, 13 | PLATFORM_ID, 14 | inject 15 | } from '@angular/core' 16 | 17 | import { 18 | DOCUMENT, 19 | isPlatformBrowser, 20 | NgForOf, 21 | NgIf, 22 | NgTemplateOutlet 23 | } from '@angular/common' 24 | 25 | import { 26 | calculateAutoPositioning, 27 | SliderDirective, 28 | TextDirective 29 | } from './helpers' 30 | 31 | import { ColorFormats, Cmyk, Hsla, Hsva, Rgba } from './formats' 32 | import { 33 | AlphaChannel, 34 | OutputFormat, 35 | SliderDimension, 36 | SliderPosition 37 | } from './helpers' 38 | 39 | import { ColorPickerService } from './color-picker.service' 40 | 41 | // Do not store that on the class instance since the condition will be run 42 | // every time the class is created. 43 | const SUPPORTS_TOUCH = typeof window !== 'undefined' && 'ontouchstart' in window 44 | 45 | @Component({ 46 | selector: 'color-picker', 47 | templateUrl: './color-picker.component.html', 48 | styleUrls: ['./color-picker.component.css'], 49 | encapsulation: ViewEncapsulation.None, 50 | imports: [SliderDirective, TextDirective, NgIf, NgForOf, NgTemplateOutlet] 51 | }) 52 | export class ColorPickerComponent implements OnInit, OnDestroy, AfterViewInit { 53 | private ngZone = inject(NgZone) 54 | 55 | private elRef = inject(ElementRef) 56 | private cdRef = inject(ChangeDetectorRef) 57 | 58 | private platformId = inject(PLATFORM_ID) 59 | 60 | private service = inject(ColorPickerService) 61 | 62 | private document = inject(DOCUMENT) 63 | 64 | private cmyk: Cmyk 65 | private hsva: Hsva 66 | 67 | private width: number 68 | private height: number 69 | 70 | private cmykColor: string 71 | private outputColor: string 72 | private initialColor: string 73 | private fallbackColor: string 74 | 75 | private listenerResize: any 76 | private listenerMouseDown: EventListener 77 | 78 | private directiveInstance: any 79 | 80 | private sliderH: number 81 | private sliderDimMax: SliderDimension 82 | private directiveElementRef: ElementRef 83 | 84 | private dialogArrowSize: number = 10 85 | private dialogArrowOffset: number = 15 86 | 87 | private dialogInputFields: ColorFormats[] = [ 88 | ColorFormats.HEX, 89 | ColorFormats.RGBA, 90 | ColorFormats.HSLA, 91 | ColorFormats.CMYK 92 | ] 93 | 94 | private useRootViewContainer: boolean = false 95 | 96 | private readonly window: Window 97 | 98 | public show: boolean 99 | public hidden: boolean 100 | 101 | public top: number 102 | public left: number 103 | public position: string 104 | 105 | public format: ColorFormats 106 | public slider: SliderPosition 107 | 108 | public hexText: string 109 | public hexAlpha: number 110 | 111 | public cmykText: Cmyk 112 | public hslaText: Hsla 113 | public rgbaText: Rgba 114 | 115 | public arrowTop: number 116 | 117 | public selectedColor: string 118 | public hueSliderColor: string 119 | public alphaSliderColor: string 120 | 121 | public cpWidth: number 122 | public cpHeight: number 123 | 124 | public cpColorMode: number 125 | 126 | public cpCmykEnabled: boolean 127 | 128 | public cpAlphaChannel: AlphaChannel 129 | public cpOutputFormat: OutputFormat 130 | 131 | public cpDisableInput: boolean 132 | public cpDialogDisplay: string 133 | 134 | public cpIgnoredElements: any 135 | 136 | public cpSaveClickOutside: boolean 137 | public cpCloseClickOutside: boolean 138 | 139 | public cpPosition: string 140 | public cpUsePosition: string 141 | public cpPositionOffset: number 142 | 143 | public cpOKButton: boolean 144 | public cpOKButtonText: string 145 | public cpOKButtonClass: string 146 | 147 | public cpCancelButton: boolean 148 | public cpCancelButtonText: string 149 | public cpCancelButtonClass: string 150 | 151 | public cpEyeDropper: boolean 152 | public eyeDropperSupported: boolean 153 | 154 | public cpPresetLabel: string 155 | public cpPresetColors: string[] 156 | public cpPresetColorsClass: string 157 | public cpMaxPresetColorsLength: number 158 | 159 | public cpPresetEmptyMessage: string 160 | public cpPresetEmptyMessageClass: string 161 | 162 | public cpAddColorButton: boolean 163 | public cpAddColorButtonText: string 164 | public cpAddColorButtonClass: string 165 | public cpRemoveColorButtonClass: string 166 | public cpArrowPosition: number 167 | 168 | public cpTriggerElement: ElementRef 169 | 170 | public cpExtraTemplate: TemplateRef 171 | 172 | @ViewChild('dialogPopup', { static: true }) dialogElement: ElementRef 173 | 174 | @ViewChild('hueSlider', { static: true }) hueSlider: ElementRef 175 | @ViewChild('alphaSlider', { static: true }) alphaSlider: ElementRef 176 | 177 | @HostListener('document:keyup.esc', ['$event']) handleEsc(event: any): void { 178 | if (this.show && this.cpDialogDisplay === 'popup') { 179 | this.onCancelColor(event) 180 | } 181 | } 182 | 183 | @HostListener('document:keyup.enter', ['$event']) handleEnter( 184 | event: any 185 | ): void { 186 | if (this.show && this.cpDialogDisplay === 'popup') { 187 | this.onAcceptColor(event) 188 | } 189 | } 190 | 191 | constructor() { 192 | this.window = this.document.defaultView 193 | this.eyeDropperSupported = 194 | isPlatformBrowser(this.platformId) && 'EyeDropper' in this.window 195 | } 196 | 197 | ngOnInit(): void { 198 | this.slider = new SliderPosition(0, 0, 0, 0) 199 | 200 | const hueWidth = this.hueSlider.nativeElement.offsetWidth || 140 201 | const alphaWidth = this.alphaSlider.nativeElement.offsetWidth || 140 202 | 203 | this.sliderDimMax = new SliderDimension( 204 | hueWidth, 205 | this.cpWidth, 206 | 130, 207 | alphaWidth 208 | ) 209 | 210 | if (this.cpCmykEnabled) { 211 | this.format = ColorFormats.CMYK 212 | } else if (this.cpOutputFormat === 'rgba') { 213 | this.format = ColorFormats.RGBA 214 | } else if (this.cpOutputFormat === 'hsla') { 215 | this.format = ColorFormats.HSLA 216 | } else { 217 | this.format = ColorFormats.HEX 218 | } 219 | 220 | this.listenerMouseDown = (event: MouseEvent) => { 221 | this.onMouseDown(event) 222 | } 223 | this.listenerResize = () => { 224 | this.onResize() 225 | } 226 | 227 | this.openDialog(this.initialColor, false) 228 | } 229 | 230 | ngOnDestroy(): void { 231 | this.closeDialog() 232 | } 233 | 234 | ngAfterViewInit(): void { 235 | if (this.cpWidth !== 230 || this.cpDialogDisplay === 'inline') { 236 | const hueWidth = this.hueSlider.nativeElement.offsetWidth || 140 237 | const alphaWidth = this.alphaSlider.nativeElement.offsetWidth || 140 238 | 239 | this.sliderDimMax = new SliderDimension( 240 | hueWidth, 241 | this.cpWidth, 242 | 130, 243 | alphaWidth 244 | ) 245 | 246 | this.updateColorPicker(false) 247 | 248 | this.cdRef.detectChanges() 249 | } 250 | } 251 | 252 | public openDialog(color: any, emit: boolean = true): void { 253 | this.service.setActive(this) 254 | 255 | if (!this.width) { 256 | this.cpWidth = this.directiveElementRef.nativeElement.offsetWidth 257 | } 258 | 259 | if (!this.height) { 260 | this.height = 320 261 | } 262 | 263 | this.setInitialColor(color) 264 | 265 | this.setColorFromString(color, emit) 266 | 267 | this.openColorPicker() 268 | } 269 | 270 | public closeDialog(): void { 271 | this.closeColorPicker() 272 | } 273 | 274 | public setupDialog( 275 | instance: any, 276 | elementRef: ElementRef, 277 | color: any, 278 | cpWidth: string, 279 | cpHeight: string, 280 | cpDialogDisplay: string, 281 | cpFallbackColor: string, 282 | cpColorMode: string, 283 | cpCmykEnabled: boolean, 284 | cpAlphaChannel: AlphaChannel, 285 | cpOutputFormat: OutputFormat, 286 | cpDisableInput: boolean, 287 | cpIgnoredElements: any, 288 | cpSaveClickOutside: boolean, 289 | cpCloseClickOutside: boolean, 290 | cpUseRootViewContainer: boolean, 291 | cpPosition: string, 292 | cpPositionOffset: string, 293 | cpPositionRelativeToArrow: boolean, 294 | cpPresetLabel: string, 295 | cpPresetColors: string[], 296 | cpPresetColorsClass: string, 297 | cpMaxPresetColorsLength: number, 298 | cpPresetEmptyMessage: string, 299 | cpPresetEmptyMessageClass: string, 300 | cpOKButton: boolean, 301 | cpOKButtonClass: string, 302 | cpOKButtonText: string, 303 | cpCancelButton: boolean, 304 | cpCancelButtonClass: string, 305 | cpCancelButtonText: string, 306 | cpAddColorButton: boolean, 307 | cpAddColorButtonClass: string, 308 | cpAddColorButtonText: string, 309 | cpRemoveColorButtonClass: string, 310 | cpEyeDropper: boolean, 311 | cpTriggerElement: ElementRef, 312 | cpExtraTemplate: TemplateRef 313 | ): void { 314 | this.setInitialColor(color) 315 | 316 | this.setColorMode(cpColorMode) 317 | 318 | this.directiveInstance = instance 319 | this.directiveElementRef = elementRef 320 | 321 | this.cpDisableInput = cpDisableInput 322 | 323 | this.cpCmykEnabled = cpCmykEnabled 324 | this.cpAlphaChannel = cpAlphaChannel 325 | this.cpOutputFormat = cpOutputFormat 326 | 327 | this.cpDialogDisplay = cpDialogDisplay 328 | 329 | this.cpIgnoredElements = cpIgnoredElements 330 | 331 | this.cpSaveClickOutside = cpSaveClickOutside 332 | this.cpCloseClickOutside = cpCloseClickOutside 333 | 334 | this.useRootViewContainer = cpUseRootViewContainer 335 | 336 | this.width = this.cpWidth = parseInt(cpWidth, 10) 337 | this.height = this.cpHeight = parseInt(cpHeight, 10) 338 | 339 | this.cpPosition = cpPosition 340 | this.cpPositionOffset = parseInt(cpPositionOffset, 10) 341 | 342 | this.cpOKButton = cpOKButton 343 | this.cpOKButtonText = cpOKButtonText 344 | this.cpOKButtonClass = cpOKButtonClass 345 | 346 | this.cpCancelButton = cpCancelButton 347 | this.cpCancelButtonText = cpCancelButtonText 348 | this.cpCancelButtonClass = cpCancelButtonClass 349 | 350 | this.cpEyeDropper = cpEyeDropper 351 | 352 | this.fallbackColor = cpFallbackColor || '#fff' 353 | 354 | this.setPresetConfig(cpPresetLabel, cpPresetColors) 355 | 356 | this.cpPresetColorsClass = cpPresetColorsClass 357 | this.cpMaxPresetColorsLength = cpMaxPresetColorsLength 358 | this.cpPresetEmptyMessage = cpPresetEmptyMessage 359 | this.cpPresetEmptyMessageClass = cpPresetEmptyMessageClass 360 | 361 | this.cpAddColorButton = cpAddColorButton 362 | this.cpAddColorButtonText = cpAddColorButtonText 363 | this.cpAddColorButtonClass = cpAddColorButtonClass 364 | this.cpRemoveColorButtonClass = cpRemoveColorButtonClass 365 | 366 | this.cpTriggerElement = cpTriggerElement 367 | this.cpExtraTemplate = cpExtraTemplate 368 | 369 | if (!cpPositionRelativeToArrow) { 370 | this.dialogArrowOffset = 0 371 | } 372 | 373 | if (cpDialogDisplay === 'inline') { 374 | this.dialogArrowSize = 0 375 | this.dialogArrowOffset = 0 376 | } 377 | 378 | if ( 379 | cpOutputFormat === 'hex' && 380 | cpAlphaChannel !== 'always' && 381 | cpAlphaChannel !== 'forced' 382 | ) { 383 | this.cpAlphaChannel = 'disabled' 384 | } 385 | } 386 | 387 | public setColorMode(mode: string): void { 388 | switch (mode.toString().toUpperCase()) { 389 | case '1': 390 | case 'C': 391 | case 'COLOR': 392 | this.cpColorMode = 1 393 | break 394 | case '2': 395 | case 'G': 396 | case 'GRAYSCALE': 397 | this.cpColorMode = 2 398 | break 399 | case '3': 400 | case 'P': 401 | case 'PRESETS': 402 | this.cpColorMode = 3 403 | break 404 | default: 405 | this.cpColorMode = 1 406 | } 407 | } 408 | 409 | public setInitialColor(color: any): void { 410 | this.initialColor = color 411 | } 412 | 413 | public setPresetConfig( 414 | cpPresetLabel: string, 415 | cpPresetColors: string[] 416 | ): void { 417 | this.cpPresetLabel = cpPresetLabel 418 | this.cpPresetColors = cpPresetColors 419 | } 420 | 421 | public setColorFromString( 422 | value: string, 423 | emit: boolean = true, 424 | update: boolean = true 425 | ): void { 426 | let hsva: Hsva | null 427 | 428 | if (this.cpAlphaChannel === 'always' || this.cpAlphaChannel === 'forced') { 429 | hsva = this.service.stringToHsva(value, true) 430 | 431 | if (!hsva && !this.hsva) { 432 | hsva = this.service.stringToHsva(value, false) 433 | } 434 | } else { 435 | hsva = this.service.stringToHsva(value, false) 436 | } 437 | 438 | if (!hsva && !this.hsva) { 439 | hsva = this.service.stringToHsva(this.fallbackColor, false) 440 | } 441 | 442 | if (hsva) { 443 | this.hsva = hsva 444 | 445 | this.sliderH = this.hsva.h 446 | 447 | if (this.cpOutputFormat === 'hex' && this.cpAlphaChannel === 'disabled') { 448 | this.hsva.a = 1 449 | } 450 | 451 | this.updateColorPicker(emit, update) 452 | } 453 | } 454 | 455 | public onResize(): void { 456 | if (this.position === 'fixed') { 457 | this.setDialogPosition() 458 | } else if (this.cpDialogDisplay !== 'inline') { 459 | this.closeColorPicker() 460 | } 461 | } 462 | 463 | public onDragEnd(slider: string): void { 464 | this.directiveInstance.sliderDragEnd({ 465 | slider: slider, 466 | color: this.outputColor 467 | }) 468 | } 469 | 470 | public onDragStart(slider: string): void { 471 | this.directiveInstance.sliderDragStart({ 472 | slider: slider, 473 | color: this.outputColor 474 | }) 475 | } 476 | 477 | public onMouseDown(event: MouseEvent): void { 478 | if ( 479 | this.show && 480 | this.cpDialogDisplay === 'popup' && 481 | event.target !== this.directiveElementRef.nativeElement && 482 | !this.isDescendant(this.elRef.nativeElement, event.target) && 483 | !this.isDescendant( 484 | this.directiveElementRef.nativeElement, 485 | event.target 486 | ) && 487 | this.cpIgnoredElements.filter((item: any) => item === event.target) 488 | .length === 0 489 | ) { 490 | this.ngZone.run(() => { 491 | if (this.cpSaveClickOutside) { 492 | this.directiveInstance.colorSelected(this.outputColor) 493 | } else { 494 | this.hsva = null 495 | 496 | this.setColorFromString(this.initialColor, false) 497 | 498 | if (this.cpCmykEnabled) { 499 | this.directiveInstance.cmykChanged(this.cmykColor) 500 | } 501 | 502 | this.directiveInstance.colorChanged(this.initialColor) 503 | 504 | this.directiveInstance.colorCanceled() 505 | } 506 | 507 | if (this.cpCloseClickOutside) { 508 | this.closeColorPicker() 509 | } 510 | }) 511 | } 512 | } 513 | 514 | public onAcceptColor(event: Event): void { 515 | event.stopPropagation() 516 | 517 | if (this.outputColor) { 518 | this.directiveInstance.colorSelected(this.outputColor) 519 | } 520 | 521 | if (this.cpDialogDisplay === 'popup') { 522 | this.closeColorPicker() 523 | } 524 | } 525 | 526 | public onCancelColor(event: Event): void { 527 | this.hsva = null 528 | 529 | event.stopPropagation() 530 | 531 | this.directiveInstance.colorCanceled() 532 | 533 | this.setColorFromString(this.initialColor, true) 534 | 535 | if (this.cpDialogDisplay === 'popup') { 536 | if (this.cpCmykEnabled) { 537 | this.directiveInstance.cmykChanged(this.cmykColor) 538 | } 539 | 540 | this.directiveInstance.colorChanged(this.initialColor, true) 541 | 542 | this.closeColorPicker() 543 | } 544 | } 545 | 546 | public onEyeDropper(): void { 547 | if (!this.eyeDropperSupported) return 548 | const eyeDropper = new (window as any).EyeDropper() 549 | eyeDropper.open().then((eyeDropperResult: { sRGBHex: string }) => { 550 | this.setColorFromString(eyeDropperResult.sRGBHex, true) 551 | }) 552 | } 553 | 554 | public onFormatToggle(change: number): void { 555 | const availableFormats = 556 | this.dialogInputFields.length - (this.cpCmykEnabled ? 0 : 1) 557 | 558 | const nextFormat = 559 | (((this.dialogInputFields.indexOf(this.format) + change) % 560 | availableFormats) + 561 | availableFormats) % 562 | availableFormats 563 | 564 | this.format = this.dialogInputFields[nextFormat] 565 | } 566 | 567 | public onColorChange(value: { 568 | s: number 569 | v: number 570 | rgX: number 571 | rgY: number 572 | }): void { 573 | this.hsva.s = value.s / value.rgX 574 | this.hsva.v = value.v / value.rgY 575 | 576 | this.updateColorPicker() 577 | 578 | this.directiveInstance.sliderChanged({ 579 | slider: 'lightness', 580 | value: this.hsva.v, 581 | color: this.outputColor 582 | }) 583 | 584 | this.directiveInstance.sliderChanged({ 585 | slider: 'saturation', 586 | value: this.hsva.s, 587 | color: this.outputColor 588 | }) 589 | } 590 | 591 | public onHueChange(value: { v: number; rgX: number }): void { 592 | this.hsva.h = value.v / value.rgX 593 | this.sliderH = this.hsva.h 594 | 595 | this.updateColorPicker() 596 | 597 | this.directiveInstance.sliderChanged({ 598 | slider: 'hue', 599 | value: this.hsva.h, 600 | color: this.outputColor 601 | }) 602 | } 603 | 604 | public onValueChange(value: { v: number; rgX: number }): void { 605 | this.hsva.v = value.v / value.rgX 606 | 607 | this.updateColorPicker() 608 | 609 | this.directiveInstance.sliderChanged({ 610 | slider: 'value', 611 | value: this.hsva.v, 612 | color: this.outputColor 613 | }) 614 | } 615 | 616 | public onAlphaChange(value: { v: number; rgX: number }): void { 617 | this.hsva.a = value.v / value.rgX 618 | 619 | this.updateColorPicker() 620 | 621 | this.directiveInstance.sliderChanged({ 622 | slider: 'alpha', 623 | value: this.hsva.a, 624 | color: this.outputColor 625 | }) 626 | } 627 | 628 | public onHexInput(value: string | null): void { 629 | if (value === null) { 630 | this.updateColorPicker() 631 | } else { 632 | if (value && value[0] !== '#') { 633 | value = '#' + value 634 | } 635 | 636 | let validHex = /^#[a-f0-9]{6}$/gi 637 | 638 | if (this.cpAlphaChannel === 'always') { 639 | validHex = /^#([a-f0-9]{6}|[a-f0-9]{8})$/gi 640 | } 641 | 642 | const valid = validHex.test(value) 643 | 644 | if (valid) { 645 | if (this.cpAlphaChannel === 'forced') { 646 | value += Math.round(this.hsva.a * 255).toString(16) 647 | } 648 | 649 | this.setColorFromString(value, true, false) 650 | } 651 | 652 | this.directiveInstance.inputChanged({ 653 | input: 'hex', 654 | valid: valid, 655 | value: value, 656 | color: this.outputColor 657 | }) 658 | } 659 | } 660 | 661 | public onRedInput(value: { v: number; rg: number }): void { 662 | const rgba = this.service.hsvaToRgba(this.hsva) 663 | 664 | const valid = !isNaN(value.v) && value.v >= 0 && value.v <= value.rg 665 | 666 | if (valid) { 667 | rgba.r = value.v / value.rg 668 | 669 | this.hsva = this.service.rgbaToHsva(rgba) 670 | 671 | this.sliderH = this.hsva.h 672 | 673 | this.updateColorPicker() 674 | } 675 | 676 | this.directiveInstance.inputChanged({ 677 | input: 'red', 678 | valid: valid, 679 | value: rgba.r, 680 | color: this.outputColor 681 | }) 682 | } 683 | 684 | public onBlueInput(value: { v: number; rg: number }): void { 685 | const rgba = this.service.hsvaToRgba(this.hsva) 686 | 687 | const valid = !isNaN(value.v) && value.v >= 0 && value.v <= value.rg 688 | 689 | if (valid) { 690 | rgba.b = value.v / value.rg 691 | 692 | this.hsva = this.service.rgbaToHsva(rgba) 693 | 694 | this.sliderH = this.hsva.h 695 | 696 | this.updateColorPicker() 697 | } 698 | 699 | this.directiveInstance.inputChanged({ 700 | input: 'blue', 701 | valid: valid, 702 | value: rgba.b, 703 | color: this.outputColor 704 | }) 705 | } 706 | 707 | public onGreenInput(value: { v: number; rg: number }): void { 708 | const rgba = this.service.hsvaToRgba(this.hsva) 709 | 710 | const valid = !isNaN(value.v) && value.v >= 0 && value.v <= value.rg 711 | 712 | if (valid) { 713 | rgba.g = value.v / value.rg 714 | 715 | this.hsva = this.service.rgbaToHsva(rgba) 716 | 717 | this.sliderH = this.hsva.h 718 | 719 | this.updateColorPicker() 720 | } 721 | 722 | this.directiveInstance.inputChanged({ 723 | input: 'green', 724 | valid: valid, 725 | value: rgba.g, 726 | color: this.outputColor 727 | }) 728 | } 729 | 730 | public onHueInput(value: { v: number; rg: number }) { 731 | const valid = !isNaN(value.v) && value.v >= 0 && value.v <= value.rg 732 | 733 | if (valid) { 734 | this.hsva.h = value.v / value.rg 735 | 736 | this.sliderH = this.hsva.h 737 | 738 | this.updateColorPicker() 739 | } 740 | 741 | this.directiveInstance.inputChanged({ 742 | input: 'hue', 743 | valid: valid, 744 | value: this.hsva.h, 745 | color: this.outputColor 746 | }) 747 | } 748 | 749 | public onValueInput(value: { v: number; rg: number }): void { 750 | const valid = !isNaN(value.v) && value.v >= 0 && value.v <= value.rg 751 | 752 | if (valid) { 753 | this.hsva.v = value.v / value.rg 754 | 755 | this.updateColorPicker() 756 | } 757 | 758 | this.directiveInstance.inputChanged({ 759 | input: 'value', 760 | valid: valid, 761 | value: this.hsva.v, 762 | color: this.outputColor 763 | }) 764 | } 765 | 766 | public onAlphaInput(value: { v: number; rg: number }): void { 767 | const valid = !isNaN(value.v) && value.v >= 0 && value.v <= value.rg 768 | 769 | if (valid) { 770 | this.hsva.a = value.v / value.rg 771 | 772 | this.updateColorPicker() 773 | } 774 | 775 | this.directiveInstance.inputChanged({ 776 | input: 'alpha', 777 | valid: valid, 778 | value: this.hsva.a, 779 | color: this.outputColor 780 | }) 781 | } 782 | 783 | public onLightnessInput(value: { v: number; rg: number }): void { 784 | const hsla = this.service.hsva2hsla(this.hsva) 785 | 786 | const valid = !isNaN(value.v) && value.v >= 0 && value.v <= value.rg 787 | 788 | if (valid) { 789 | hsla.l = value.v / value.rg 790 | 791 | this.hsva = this.service.hsla2hsva(hsla) 792 | 793 | this.sliderH = this.hsva.h 794 | 795 | this.updateColorPicker() 796 | } 797 | 798 | this.directiveInstance.inputChanged({ 799 | input: 'lightness', 800 | valid: valid, 801 | value: hsla.l, 802 | color: this.outputColor 803 | }) 804 | } 805 | 806 | public onSaturationInput(value: { v: number; rg: number }): void { 807 | const hsla = this.service.hsva2hsla(this.hsva) 808 | 809 | const valid = !isNaN(value.v) && value.v >= 0 && value.v <= value.rg 810 | 811 | if (valid) { 812 | hsla.s = value.v / value.rg 813 | 814 | this.hsva = this.service.hsla2hsva(hsla) 815 | 816 | this.sliderH = this.hsva.h 817 | 818 | this.updateColorPicker() 819 | } 820 | 821 | this.directiveInstance.inputChanged({ 822 | input: 'saturation', 823 | valid: valid, 824 | value: hsla.s, 825 | color: this.outputColor 826 | }) 827 | } 828 | 829 | public onCyanInput(value: { v: number; rg: number }): void { 830 | const valid = !isNaN(value.v) && value.v >= 0 && value.v <= value.rg 831 | 832 | if (valid) { 833 | this.cmyk.c = value.v 834 | 835 | this.updateColorPicker(false, true, true) 836 | } 837 | 838 | this.directiveInstance.inputChanged({ 839 | input: 'cyan', 840 | valid: true, 841 | value: this.cmyk.c, 842 | color: this.outputColor 843 | }) 844 | } 845 | 846 | public onMagentaInput(value: { v: number; rg: number }): void { 847 | const valid = !isNaN(value.v) && value.v >= 0 && value.v <= value.rg 848 | 849 | if (valid) { 850 | this.cmyk.m = value.v 851 | 852 | this.updateColorPicker(false, true, true) 853 | } 854 | 855 | this.directiveInstance.inputChanged({ 856 | input: 'magenta', 857 | valid: true, 858 | value: this.cmyk.m, 859 | color: this.outputColor 860 | }) 861 | } 862 | 863 | public onYellowInput(value: { v: number; rg: number }): void { 864 | const valid = !isNaN(value.v) && value.v >= 0 && value.v <= value.rg 865 | 866 | if (valid) { 867 | this.cmyk.y = value.v 868 | 869 | this.updateColorPicker(false, true, true) 870 | } 871 | 872 | this.directiveInstance.inputChanged({ 873 | input: 'yellow', 874 | valid: true, 875 | value: this.cmyk.y, 876 | color: this.outputColor 877 | }) 878 | } 879 | 880 | public onBlackInput(value: { v: number; rg: number }): void { 881 | const valid = !isNaN(value.v) && value.v >= 0 && value.v <= value.rg 882 | 883 | if (valid) { 884 | this.cmyk.k = value.v 885 | 886 | this.updateColorPicker(false, true, true) 887 | } 888 | 889 | this.directiveInstance.inputChanged({ 890 | input: 'black', 891 | valid: true, 892 | value: this.cmyk.k, 893 | color: this.outputColor 894 | }) 895 | } 896 | 897 | public onAddPresetColor(event: any, value: string): void { 898 | event.stopPropagation() 899 | 900 | if (!this.cpPresetColors.filter((color) => color === value).length) { 901 | this.cpPresetColors = this.cpPresetColors.concat(value) 902 | 903 | this.directiveInstance.presetColorsChanged(this.cpPresetColors) 904 | } 905 | } 906 | 907 | public onRemovePresetColor(event: any, value: string): void { 908 | event.stopPropagation() 909 | 910 | this.cpPresetColors = this.cpPresetColors.filter((color) => color !== value) 911 | 912 | this.directiveInstance.presetColorsChanged(this.cpPresetColors) 913 | } 914 | 915 | // Private helper functions for the color picker dialog status 916 | 917 | private openColorPicker(): void { 918 | if (!this.show) { 919 | this.show = true 920 | this.hidden = true 921 | 922 | setTimeout(() => { 923 | this.hidden = false 924 | 925 | this.setDialogPosition() 926 | 927 | this.cdRef.detectChanges() 928 | }, 0) 929 | 930 | this.directiveInstance.stateChanged(true) 931 | 932 | // The change detection should be run on `mousedown` event only when the condition 933 | // is met within the `onMouseDown` method. 934 | this.ngZone.runOutsideAngular(() => { 935 | // There's no sense to add both event listeners on touch devices since the `touchstart` 936 | // event is handled earlier than `mousedown`, so we'll get 2 change detections and the 937 | // second one will be unnecessary. 938 | if (SUPPORTS_TOUCH) { 939 | this.document.addEventListener('touchstart', this.listenerMouseDown) 940 | } else { 941 | this.document.addEventListener('mousedown', this.listenerMouseDown) 942 | } 943 | }) 944 | 945 | this.window.addEventListener('resize', this.listenerResize) 946 | } 947 | } 948 | 949 | private closeColorPicker(): void { 950 | if (this.show) { 951 | this.show = false 952 | 953 | this.directiveInstance.stateChanged(false) 954 | 955 | if (SUPPORTS_TOUCH) { 956 | this.document.removeEventListener('touchstart', this.listenerMouseDown) 957 | } else { 958 | this.document.removeEventListener('mousedown', this.listenerMouseDown) 959 | } 960 | 961 | this.window.removeEventListener('resize', this.listenerResize) 962 | 963 | if (!this.cdRef['destroyed']) { 964 | this.cdRef.detectChanges() 965 | } 966 | } 967 | } 968 | 969 | private updateColorPicker( 970 | emit: boolean = true, 971 | update: boolean = true, 972 | cmykInput: boolean = false 973 | ): void { 974 | if (this.sliderDimMax) { 975 | if (this.cpColorMode === 2) { 976 | this.hsva.s = 0 977 | } 978 | 979 | let hue: Rgba, hsla: Hsla, rgba: Rgba 980 | 981 | const lastOutput = this.outputColor 982 | 983 | hsla = this.service.hsva2hsla(this.hsva) 984 | 985 | if (!this.cpCmykEnabled) { 986 | rgba = this.service.denormalizeRGBA(this.service.hsvaToRgba(this.hsva)) 987 | } else { 988 | if (!cmykInput) { 989 | rgba = this.service.hsvaToRgba(this.hsva) 990 | 991 | this.cmyk = this.service.denormalizeCMYK( 992 | this.service.rgbaToCmyk(rgba) 993 | ) 994 | } else { 995 | rgba = this.service.cmykToRgb(this.service.normalizeCMYK(this.cmyk)) 996 | 997 | this.hsva = this.service.rgbaToHsva(rgba) 998 | } 999 | 1000 | rgba = this.service.denormalizeRGBA(rgba) 1001 | 1002 | this.sliderH = this.hsva.h 1003 | } 1004 | 1005 | hue = this.service.denormalizeRGBA( 1006 | this.service.hsvaToRgba(new Hsva(this.sliderH || this.hsva.h, 1, 1, 1)) 1007 | ) 1008 | 1009 | if (update) { 1010 | this.hslaText = new Hsla( 1011 | Math.round(hsla.h * 360), 1012 | Math.round(hsla.s * 100), 1013 | Math.round(hsla.l * 100), 1014 | Math.round(hsla.a * 100) / 100 1015 | ) 1016 | 1017 | this.rgbaText = new Rgba( 1018 | rgba.r, 1019 | rgba.g, 1020 | rgba.b, 1021 | Math.round(rgba.a * 100) / 100 1022 | ) 1023 | 1024 | if (this.cpCmykEnabled) { 1025 | this.cmykText = new Cmyk( 1026 | this.cmyk.c, 1027 | this.cmyk.m, 1028 | this.cmyk.y, 1029 | this.cmyk.k, 1030 | Math.round(this.cmyk.a * 100) / 100 1031 | ) 1032 | } 1033 | 1034 | const allowHex8 = this.cpAlphaChannel === 'always' 1035 | 1036 | this.hexText = this.service.rgbaToHex(rgba, allowHex8) 1037 | this.hexAlpha = this.rgbaText.a 1038 | } 1039 | 1040 | if (this.cpOutputFormat === 'auto') { 1041 | if ( 1042 | this.format !== ColorFormats.RGBA && 1043 | this.format !== ColorFormats.CMYK && 1044 | this.format !== ColorFormats.HSLA 1045 | ) { 1046 | if (this.hsva.a < 1) { 1047 | this.format = this.hsva.a < 1 ? ColorFormats.RGBA : ColorFormats.HEX 1048 | } 1049 | } 1050 | } 1051 | 1052 | this.hueSliderColor = 'rgb(' + hue.r + ',' + hue.g + ',' + hue.b + ')' 1053 | this.alphaSliderColor = 1054 | 'rgb(' + rgba.r + ',' + rgba.g + ',' + rgba.b + ')' 1055 | 1056 | this.outputColor = this.service.outputFormat( 1057 | this.hsva, 1058 | this.cpOutputFormat, 1059 | this.cpAlphaChannel 1060 | ) 1061 | this.selectedColor = this.service.outputFormat(this.hsva, 'rgba', null) 1062 | 1063 | if (this.format !== ColorFormats.CMYK) { 1064 | this.cmykColor = '' 1065 | } else { 1066 | if ( 1067 | this.cpAlphaChannel === 'always' || 1068 | this.cpAlphaChannel === 'enabled' || 1069 | this.cpAlphaChannel === 'forced' 1070 | ) { 1071 | const alpha = Math.round(this.cmyk.a * 100) / 100 1072 | 1073 | this.cmykColor = `cmyka(${this.cmyk.c},${this.cmyk.m},${this.cmyk.y},${this.cmyk.k},${alpha})` 1074 | } else { 1075 | this.cmykColor = `cmyk(${this.cmyk.c},${this.cmyk.m},${this.cmyk.y},${this.cmyk.k})` 1076 | } 1077 | } 1078 | 1079 | this.slider = new SliderPosition( 1080 | (this.sliderH || this.hsva.h) * this.sliderDimMax.h - 8, 1081 | this.hsva.s * this.sliderDimMax.s - 8, 1082 | (1 - this.hsva.v) * this.sliderDimMax.v - 8, 1083 | this.hsva.a * this.sliderDimMax.a - 8 1084 | ) 1085 | 1086 | if (emit && lastOutput !== this.outputColor) { 1087 | if (this.cpCmykEnabled) { 1088 | this.directiveInstance.cmykChanged(this.cmykColor) 1089 | } 1090 | 1091 | this.directiveInstance.colorChanged(this.outputColor) 1092 | } 1093 | } 1094 | } 1095 | 1096 | // Private helper functions for the color picker dialog positioning 1097 | 1098 | private setDialogPosition(): void { 1099 | if (this.cpDialogDisplay === 'inline') { 1100 | this.position = 'relative' 1101 | } else { 1102 | let position = 'static', 1103 | transform = '', 1104 | style 1105 | 1106 | let parentNode: any = null, 1107 | transformNode: any = null 1108 | 1109 | let node = this.directiveElementRef.nativeElement.parentNode 1110 | 1111 | const dialogHeight = this.dialogElement.nativeElement.offsetHeight 1112 | 1113 | while (node !== null && node.tagName !== 'HTML') { 1114 | style = this.window.getComputedStyle(node) 1115 | position = style.getPropertyValue('position') 1116 | transform = style.getPropertyValue('transform') 1117 | 1118 | if (position !== 'static' && parentNode === null) { 1119 | parentNode = node 1120 | } 1121 | 1122 | if (transform && transform !== 'none' && transformNode === null) { 1123 | transformNode = node 1124 | } 1125 | 1126 | if (position === 'fixed') { 1127 | parentNode = transformNode 1128 | 1129 | break 1130 | } 1131 | 1132 | node = node.parentNode 1133 | } 1134 | 1135 | const boxDirective = this.createDialogBox( 1136 | this.directiveElementRef.nativeElement, 1137 | position !== 'fixed' 1138 | ) 1139 | 1140 | if ( 1141 | this.useRootViewContainer || 1142 | (position === 'fixed' && 1143 | (!parentNode || parentNode instanceof HTMLUnknownElement)) 1144 | ) { 1145 | this.top = boxDirective.top 1146 | this.left = boxDirective.left 1147 | } else { 1148 | if (parentNode === null) { 1149 | parentNode = node 1150 | } 1151 | 1152 | const boxParent = this.createDialogBox(parentNode, position !== 'fixed') 1153 | 1154 | this.top = boxDirective.top - boxParent.top 1155 | this.left = boxDirective.left - boxParent.left 1156 | } 1157 | 1158 | if (position === 'fixed') { 1159 | this.position = 'fixed' 1160 | } 1161 | 1162 | let usePosition = this.cpPosition 1163 | 1164 | const dialogBounds = 1165 | this.dialogElement.nativeElement.getBoundingClientRect() 1166 | if (this.cpPosition === 'auto') { 1167 | const triggerBounds = 1168 | this.cpTriggerElement.nativeElement.getBoundingClientRect() 1169 | usePosition = calculateAutoPositioning( 1170 | dialogBounds, 1171 | triggerBounds, 1172 | this.window 1173 | ) 1174 | } 1175 | 1176 | this.arrowTop = usePosition === 'top' ? dialogHeight - 1 : undefined 1177 | this.cpArrowPosition = undefined 1178 | 1179 | switch (usePosition) { 1180 | case 'top': 1181 | this.top -= dialogHeight + this.dialogArrowSize 1182 | this.left += 1183 | (this.cpPositionOffset / 100) * boxDirective.width - 1184 | this.dialogArrowOffset 1185 | break 1186 | case 'bottom': 1187 | this.top += boxDirective.height + this.dialogArrowSize 1188 | this.left += 1189 | (this.cpPositionOffset / 100) * boxDirective.width - 1190 | this.dialogArrowOffset 1191 | break 1192 | case 'top-left': 1193 | case 'left-top': 1194 | this.top -= 1195 | dialogHeight - 1196 | boxDirective.height + 1197 | (boxDirective.height * this.cpPositionOffset) / 100 1198 | this.left -= 1199 | this.cpWidth + this.dialogArrowSize - 2 - this.dialogArrowOffset 1200 | break 1201 | case 'top-right': 1202 | case 'right-top': 1203 | this.top -= 1204 | dialogHeight - 1205 | boxDirective.height + 1206 | (boxDirective.height * this.cpPositionOffset) / 100 1207 | this.left += 1208 | boxDirective.width + 1209 | this.dialogArrowSize - 1210 | 2 - 1211 | this.dialogArrowOffset 1212 | break 1213 | case 'left': 1214 | case 'bottom-left': 1215 | case 'left-bottom': 1216 | this.top += 1217 | (boxDirective.height * this.cpPositionOffset) / 100 - 1218 | this.dialogArrowOffset 1219 | this.left -= this.cpWidth + this.dialogArrowSize - 2 1220 | break 1221 | case 'right': 1222 | case 'bottom-right': 1223 | case 'right-bottom': 1224 | default: 1225 | this.top += 1226 | (boxDirective.height * this.cpPositionOffset) / 100 - 1227 | this.dialogArrowOffset 1228 | this.left += boxDirective.width + this.dialogArrowSize - 2 1229 | break 1230 | } 1231 | 1232 | const windowInnerHeight = this.window.innerHeight 1233 | const windowInnerWidth = this.window.innerWidth 1234 | const elRefClientRect = this.elRef.nativeElement.getBoundingClientRect() 1235 | const bottom = this.top + dialogBounds.height 1236 | if (bottom > windowInnerHeight) { 1237 | this.top = windowInnerHeight - dialogBounds.height 1238 | this.cpArrowPosition = elRefClientRect.x / 2 - 20 1239 | } 1240 | const right = this.left + dialogBounds.width 1241 | if (right > windowInnerWidth) { 1242 | this.left = windowInnerWidth - dialogBounds.width 1243 | this.cpArrowPosition = elRefClientRect.x / 2 - 20 1244 | } 1245 | 1246 | this.cpUsePosition = usePosition 1247 | } 1248 | } 1249 | 1250 | // Private helper functions for the color picker dialog positioning and opening 1251 | 1252 | private isDescendant(parent: any, child: any): boolean { 1253 | let node: any = child.parentNode 1254 | 1255 | while (node !== null) { 1256 | if (node === parent) { 1257 | return true 1258 | } 1259 | 1260 | node = node.parentNode 1261 | } 1262 | 1263 | return false 1264 | } 1265 | 1266 | private createDialogBox(element: any, offset: boolean): any { 1267 | const { top, left } = element.getBoundingClientRect() 1268 | return { 1269 | top: top + (offset ? this.window.pageYOffset : 0), 1270 | left: left + (offset ? this.window.pageXOffset : 0), 1271 | width: element.offsetWidth, 1272 | height: element.offsetHeight 1273 | } 1274 | } 1275 | } 1276 | --------------------------------------------------------------------------------