├── 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 |
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 |
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 |
7 |
8 |
9 |
10 |
11 |
12 |
15 |
16 |
= cpMaxPresetColorsLength" (click)="onAddPresetColor($event, selectedColor)">
17 | {{cpAddColorButtonText}}
18 |
19 |
20 |
21 |
22 |
23 |
24 |
27 |
28 |
31 |
32 |
35 |
36 |
37 |
38 |
51 |
52 |
64 |
65 |
77 |
78 |
90 |
91 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
{{cpPresetLabel}}
111 |
112 |
117 |
118 |
{{cpPresetEmptyMessage}}
119 |
120 |
121 |
122 | {{cpCancelButtonText}}
123 |
124 | {{cpOKButtonText}}
125 |
126 |
127 |
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 |
Toggle
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 |
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 | Options
483 | Values (default values in bold)
484 |
485 |
486 |
487 |
488 |
489 | cpOutputFormat
490 |
491 | 'auto' , 'hex', 'rgba', 'hsla'
492 |
493 |
494 |
495 |
496 | cpPosition
497 |
498 | 'auto' , 'top', 'bottom', 'top-right', 'top-left', 'bottom-left', 'bottom-right'
499 |
500 |
501 |
502 |
503 | cpPositionOffset
504 |
505 | '0%'
506 | Dialog offset (percent) relative to the element that contains the directive.
507 |
508 |
509 |
510 |
511 | cpPositionRelativeToArrow
512 |
513 | false , true
514 | Dialog position is calculated relative to the dialog (false) or relative to the dialog arrow (true).
515 |
516 |
517 |
518 |
519 | cpWidth
520 |
521 | '230px'
522 | Use this option to set color picker dialog width (pixels).
523 |
524 |
525 |
526 |
527 | cpHeight
528 |
529 | 'auto'
530 | Use this option to force color picker dialog height (pixels).
531 |
532 |
533 |
534 |
535 | cpSaveClickOutside
536 |
537 | true , false
538 | If true the initial color is restored when user clicks outside.
539 |
540 |
541 |
542 |
543 | cpOKButton
544 |
545 | false , true
546 | Shows the Ok button. Saves the selected color.
547 |
548 |
549 |
550 |
551 | cpOKButtonText
552 |
553 | 'OK'
554 |
555 |
556 |
557 |
558 | cpOKButtonClass
559 |
560 | Class to customize the OK button.
561 |
562 |
563 |
564 |
565 | cpCancelButton
566 |
567 | false , true
568 | Shows the Cancel button. Cancel the selected color.
569 |
570 |
571 |
572 |
573 | cpCancelButtonText
574 |
575 | 'Cancel'
576 |
577 |
578 |
579 |
580 | cpCancelButtonClass
581 |
582 | Class to customize the Cancel button.
583 |
584 |
585 |
586 |
587 | cpFallbackColor
588 |
589 | '#fff'
590 | Is used when the color is not well-formed or not defined.
591 |
592 |
593 |
594 |
595 | cpPresetLabel
596 |
597 | 'Preset colors'
598 | Label for preset colors if any provided used.
599 |
600 |
601 |
602 |
603 | cpPresetColors
604 |
605 | []
606 | Array of preset colors to show in the color picker dialog.
607 |
608 |
609 |
610 |
611 | cpToggle
612 |
613 | false , true
614 | Input/ouput to open/close the color picker.
615 |
616 |
617 |
618 |
619 | cpIgnoredElements
620 |
621 | []
622 | Array of HTML elements that will be ignored by the color picker when they are clicked.
623 |
624 |
625 |
626 |
627 | cpDialogDisplay
628 |
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 |
633 |
634 |
635 |
636 | cpDisableInput
637 |
638 | false , true
639 | Disables / hides the color input field from the dialog.
640 |
641 |
642 |
643 |
644 | cpAlphaChannel
645 |
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 |
652 |
653 |
654 |
655 | cpCmykEnabled
656 |
657 | false , true
658 | Enables CMYK color input and selected CMYK color event sending on color change.
659 |
660 |
661 |
662 |
663 | cpUseRootViewContainer
664 |
665 | false , true
666 | Create dialog component in the root view container instead the elements view container.
667 |
668 |
669 |
670 |
671 | cpAddColorButton
672 |
673 | false , true
674 | Add or remove colors into your preset panel. The [cpPresetColors] is needed
675 |
676 |
677 |
678 |
679 | cpAddColorButtonText
680 |
681 | 'Add color'
682 |
683 |
684 |
685 |
686 | cpAddColorButtonClass
687 |
688 | Class to customize the add color button.
689 |
690 |
691 |
692 |
693 | cpRemoveColorButtonClass
694 |
695 | Class to customize the remove color button.
696 |
697 |
698 |
699 |
700 | cpPresetColorsClass
701 |
702 | Class to customize the preset colors container.
703 |
704 |
705 |
706 |
707 | cpMaxPresetColorsLength
708 |
709 | 8 (number)
710 | Use this option to set the max colors allowed into preset panel.
711 |
712 |
713 |
714 |
715 | cpPresetEmptyMessage
716 |
717 | 'No colors added'
718 | Message for empty colors if any provided used.
719 |
720 |
721 |
722 |
723 | cpPresetEmptyMessageClass
724 |
725 | Class to customize the empty colors message.
726 |
727 |
728 |
729 | cpEyeDropper
730 |
731 | Enable eye dropper on click of colored circle. Click again to pick a color.
732 |
733 |
734 |
735 |
736 |
737 |
738 |
739 |
740 |
741 |
742 |
743 |
744 |
745 |
746 | Events
747 | Description (data format in bold)
748 |
749 |
750 |
751 |
752 |
753 | colorPickerChange
754 |
755 | Changed color value, send when color is changed. (value: string)
756 |
757 |
758 |
759 |
760 | colorPickerSelect
761 |
762 | Selected color value, send when user presses the OK button. (value: string)
763 |
764 |
765 |
766 |
767 | cpToggleChange
768 |
769 | Status of the dialog, send when dialog is opened / closed. (open: boolean)
770 |
771 |
772 |
773 |
774 | cpInputChange
775 |
776 | Input name and its value, send when user changes color through inputs. ({{ '{' }}input: string, value: string{{ '}' }})
777 |
778 |
779 |
780 |
781 | cpSliderChange
782 |
783 | Slider name and its value, send when user changes color through slider. ({{ '{' }}slider: string, value: Object{{ '}' }})
784 |
785 |
786 |
787 |
788 | cpCmykColorChange
789 |
790 | CMYK color value, send when on color change if cpCmykEnabled is true. (value: string)
791 |
792 |
793 |
794 |
795 | cpPresetColorsChange
796 |
797 | Preset colors value, send when Add Color button is pressed. (value: array)
798 |
799 |
800 |
801 |
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 |
--------------------------------------------------------------------------------