├── .browserslistrc
├── .codecov.yml
├── .editorconfig
├── .eslintrc.json
├── .github
└── workflows
│ └── ci.yml
├── .gitignore
├── .nvmrc
├── .prettierrc
├── LICENSE
├── README.md
├── angular.json
├── karma.conf.js
├── misc
└── sketch-example.png
├── package-lock.json
├── package.json
├── public
└── favicon.ico
├── src
├── app
│ ├── app.component.css
│ ├── app.component.html
│ ├── app.component.spec.ts
│ ├── app.component.ts
│ ├── app.module.ts
│ ├── footer.component.ts
│ └── package.json
├── assets
│ └── .gitkeep
├── environments
│ ├── environment.prod.ts
│ └── environment.ts
├── index.html
├── lib
│ ├── alpha.component.ts
│ ├── alpha
│ │ ├── alpha-picker.component.ts
│ │ ├── alpha.spec.ts
│ │ ├── ng-package.json
│ │ ├── package.json
│ │ └── public_api.ts
│ ├── block
│ │ ├── block-swatches.component.ts
│ │ ├── block.component.ts
│ │ ├── block.spec.ts
│ │ ├── ng-package.json
│ │ ├── package.json
│ │ └── public_api.ts
│ ├── checkboard.component.ts
│ ├── chrome
│ │ ├── chrome-fields.component.ts
│ │ ├── chrome.component.ts
│ │ ├── chrome.spec.ts
│ │ ├── ng-package.json
│ │ ├── package.json
│ │ └── public_api.ts
│ ├── circle
│ │ ├── circle-swatch.component.ts
│ │ ├── circle.component.ts
│ │ ├── circle.spec.ts
│ │ ├── ng-package.json
│ │ ├── package.json
│ │ └── public_api.ts
│ ├── color-wrap.component.ts
│ ├── compact
│ │ ├── compact-color.component.ts
│ │ ├── compact-fields.component.ts
│ │ ├── compact.component.ts
│ │ ├── compact.spec.ts
│ │ ├── ng-package.json
│ │ ├── package.json
│ │ └── public_api.ts
│ ├── coordinates.directive.ts
│ ├── editable-input.component.ts
│ ├── github
│ │ ├── github-swatch.component.ts
│ │ ├── github.component.ts
│ │ ├── github.spec.ts
│ │ ├── ng-package.json
│ │ ├── package.json
│ │ └── public_api.ts
│ ├── helpers
│ │ ├── checkboard.ts
│ │ ├── color.interfaces.ts
│ │ └── color.ts
│ ├── hue.component.ts
│ ├── hue
│ │ ├── hue-picker.component.ts
│ │ ├── hue.spec.ts
│ │ ├── ng-package.json
│ │ ├── package.json
│ │ └── public_api.ts
│ ├── material
│ │ ├── material.component.ts
│ │ ├── material.spec.ts
│ │ ├── ng-package.json
│ │ ├── package.json
│ │ └── public_api.ts
│ ├── ng-package.json
│ ├── package.json
│ ├── photoshop
│ │ ├── ng-package.json
│ │ ├── package.json
│ │ ├── photoshop-button.component.ts
│ │ ├── photoshop-fields.component.ts
│ │ ├── photoshop-previews.component.ts
│ │ ├── photoshop.component.ts
│ │ ├── photoshop.spec.ts
│ │ └── public_api.ts
│ ├── public_api.ts
│ ├── raised.component.ts
│ ├── saturation.component.ts
│ ├── shade.component.ts
│ ├── shade
│ │ ├── ng-package.json
│ │ ├── package.json
│ │ ├── public_api.ts
│ │ ├── shade-picker.component.ts
│ │ └── shade-picker.spec.ts
│ ├── sketch
│ │ ├── ng-package.json
│ │ ├── package.json
│ │ ├── public_api.ts
│ │ ├── sketch-fields.component.ts
│ │ ├── sketch-preset-colors.component.ts
│ │ ├── sketch.component.ts
│ │ └── sketch.spec.ts
│ ├── slider
│ │ ├── ng-package.json
│ │ ├── package.json
│ │ ├── public_api.ts
│ │ ├── slider-swatch.component.ts
│ │ ├── slider-swatches.component.ts
│ │ ├── slider.component.ts
│ │ └── slider.spec.ts
│ ├── swatch.component.ts
│ ├── swatches
│ │ ├── ng-package.json
│ │ ├── package.json
│ │ ├── public_api.ts
│ │ ├── swatches-color.component.ts
│ │ ├── swatches-group.component.ts
│ │ ├── swatches.component.ts
│ │ └── swatches.spec.ts
│ └── twitter
│ │ ├── ng-package.json
│ │ ├── package.json
│ │ ├── public_api.ts
│ │ ├── twitter.component.ts
│ │ └── twitter.spec.ts
├── main.ts
├── polyfills.ts
├── styles.css
├── test.ts
└── types.d.ts
├── tsconfig.app.json
├── tsconfig.json
├── tsconfig.spec.json
└── vercel.json
/.browserslistrc:
--------------------------------------------------------------------------------
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 | # For the full list of supported browsers by the Angular framework, please see:
6 | # https://angular.io/guide/browser-support
7 |
8 | # You can see what browsers were selected by your queries by running:
9 | # npx browserslist
10 |
11 | last 1 Chrome version
12 | last 1 Firefox version
13 | last 1 Safari major versions
14 | not IE 11
15 |
--------------------------------------------------------------------------------
/.codecov.yml:
--------------------------------------------------------------------------------
1 | coverage:
2 | range: '50..100'
3 | status:
4 | project: no
5 | patch: no
6 | comment:
7 | require_changes: yes
8 | behavior: once
9 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # Editor configuration, see https://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | indent_style = space
7 | indent_size = 2
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | [*.ts]
12 | quote_type = single
13 |
14 | [*.md]
15 | max_line_length = off
16 | trim_trailing_whitespace = false
17 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "root": true,
3 | "ignorePatterns": ["projects/**/*"],
4 | "overrides": [
5 | {
6 | "files": ["*.ts"],
7 | "parserOptions": {
8 | "project": ["tsconfig.json", "e2e/tsconfig.json"],
9 | "createDefaultProgram": true
10 | },
11 | "extends": [
12 | "plugin:@angular-eslint/recommended",
13 | "plugin:@angular-eslint/template/process-inline-templates"
14 | ],
15 | "rules": {
16 | "@angular-eslint/no-output-on-prefix": "off",
17 | "@angular-eslint/component-class-suffix": "off",
18 | "@angular-eslint/prefer-standalone": "off"
19 | }
20 | },
21 | {
22 | "files": ["*.html"],
23 | "extends": ["plugin:@angular-eslint/template/recommended"],
24 | "rules": {}
25 | }
26 | ]
27 | }
28 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 | pull_request:
8 |
9 | jobs:
10 | build:
11 | runs-on: ubuntu-latest
12 | steps:
13 | - uses: actions/checkout@v4
14 | - uses: actions/setup-node@v4
15 | with:
16 | node-version: 22
17 | cache: 'npm'
18 | - run: npm ci
19 | - name: lint
20 | run: npm run lint
21 | - run: npm run build
22 | - name: test
23 | run: npm run test:ci
24 | - name: coverage
25 | uses: codecov/codecov-action@v3
26 | with:
27 | token: ${{ secrets.CODECOV_TOKEN }}
28 |
29 | publish:
30 | needs: build
31 | runs-on: ubuntu-latest
32 | if: github.ref_name == 'master'
33 | permissions:
34 | contents: write # to be able to publish a GitHub release
35 | issues: write # to be able to comment on released issues
36 | pull-requests: write # to be able to comment on released pull requests
37 | id-token: write # to enable use of OIDC for npm provenance
38 | steps:
39 | - uses: actions/checkout@v4
40 | - uses: actions/setup-node@v4
41 | with:
42 | node-version: 22
43 | cache: 'npm'
44 | - run: npm ci
45 | - run: npm run build
46 | - name: release
47 | run: cd dist && npx semantic-release
48 | env:
49 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
50 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
51 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See http://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # compiled output
4 | /dist
5 | /tmp
6 | /out-tsc
7 |
8 | # dependencies
9 | /node_modules
10 |
11 | # IDEs and editors
12 | /.idea
13 | .project
14 | .classpath
15 | .c9/
16 | *.launch
17 | .settings/
18 | *.sublime-workspace
19 |
20 | # IDE - VSCode
21 | .vscode/*
22 | !.vscode/settings.json
23 | !.vscode/tasks.json
24 | !.vscode/launch.json
25 | !.vscode/extensions.json
26 |
27 | # misc
28 | /.angular/cache
29 | /.sass-cache
30 | /connect.lock
31 | coverage
32 | /libpeerconnection.log
33 | npm-debug.log
34 | yarn-error.log
35 | testem.log
36 | /typings
37 |
38 | # System Files
39 | .DS_Store
40 | Thumbs.db
41 |
--------------------------------------------------------------------------------
/.nvmrc:
--------------------------------------------------------------------------------
1 | 22
2 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "semi": true,
3 | "singleQuote": true,
4 | "trailingComma": "all",
5 | "bracketSpacing": true,
6 | "printWidth": 100,
7 | "arrowParens": "avoid",
8 | "tabWidth": 2
9 | }
10 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) Scott Cooper
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/angular.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
3 | "version": 1,
4 | "newProjectRoot": "projects",
5 | "projects": {
6 | "ngx-color": {
7 | "projectType": "application",
8 | "schematics": {
9 | "@schematics/angular:component": {
10 | "style": "css"
11 | },
12 | "@schematics/angular:application": {
13 | "strict": true
14 | }
15 | },
16 | "root": "",
17 | "sourceRoot": "src",
18 | "prefix": "app",
19 | "architect": {
20 | "build": {
21 | "builder": "@angular-devkit/build-angular:application",
22 | "options": {
23 | "outputPath": {
24 | "base": "dist"
25 | },
26 | "index": "src/index.html",
27 | "polyfills": ["src/polyfills.ts"],
28 | "tsConfig": "tsconfig.app.json",
29 | "assets": [
30 | {
31 | "glob": "**/*",
32 | "input": "public"
33 | },
34 | "src/assets"
35 | ],
36 | "styles": ["src/styles.css"],
37 | "scripts": [],
38 | "extractLicenses": false,
39 | "sourceMap": true,
40 | "optimization": false,
41 | "namedChunks": true,
42 | "browser": "src/main.ts"
43 | },
44 | "configurations": {
45 | "production": {
46 | "fileReplacements": [
47 | {
48 | "replace": "src/environments/environment.ts",
49 | "with": "src/environments/environment.prod.ts"
50 | }
51 | ],
52 | "optimization": true,
53 | "outputHashing": "all",
54 | "sourceMap": false,
55 | "namedChunks": false,
56 | "extractLicenses": true,
57 | "budgets": [
58 | {
59 | "type": "initial",
60 | "maximumWarning": "500kb",
61 | "maximumError": "1mb"
62 | },
63 | {
64 | "type": "anyComponentStyle",
65 | "maximumWarning": "2kb",
66 | "maximumError": "4kb"
67 | }
68 | ]
69 | }
70 | }
71 | },
72 | "serve": {
73 | "builder": "@angular-devkit/build-angular:dev-server",
74 | "options": {
75 | "buildTarget": "ngx-color:build"
76 | },
77 | "configurations": {
78 | "production": {
79 | "buildTarget": "ngx-color:build:production"
80 | }
81 | }
82 | },
83 | "extract-i18n": {
84 | "builder": "@angular-devkit/build-angular:extract-i18n",
85 | "options": {
86 | "buildTarget": "ngx-color:build"
87 | }
88 | },
89 | "test": {
90 | "builder": "@angular-devkit/build-angular:karma",
91 | "options": {
92 | "main": "src/test.ts",
93 | "polyfills": "src/polyfills.ts",
94 | "tsConfig": "tsconfig.spec.json",
95 | "karmaConfig": "karma.conf.js",
96 | "assets": [
97 | {
98 | "glob": "**/*",
99 | "input": "public"
100 | },
101 | "src/assets"
102 | ],
103 | "styles": ["src/styles.css"],
104 | "scripts": []
105 | }
106 | },
107 | "lint": {
108 | "builder": "@angular-eslint/builder:lint",
109 | "options": {
110 | "lintFilePatterns": ["src/**/*.ts", "src/**/*.html"]
111 | }
112 | }
113 | }
114 | }
115 | },
116 | "cli": {
117 | "analytics": false
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/karma.conf.js:
--------------------------------------------------------------------------------
1 | // Karma configuration file, see link for more information
2 | // https://karma-runner.github.io/1.0/config/configuration-file.html
3 |
4 | module.exports = function (config) {
5 | config.set({
6 | basePath: '',
7 | frameworks: ['jasmine', '@angular-devkit/build-angular'],
8 | plugins: [
9 | require('karma-jasmine'),
10 | require('karma-chrome-launcher'),
11 | require('karma-jasmine-html-reporter'),
12 | require('karma-coverage-istanbul-reporter'),
13 | require('@angular-devkit/build-angular/plugins/karma'),
14 | ],
15 | client: {
16 | clearContext: false, // leave Jasmine Spec Runner output visible in browser
17 | },
18 | coverageIstanbulReporter: {
19 | dir: require('path').join(__dirname, './coverage/zzz'),
20 | reports: ['html', 'lcovonly', 'text-summary'],
21 | fixWebpackSourcePaths: true,
22 | },
23 | reporters: ['coverage-istanbul', 'progress', 'kjhtml'],
24 | port: 9876,
25 | colors: true,
26 | logLevel: config.LOG_INFO,
27 | autoWatch: true,
28 | browsers: ['Chrome'],
29 | customLaunchers: {
30 | ChromeHeadlessCustom: {
31 | base: 'ChromeHeadless',
32 | flags: ['--no-sandbox', '--disable-gpu'],
33 | },
34 | },
35 | singleRun: false,
36 | restartOnFileChange: true,
37 | });
38 | };
39 |
--------------------------------------------------------------------------------
/misc/sketch-example.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scttcper/ngx-color/c2d3742e098bc7ea14bb7e44d80ea88233efe0af/misc/sketch-example.png
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ngx-color",
3 | "version": "0.0.0",
4 | "license": "MIT",
5 | "homepage": "https://github.com/scttcper/ngx-color",
6 | "bugs": "https://github.com/scttcper/ngx-color/issues",
7 | "repository": "scttcper/ngx-color",
8 | "scripts": {
9 | "ng": "ng",
10 | "start": "ng serve",
11 | "build": "ng-packagr -p ./src/lib/ng-package.json && cp README.md ./dist && cp LICENSE ./dist",
12 | "ghpages": "ng build --configuration production --no-progress",
13 | "test": "ng test --watch=false --code-coverage",
14 | "test:watch": "ng test",
15 | "test:ci": "ng test --watch=false --code-coverage --no-progress --browsers=ChromeHeadlessCustom",
16 | "lint": "ng lint && prettier --check .",
17 | "lint:fix": "ng lint --fix && prettier --write .",
18 | "format": "prettier --write ."
19 | },
20 | "private": true,
21 | "dependencies": {
22 | "@ctrl/tinycolor": "4.1.0",
23 | "material-colors": "1.2.6"
24 | },
25 | "devDependencies": {
26 | "@angular-devkit/build-angular": "19.1.4",
27 | "@angular-eslint/builder": "19.0.2",
28 | "@angular-eslint/eslint-plugin": "19.0.2",
29 | "@angular-eslint/eslint-plugin-template": "19.0.2",
30 | "@angular-eslint/template-parser": "19.0.2",
31 | "@angular/animations": "19.1.3",
32 | "@angular/cli": "19.1.4",
33 | "@angular/common": "19.1.3",
34 | "@angular/compiler": "19.1.3",
35 | "@angular/compiler-cli": "19.1.3",
36 | "@angular/core": "19.1.3",
37 | "@angular/forms": "19.1.3",
38 | "@angular/language-service": "19.1.3",
39 | "@angular/platform-browser": "19.1.3",
40 | "@angular/platform-browser-dynamic": "19.1.3",
41 | "@angular/router": "19.1.3",
42 | "@ctrl/ngx-github-buttons": "9.0.0",
43 | "@types/fs-extra": "11.0.4",
44 | "@types/jasmine": "5.1.5",
45 | "@types/node": "22.12.0",
46 | "@typescript-eslint/eslint-plugin": "8.22.0",
47 | "@typescript-eslint/parser": "8.22.0",
48 | "bootstrap": "5.3.3",
49 | "core-js": "3.30.2",
50 | "del": "8.0.0",
51 | "eslint": "9.19.0",
52 | "fs-extra": "11.3.0",
53 | "jasmine-core": "5.5.0",
54 | "karma": "6.4.4",
55 | "karma-chrome-launcher": "3.2.0",
56 | "karma-cli": "2.0.0",
57 | "karma-coverage-istanbul-reporter": "3.0.3",
58 | "karma-jasmine": "5.1.0",
59 | "karma-jasmine-html-reporter": "2.1.0",
60 | "ng-packagr": "19.1.2",
61 | "prettier": "3.2.5",
62 | "puppeteer": "20.2.0",
63 | "rxjs": "7.8.1",
64 | "tslib": "2.8.1",
65 | "typescript": "5.7.3",
66 | "zone.js": "0.15.0"
67 | },
68 | "release": {
69 | "branches": [
70 | "master"
71 | ]
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scttcper/ngx-color/c2d3742e098bc7ea14bb7e44d80ea88233efe0af/public/favicon.ico
--------------------------------------------------------------------------------
/src/app/app.component.css:
--------------------------------------------------------------------------------
1 | .home {
2 | position: absolute;
3 | display: block;
4 | }
5 |
6 | .cover {
7 | bottom: 0;
8 | left: 0;
9 | opacity: 0.5;
10 | position: absolute;
11 | right: 0;
12 | top: 0;
13 | transition: background-color 100ms linear;
14 | }
15 |
16 | .title {
17 | color: rgba(0, 0, 0, 0.65);
18 | font-size: 52px;
19 | padding-top: 70px;
20 | }
21 |
22 | .description {
23 | color: rgba(0, 0, 0, 0.6);
24 | font-size: 20px;
25 | font-weight: 300;
26 | line-height: 27px;
27 | padding-top: 15px;
28 | }
29 | .description > a {
30 | color: rgba(0, 0, 0, 0.6);
31 | text-decoration: underline;
32 | }
33 |
34 | .label {
35 | text-align: center;
36 | width: 100%;
37 | color: rgba(0, 0, 0, 0.4);
38 | font-size: 12px;
39 | margin-top: 5px;
40 | }
41 | .white-label {
42 | width: 100%;
43 | font-size: 12px;
44 | margin-top: 10px;
45 | color: rgba(255, 255, 255, 0.7);
46 | }
47 |
--------------------------------------------------------------------------------
/src/app/app.component.html:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
Angular Color
8 |
12 |
13 | A Collection of Color Pickers from Sketch, Photoshop, Chrome, Github, Twitter, Material
14 | Design & more
15 |
16 |
17 |
18 |
19 |
20 |
28 |
29 |
30 |
38 |
39 |
40 |
41 |
Photoshop
42 |
43 |
44 |
124 |
125 |
126 |
136 |
137 |
138 |
139 |
140 |
145 |
Compact
146 |
147 |
148 |
149 |
150 |
151 |
152 |
157 |
Material
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
171 |
Swatches
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
185 |
Shade Slider
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
--------------------------------------------------------------------------------
/src/app/app.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed, waitForAsync } from '@angular/core/testing';
2 |
3 | import { AppComponent } from './app.component';
4 | import { AppModule } from './app.module';
5 |
6 | describe('AppComponent', () => {
7 | beforeEach(waitForAsync(() => {
8 | TestBed.configureTestingModule({
9 | declarations: [],
10 | imports: [AppModule],
11 | }).compileComponents();
12 | }));
13 | it('should create the app', () => {
14 | const fixture = TestBed.createComponent(AppComponent);
15 | const app = fixture.debugElement.componentInstance;
16 | fixture.detectChanges();
17 | expect(app).toBeTruthy();
18 | });
19 | });
20 |
--------------------------------------------------------------------------------
/src/app/app.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | import { ColorEvent } from 'ngx-color';
4 |
5 | @Component({
6 | selector: 'app-root',
7 | templateUrl: './app.component.html',
8 | styleUrls: ['./app.component.css'],
9 | standalone: false,
10 | })
11 | export class AppComponent {
12 | title = 'app';
13 | primaryColor = '#194D33';
14 | state = {
15 | h: 150,
16 | s: 0.5,
17 | l: 0.2,
18 | a: 1,
19 | };
20 |
21 | changeComplete($event: ColorEvent): void {
22 | this.state = $event.color.hsl;
23 | this.primaryColor = $event.color.hex;
24 | console.log('changeComplete', $event);
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/app/app.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { BrowserModule } from '@angular/platform-browser';
3 |
4 | import { GhButtonModule } from '@ctrl/ngx-github-buttons';
5 |
6 | import { ColorAlphaModule } from '../lib/alpha/alpha-picker.component';
7 | import { ColorBlockModule } from '../lib/block/block.component';
8 | import { ColorChromeModule } from '../lib/chrome/chrome.component';
9 | import { ColorCircleModule } from '../lib/circle/circle.component';
10 | import { ColorCompactModule } from '../lib/compact/compact.component';
11 | import { ColorGithubModule } from '../lib/github/github.component';
12 | import { ColorHueModule } from '../lib/hue/hue-picker.component';
13 | import { ColorMaterialModule } from '../lib/material/material.component';
14 | import { ColorPhotoshopModule } from '../lib/photoshop/photoshop.component';
15 | import { ColorSketchModule } from '../lib/sketch/sketch.component';
16 | import { ColorSliderModule } from '../lib/slider/slider.component';
17 | import { ColorSwatchesModule } from '../lib/swatches/swatches.component';
18 | import { ColorTwitterModule } from '../lib/twitter/twitter.component';
19 | import { AppComponent } from './app.component';
20 | import { FooterComponent } from './footer.component';
21 | import { ColorShadeModule } from '../lib/shade/shade-picker.component';
22 |
23 | @NgModule({
24 | declarations: [AppComponent, FooterComponent],
25 | imports: [
26 | BrowserModule,
27 |
28 | GhButtonModule,
29 |
30 | ColorAlphaModule,
31 | ColorBlockModule,
32 | ColorChromeModule,
33 | ColorCircleModule,
34 | ColorCompactModule,
35 | ColorGithubModule,
36 | ColorHueModule,
37 | ColorMaterialModule,
38 | ColorPhotoshopModule,
39 | ColorSketchModule,
40 | ColorSliderModule,
41 | ColorSwatchesModule,
42 | ColorTwitterModule,
43 | ColorShadeModule,
44 | ],
45 | providers: [],
46 | bootstrap: [AppComponent],
47 | })
48 | export class AppModule {}
49 |
--------------------------------------------------------------------------------
/src/app/footer.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, VERSION } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-footer',
5 | template: `
6 |
13 | `,
14 | styles: [
15 | `
16 | .footer {
17 | line-height: 2;
18 | text-align: center;
19 | font-size: 70%;
20 | color: #999;
21 | font-family: var(--font-family-monospace);
22 | }
23 | `,
24 | ],
25 | standalone: false,
26 | })
27 | export class FooterComponent {
28 | version = VERSION.full;
29 | }
30 |
--------------------------------------------------------------------------------
/src/app/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ngx-color",
3 | "private": true,
4 | "description_1": "This is a special package.json file that is not used by package managers.",
5 | "description_2": "It is used to tell the tools and bundlers whether the code under this directory is free of code with non-local side-effect. Any code that does have non-local side-effects can't be well optimized (tree-shaken) and will result in unnecessary increased payload size.",
6 | "description_3": "It should be safe to set this option to 'false' for new applications, but existing code bases could be broken when built with the production config if the application code does contain non-local side-effects that the application depends on.",
7 | "description_4": "To learn more about this file see: https://angular.io/config/app-package-json.",
8 | "sideEffects": false
9 | }
10 |
--------------------------------------------------------------------------------
/src/assets/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scttcper/ngx-color/c2d3742e098bc7ea14bb7e44d80ea88233efe0af/src/assets/.gitkeep
--------------------------------------------------------------------------------
/src/environments/environment.prod.ts:
--------------------------------------------------------------------------------
1 | export const environment = {
2 | production: true,
3 | };
4 |
--------------------------------------------------------------------------------
/src/environments/environment.ts:
--------------------------------------------------------------------------------
1 | // The file contents for the current environment will overwrite these during build.
2 | // The build system defaults to the dev environment which uses `environment.ts`, but if you do
3 | // `ng build --env=prod` then `environment.prod.ts` will be used instead.
4 | // The list of which env maps to which file can be found in `.angular-cli.json`.
5 |
6 | export const environment = {
7 | production: false,
8 | };
9 |
--------------------------------------------------------------------------------
/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Angular Color
7 |
8 |
9 |
13 |
14 |
15 |
16 |
20 |
21 |
22 |
26 |
27 |
28 |
29 |
30 |
31 |
35 |
36 |
37 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
67 |
68 |
69 |
70 |
71 |
72 |
--------------------------------------------------------------------------------
/src/lib/alpha.component.ts:
--------------------------------------------------------------------------------
1 | import { CommonModule } from '@angular/common';
2 | import {
3 | ChangeDetectionStrategy,
4 | Component,
5 | EventEmitter,
6 | Input,
7 | NgModule,
8 | OnChanges,
9 | Output,
10 | } from '@angular/core';
11 |
12 | import { CheckboardModule } from './checkboard.component';
13 | import { CoordinatesModule } from './coordinates.directive';
14 | import { HSLA, RGBA } from './helpers/color.interfaces';
15 |
16 | @Component({
17 | selector: 'color-alpha',
18 | template: `
19 |
20 |
21 |
22 |
23 |
29 |
38 |
39 | `,
40 | styles: [
41 | `
42 | .alpha {
43 | position: absolute;
44 | top: 0;
45 | bottom: 0;
46 | left: 0;
47 | right: 0;
48 | }
49 | .alpha-checkboard {
50 | position: absolute;
51 | top: 0;
52 | bottom: 0;
53 | left: 0;
54 | right: 0;
55 | overflow: hidden;
56 | }
57 | .alpha-gradient {
58 | position: absolute;
59 | top: 0;
60 | bottom: 0;
61 | left: 0;
62 | right: 0;
63 | }
64 | .alpha-container {
65 | position: relative;
66 | height: 100%;
67 | margin: 0 3px;
68 | }
69 | .alpha-pointer {
70 | position: absolute;
71 | }
72 | .alpha-slider {
73 | width: 4px;
74 | border-radius: 1px;
75 | height: 8px;
76 | box-shadow: 0 0 2px rgba(0, 0, 0, 0.6);
77 | background: #fff;
78 | margin-top: 1px;
79 | transform: translateX(-2px);
80 | }
81 | `,
82 | ],
83 | changeDetection: ChangeDetectionStrategy.OnPush,
84 | preserveWhitespaces: false,
85 | standalone: false,
86 | })
87 | export class AlphaComponent implements OnChanges {
88 | @Input() hsl!: HSLA;
89 | @Input() rgb!: RGBA;
90 | @Input() pointer!: Record;
91 | @Input() shadow!: string;
92 | @Input() radius!: number | string;
93 | @Input() direction: 'horizontal' | 'vertical' = 'horizontal';
94 | @Output() onChange = new EventEmitter();
95 | gradient!: Record;
96 | pointerLeft!: number;
97 | pointerTop!: number;
98 |
99 | ngOnChanges() {
100 | if (this.direction === 'vertical') {
101 | this.pointerLeft = 0;
102 | this.pointerTop = this.rgb.a * 100;
103 | this.gradient = {
104 | background: `linear-gradient(to bottom, rgba(${this.rgb.r},${this.rgb.g},${this.rgb.b}, 0) 0%,
105 | rgba(${this.rgb.r},${this.rgb.g},${this.rgb.b}, 1) 100%)`,
106 | };
107 | } else {
108 | this.gradient = {
109 | background: `linear-gradient(to right, rgba(${this.rgb.r},${this.rgb.g},${this.rgb.b}, 0) 0%,
110 | rgba(${this.rgb.r},${this.rgb.g},${this.rgb.b}, 1) 100%)`,
111 | };
112 | this.pointerLeft = this.rgb.a * 100;
113 | }
114 | }
115 | handleChange({ top, left, containerHeight, containerWidth, $event }): void {
116 | let data: any;
117 | if (this.direction === 'vertical') {
118 | let a: number;
119 | if (top < 0) {
120 | a = 0;
121 | } else if (top > containerHeight) {
122 | a = 1;
123 | } else {
124 | a = Math.round((top * 100) / containerHeight) / 100;
125 | }
126 |
127 | if (this.hsl.a !== a) {
128 | data = {
129 | h: this.hsl.h,
130 | s: this.hsl.s,
131 | l: this.hsl.l,
132 | a,
133 | source: 'rgb',
134 | };
135 | }
136 | } else {
137 | let a: number;
138 | if (left < 0) {
139 | a = 0;
140 | } else if (left > containerWidth) {
141 | a = 1;
142 | } else {
143 | a = Math.round((left * 100) / containerWidth) / 100;
144 | }
145 |
146 | if (this.hsl.a !== a) {
147 | data = {
148 | h: this.hsl.h,
149 | s: this.hsl.s,
150 | l: this.hsl.l,
151 | a,
152 | source: 'rgb',
153 | };
154 | }
155 | }
156 |
157 | if (!data) {
158 | return;
159 | }
160 |
161 | this.onChange.emit({ data, $event });
162 | }
163 | }
164 |
165 | @NgModule({
166 | declarations: [AlphaComponent],
167 | exports: [AlphaComponent],
168 | imports: [CommonModule, CheckboardModule, CoordinatesModule],
169 | })
170 | export class AlphaModule {}
171 |
--------------------------------------------------------------------------------
/src/lib/alpha/alpha-picker.component.ts:
--------------------------------------------------------------------------------
1 | import { CommonModule } from '@angular/common';
2 | import {
3 | ChangeDetectionStrategy,
4 | Component,
5 | forwardRef,
6 | Input,
7 | NgModule,
8 | OnChanges,
9 | } from '@angular/core';
10 |
11 | import { AlphaModule, CheckboardModule, ColorWrap, toState } from 'ngx-color';
12 | import { NG_VALUE_ACCESSOR } from '@angular/forms';
13 |
14 | @Component({
15 | selector: 'color-alpha-picker',
16 | template: `
17 |
18 |
25 |
26 | `,
27 | styles: [
28 | `
29 | .alpha-picker {
30 | position: relative;
31 | }
32 | .color-alpha {
33 | radius: 2px;
34 | }
35 | `,
36 | ],
37 | changeDetection: ChangeDetectionStrategy.OnPush,
38 | preserveWhitespaces: false,
39 | providers: [
40 | {
41 | provide: NG_VALUE_ACCESSOR,
42 | useExisting: forwardRef(() => AlphaPickerComponent),
43 | multi: true,
44 | },
45 | {
46 | provide: ColorWrap,
47 | useExisting: forwardRef(() => AlphaPickerComponent),
48 | },
49 | ],
50 | standalone: false,
51 | })
52 | export class AlphaPickerComponent extends ColorWrap implements OnChanges {
53 | /** Pixel value for picker width */
54 | @Input() width: string | number = 316;
55 | /** Pixel value for picker height */
56 | @Input() height: string | number = 16;
57 | @Input() direction: 'horizontal' | 'vertical' = 'horizontal';
58 | pointer: { [key: string]: string } = {
59 | width: '18px',
60 | height: '18px',
61 | borderRadius: '50%',
62 | transform: 'translate(-9px, -2px)',
63 | boxShadow: '0 1px 4px 0 rgba(0, 0, 0, 0.37)',
64 | };
65 |
66 | constructor() {
67 | super();
68 | }
69 | ngOnChanges() {
70 | if (this.direction === 'vertical') {
71 | this.pointer.transform = 'translate(-3px, -9px)';
72 | }
73 | this.setState(toState(this.color, this.oldHue));
74 | }
75 | handlePickerChange({ data, $event }) {
76 | this.handleChange(data, $event);
77 | }
78 | }
79 |
80 | @NgModule({
81 | declarations: [AlphaPickerComponent],
82 | exports: [AlphaPickerComponent],
83 | imports: [CommonModule, AlphaModule, CheckboardModule],
84 | })
85 | export class ColorAlphaModule {}
86 |
--------------------------------------------------------------------------------
/src/lib/alpha/alpha.spec.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 | import { TestBed, waitForAsync } from '@angular/core/testing';
3 | import { By } from '@angular/platform-browser';
4 |
5 | import { ColorAlphaModule } from './alpha-picker.component';
6 |
7 | export const red = {
8 | hsl: { a: 1, h: 0, l: 0.5, s: 1 },
9 | hex: '#ff0000',
10 | rgb: { r: 255, g: 0, b: 0, a: 1 },
11 | hsv: { h: 0, s: 1, v: 1, a: 1 },
12 | };
13 |
14 | describe('AlphaComponent', () => {
15 | beforeEach(waitForAsync(() => {
16 | TestBed.configureTestingModule({
17 | declarations: [AlphaTestApp],
18 | imports: [ColorAlphaModule],
19 | });
20 |
21 | TestBed.compileComponents();
22 | }));
23 | it(`should apply className to root element`, () => {
24 | const fixture = TestBed.createComponent(AlphaTestApp);
25 | fixture.detectChanges();
26 | const compiled = fixture.nativeElement;
27 | expect(compiled.querySelector('.alpha-picker').className).toContain('classy');
28 | });
29 | it(`should draw vertical`, () => {
30 | const fixture = TestBed.createComponent(AlphaTestApp);
31 | const testComponent = fixture.componentInstance;
32 | fixture.detectChanges();
33 | testComponent.direction = 'vertical';
34 | fixture.detectChanges();
35 | const div = fixture.debugElement.query(By.css('.alpha-container'));
36 | expect(div.nativeElement.classList.contains('color-alpha-vertical')).toBe(true);
37 | });
38 | // it(`should change alpha on mousedown`, () => {
39 | // const fixture = TestBed.createComponent(AlphaPickerComponent);
40 | // const component = fixture.componentInstance;
41 | // component.width = 20;
42 | // component.height = 200;
43 | // component.color = red.hsl;
44 | // fixture.detectChanges();
45 | // const $event = new MouseEvent('mousedown', {
46 | // bubbles: true,
47 | // cancelable: true,
48 | // view: window,
49 | // clientX: 0,
50 | // clientY: 0,
51 | // });
52 | // fixture.detectChanges();
53 | // expect(component.hsl.a).toEqual(0);
54 | // });
55 | });
56 |
57 | @Component({
58 | selector: 'test-app',
59 | template: ``,
63 | standalone: false,
64 | })
65 | class AlphaTestApp {
66 | className = 'classy';
67 | direction = 'horizontal';
68 | }
69 |
--------------------------------------------------------------------------------
/src/lib/alpha/ng-package.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "../../../node_modules/ng-packagr/ng-package.schema.json",
3 | "lib": {
4 | "entryFile": "public_api.ts"
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/src/lib/alpha/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ngx-color/alpha",
3 | "author": "scttcper",
4 | "repository": {
5 | "type": "git",
6 | "url": "git+https://github.com/scttcper/ngx-color.git"
7 | },
8 | "license": "MIT"
9 | }
10 |
--------------------------------------------------------------------------------
/src/lib/alpha/public_api.ts:
--------------------------------------------------------------------------------
1 | export { AlphaPickerComponent, ColorAlphaModule } from './alpha-picker.component';
2 |
--------------------------------------------------------------------------------
/src/lib/block/block-swatches.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, EventEmitter, Input, Output } from '@angular/core';
2 | @Component({
3 | selector: 'color-block-swatches',
4 | template: `
5 |
6 | @for (c of colors; track c) {
7 |
14 | }
15 |
16 |
17 | `,
18 | styles: [
19 | `
20 | .block-swatches {
21 | margin-right: -10px;
22 | }
23 | .clear {
24 | clear: both;
25 | }
26 | `,
27 | ],
28 | standalone: false,
29 | })
30 | export class BlockSwatchesComponent {
31 | @Input() colors!: string[];
32 | @Output() onClick = new EventEmitter();
33 | @Output() onSwatchHover = new EventEmitter();
34 |
35 | swatchStyle = {
36 | width: '22px',
37 | height: '22px',
38 | float: 'left',
39 | marginRight: '10px',
40 | marginBottom: '10px',
41 | borderRadius: '4px',
42 | };
43 |
44 | handleClick({ hex, $event }) {
45 | this.onClick.emit({ hex, $event });
46 | }
47 | focusStyle(c) {
48 | return {
49 | boxShadow: `${c} 0 0 4px`,
50 | };
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/lib/block/block.component.ts:
--------------------------------------------------------------------------------
1 | import { CommonModule } from '@angular/common';
2 | import { ChangeDetectionStrategy, Component, forwardRef, Input, NgModule } from '@angular/core';
3 |
4 | import {
5 | CheckboardModule,
6 | ColorWrap,
7 | EditableInputModule,
8 | getContrastingColor,
9 | isValidHex,
10 | SwatchModule,
11 | } from 'ngx-color';
12 | import { BlockSwatchesComponent } from './block-swatches.component';
13 | import { NG_VALUE_ACCESSOR } from '@angular/forms';
14 |
15 | @Component({
16 | selector: 'color-block',
17 | template: `
18 |
19 | @if (triangle !== 'hide') {
20 |
24 | }
25 |
26 |
27 | @if (hex === 'transparent') {
28 |
29 | }
30 |
31 | {{ hex }}
32 |
33 |
34 |
35 |
36 |
41 |
46 |
47 |
48 | `,
49 | styles: [
50 | `
51 | .block-card {
52 | background: #fff;
53 | border-radius: 6px;
54 | box-shadow: 0 1px rgba(0, 0, 0, 0.1);
55 | position: relative;
56 | }
57 | .block-head {
58 | align-items: center;
59 | border-radius: 6px 6px 0 0;
60 | display: flex;
61 | height: 110px;
62 | justify-content: center;
63 | position: relative;
64 | }
65 | .block-body {
66 | padding: 10px;
67 | }
68 | .block-label {
69 | font-size: 18px;
70 | position: relative;
71 | }
72 | .block-triangle {
73 | border-style: solid;
74 | border-width: 0 10px 10px 10px;
75 | height: 0;
76 | left: 50%;
77 | margin-left: -10px;
78 | position: absolute;
79 | top: -10px;
80 | width: 0;
81 | }
82 | `,
83 | ],
84 | preserveWhitespaces: false,
85 | changeDetection: ChangeDetectionStrategy.OnPush,
86 | providers: [
87 | {
88 | provide: NG_VALUE_ACCESSOR,
89 | useExisting: forwardRef(() => BlockComponent),
90 | multi: true,
91 | },
92 | {
93 | provide: ColorWrap,
94 | useExisting: forwardRef(() => BlockComponent),
95 | },
96 | ],
97 | standalone: false,
98 | })
99 | export class BlockComponent extends ColorWrap {
100 | /** Pixel value for picker width */
101 | @Input() width: string | number = 170;
102 | /** Color squares to display */
103 | @Input() colors = [
104 | '#D9E3F0',
105 | '#F47373',
106 | '#697689',
107 | '#37D67A',
108 | '#2CCCE4',
109 | '#555555',
110 | '#dce775',
111 | '#ff8a65',
112 | '#ba68c8',
113 | ];
114 | @Input() triangle: 'top' | 'hide' = 'top';
115 | input: { [key: string]: string } = {
116 | width: '100%',
117 | fontSize: '12px',
118 | color: '#666',
119 | border: '0px',
120 | outline: 'none',
121 | height: '22px',
122 | boxShadow: 'inset 0 0 0 1px #ddd',
123 | borderRadius: '4px',
124 | padding: '0 7px',
125 | boxSizing: 'border-box',
126 | };
127 | wrap: { [key: string]: string } = {
128 | position: 'relative',
129 | width: '100%',
130 | };
131 | disableAlpha = true;
132 |
133 | constructor() {
134 | super();
135 | }
136 |
137 | handleValueChange({ data, $event }) {
138 | this.handleBlockChange({ hex: data, $event });
139 | }
140 | getContrastingColor(hex) {
141 | return getContrastingColor(hex);
142 | }
143 | handleBlockChange({ hex, $event }) {
144 | if (isValidHex(hex)) {
145 | // this.hex = hex;
146 | this.handleChange(
147 | {
148 | hex,
149 | source: 'hex',
150 | },
151 | $event,
152 | );
153 | }
154 | }
155 | }
156 |
157 | @NgModule({
158 | declarations: [BlockComponent, BlockSwatchesComponent],
159 | exports: [BlockComponent, BlockSwatchesComponent],
160 | imports: [CommonModule, CheckboardModule, SwatchModule, EditableInputModule],
161 | })
162 | export class ColorBlockModule {}
163 |
--------------------------------------------------------------------------------
/src/lib/block/block.spec.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 | import { TestBed, waitForAsync } from '@angular/core/testing';
3 | import { By } from '@angular/platform-browser';
4 |
5 | import { BlockComponent, ColorBlockModule } from './block.component';
6 |
7 | describe('BlockComponent', () => {
8 | beforeEach(waitForAsync(() => {
9 | TestBed.configureTestingModule({
10 | declarations: [BlockTestApp],
11 | imports: [ColorBlockModule],
12 | });
13 |
14 | TestBed.compileComponents();
15 | }));
16 | it(`should apply className to root element`, () => {
17 | const fixture = TestBed.createComponent(BlockTestApp);
18 | fixture.detectChanges();
19 | const div = fixture.debugElement.query(By.css('.block-card'));
20 | expect(div.nativeElement.classList.contains('classy')).toBe(true);
21 | });
22 | it(`should change color on swatch click`, () => {
23 | const fixture = TestBed.createComponent(BlockComponent);
24 | const component = fixture.componentInstance;
25 | component.colors = ['#000000'];
26 | fixture.detectChanges();
27 | const div = fixture.debugElement.query(By.css('.swatch'));
28 | div.triggerEventHandler('click', {});
29 | fixture.detectChanges();
30 | expect(component.hex).toEqual('#000000');
31 | });
32 | it(`should change color on input`, () => {
33 | const fixture = TestBed.createComponent(BlockComponent);
34 | const component = fixture.componentInstance;
35 | fixture.detectChanges();
36 | const inputElement = fixture.debugElement.query(By.css('input'));
37 | inputElement.nativeElement.value = '#FFFFFF';
38 | inputElement.nativeElement.dispatchEvent(new Event('keydown'));
39 | inputElement.nativeElement.dispatchEvent(new Event('keyup'));
40 | fixture.detectChanges();
41 | expect(component.hex).toEqual('#ffffff');
42 | });
43 | });
44 |
45 | @Component({
46 | selector: 'test-app',
47 | template: ``,
48 | standalone: false,
49 | })
50 | class BlockTestApp {
51 | className = 'classy';
52 | }
53 |
--------------------------------------------------------------------------------
/src/lib/block/ng-package.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "../../../node_modules/ng-packagr/ng-package.schema.json",
3 | "lib": {
4 | "entryFile": "public_api.ts"
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/src/lib/block/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ngx-color/block",
3 | "author": "scttcper",
4 | "repository": {
5 | "type": "git",
6 | "url": "git+https://github.com/scttcper/ngx-color.git"
7 | },
8 | "license": "MIT"
9 | }
10 |
--------------------------------------------------------------------------------
/src/lib/block/public_api.ts:
--------------------------------------------------------------------------------
1 | export { BlockSwatchesComponent } from './block-swatches.component';
2 | export { BlockComponent, ColorBlockModule } from './block.component';
3 |
--------------------------------------------------------------------------------
/src/lib/checkboard.component.ts:
--------------------------------------------------------------------------------
1 | import { CommonModule } from '@angular/common';
2 | import { ChangeDetectionStrategy, Component, Input, NgModule, OnInit } from '@angular/core';
3 |
4 | import { getCheckerboard } from './helpers/checkboard';
5 |
6 | @Component({
7 | selector: 'color-checkboard',
8 | template: ``,
9 | styles: [
10 | `
11 | .grid {
12 | top: 0px;
13 | right: 0px;
14 | bottom: 0px;
15 | left: 0px;
16 | position: absolute;
17 | }
18 | `,
19 | ],
20 | preserveWhitespaces: false,
21 | changeDetection: ChangeDetectionStrategy.OnPush,
22 | standalone: false,
23 | })
24 | export class CheckboardComponent implements OnInit {
25 | @Input() white = 'transparent';
26 | @Input() size = 8;
27 | @Input() grey = 'rgba(0,0,0,.08)';
28 | @Input() boxShadow!: string;
29 | @Input() borderRadius!: string;
30 | gridStyles!: Record;
31 |
32 | ngOnInit() {
33 | const background = getCheckerboard(this.white, this.grey, this.size);
34 | this.gridStyles = {
35 | borderRadius: this.borderRadius,
36 | boxShadow: this.boxShadow,
37 | background: `url(${background}) center left`,
38 | };
39 | }
40 | }
41 |
42 | @NgModule({
43 | declarations: [CheckboardComponent],
44 | exports: [CheckboardComponent],
45 | imports: [CommonModule],
46 | })
47 | export class CheckboardModule {}
48 |
--------------------------------------------------------------------------------
/src/lib/chrome/chrome-fields.component.ts:
--------------------------------------------------------------------------------
1 | import {
2 | ChangeDetectionStrategy,
3 | Component,
4 | EventEmitter,
5 | Input,
6 | OnInit,
7 | Output,
8 | } from '@angular/core';
9 |
10 | import { isValidHex, HSLA, RGBA } from 'ngx-color';
11 | import { TinyColor } from '@ctrl/tinycolor';
12 |
13 | @Component({
14 | selector: 'color-chrome-fields',
15 | template: `
16 |
17 |
18 | @if (view === 'hex') {
19 |
20 |
26 |
27 | }
28 | @if (view === 'rgb') {
29 |
30 |
36 |
37 |
38 |
44 |
45 |
46 |
52 |
53 |
54 | @if (!disableAlpha) {
55 |
62 | }
63 |
64 | }
65 | @if (view === 'hsl') {
66 |
67 |
73 |
74 |
75 |
81 |
82 |
83 |
89 |
90 |
91 | @if (!disableAlpha) {
92 |
99 | }
100 |
101 | }
102 |
103 |
104 |
105 |
106 |
118 |
119 |
120 |
121 | `,
122 | styles: [
123 | `
124 | .chrome-wrap {
125 | padding-top: 16px;
126 | display: flex;
127 | }
128 | .chrome-fields {
129 | flex: 1;
130 | display: flex;
131 | margin-left: -6px;
132 | }
133 | .chrome-field {
134 | padding-left: 6px;
135 | width: 100%;
136 | }
137 | .chrome-toggle {
138 | width: 32px;
139 | text-align: right;
140 | position: relative;
141 | }
142 | .chrome-icon {
143 | margin-right: -4px;
144 | margin-top: 12px;
145 | cursor: pointer;
146 | position: relative;
147 | }
148 | .chrome-toggle-svg {
149 | width: 24px;
150 | height: 24px;
151 | border: 1px transparent solid;
152 | border-radius: 5px;
153 | }
154 | .chrome-toggle-svg:hover {
155 | background: #eee;
156 | }
157 | `,
158 | ],
159 | changeDetection: ChangeDetectionStrategy.OnPush,
160 | preserveWhitespaces: false,
161 | standalone: false,
162 | })
163 | export class ChromeFieldsComponent implements OnInit {
164 | @Input() disableAlpha!: boolean;
165 | @Input() hsl!: HSLA;
166 | @Input() rgb!: RGBA;
167 | @Input() hex!: string;
168 | @Output() onChange = new EventEmitter();
169 | view = '';
170 | input: Record = {
171 | fontSize: '11px',
172 | color: '#333',
173 | width: '100%',
174 | borderRadius: '2px',
175 | border: 'none',
176 | boxShadow: 'inset 0 0 0 1px #dadada',
177 | height: '21px',
178 | 'text-align': 'center',
179 | };
180 | label: Record = {
181 | 'text-transform': 'uppercase',
182 | fontSize: '11px',
183 | 'line-height': '11px',
184 | color: '#969696',
185 | 'text-align': 'center',
186 | display: 'block',
187 | marginTop: '12px',
188 | };
189 |
190 | ngOnInit() {
191 | if (this.hsl.a === 1 && this.view !== 'hex') {
192 | this.view = 'hex';
193 | } else if (this.view !== 'rgb' && this.view !== 'hsl') {
194 | this.view = 'rgb';
195 | }
196 | }
197 | toggleViews() {
198 | if (this.view === 'hex') {
199 | this.view = 'rgb';
200 | } else if (this.view === 'rgb') {
201 | this.view = 'hsl';
202 | } else if (this.view === 'hsl') {
203 | if (this.hsl.a === 1) {
204 | this.view = 'hex';
205 | } else {
206 | this.view = 'rgb';
207 | }
208 | }
209 | }
210 | round(value) {
211 | return Math.round(value);
212 | }
213 | handleChange({ data, $event }) {
214 | if (data.hex) {
215 | if (isValidHex(data.hex)) {
216 | const color = new TinyColor(data.hex);
217 | this.onChange.emit({
218 | data: {
219 | hex: this.disableAlpha ? color.toHex() : color.toHex8(),
220 | source: 'hex',
221 | },
222 | $event,
223 | });
224 | }
225 | } else if (data.r || data.g || data.b) {
226 | this.onChange.emit({
227 | data: {
228 | r: data.r || this.rgb.r,
229 | g: data.g || this.rgb.g,
230 | b: data.b || this.rgb.b,
231 | source: 'rgb',
232 | },
233 | $event,
234 | });
235 | } else if (data.a) {
236 | if (data.a < 0) {
237 | data.a = 0;
238 | } else if (data.a > 1) {
239 | data.a = 1;
240 | }
241 |
242 | if (this.disableAlpha) {
243 | data.a = 1;
244 | }
245 |
246 | this.onChange.emit({
247 | data: {
248 | h: this.hsl.h,
249 | s: this.hsl.s,
250 | l: this.hsl.l,
251 | a: Math.round(data.a * 100) / 100,
252 | source: 'rgb',
253 | },
254 | $event,
255 | });
256 | } else if (data.h || data.s || data.l) {
257 | const s = data.s && data.s.replace('%', '');
258 | const l = data.l && data.l.replace('%', '');
259 | this.onChange.emit({
260 | data: {
261 | h: data.h || this.hsl.h,
262 | s: Number(s || this.hsl.s),
263 | l: Number(l || this.hsl.l),
264 | source: 'hsl',
265 | },
266 | $event,
267 | });
268 | }
269 | }
270 | }
271 |
--------------------------------------------------------------------------------
/src/lib/chrome/chrome.component.ts:
--------------------------------------------------------------------------------
1 | import { CommonModule } from '@angular/common';
2 | import { ChangeDetectionStrategy, Component, forwardRef, Input, NgModule } from '@angular/core';
3 |
4 | import {
5 | AlphaModule,
6 | CheckboardModule,
7 | ColorWrap,
8 | EditableInputModule,
9 | HueModule,
10 | SaturationModule,
11 | } from 'ngx-color';
12 | import { ChromeFieldsComponent } from './chrome-fields.component';
13 | import { NG_VALUE_ACCESSOR } from '@angular/forms';
14 |
15 | @Component({
16 | selector: 'color-chrome',
17 | template: `
18 |
19 |
20 |
26 |
27 |
28 |
29 |
35 |
36 |
37 |
43 |
44 | @if (!disableAlpha) {
45 |
46 |
53 |
54 | }
55 |
56 |
57 |
64 |
65 |
66 | `,
67 | styles: [
68 | `
69 | .chrome-picker {
70 | background: #fff;
71 | border-radius: 2px;
72 | box-shadow:
73 | 0 0 2px rgba(0, 0, 0, 0.3),
74 | 0 4px 8px rgba(0, 0, 0, 0.3);
75 | box-sizing: initial;
76 | width: 225px;
77 | font-family: 'Menlo';
78 | }
79 | .chrome-controls {
80 | display: flex;
81 | }
82 | .chrome-color {
83 | width: 42px;
84 | }
85 | .chrome-body {
86 | padding: 14px 14px 12px;
87 | }
88 | .chrome-active {
89 | position: absolute;
90 | top: 0;
91 | bottom: 0;
92 | left: 0;
93 | right: 0;
94 | border-radius: 20px;
95 | box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.1);
96 | z-index: 2;
97 | }
98 | .chrome-swatch {
99 | width: 28px;
100 | height: 28px;
101 | border-radius: 15px;
102 | position: relative;
103 | overflow: hidden;
104 | }
105 | .saturation {
106 | width: 100%;
107 | padding-bottom: 55%;
108 | position: relative;
109 | border-radius: 2px 2px 0 0;
110 | overflow: hidden;
111 | }
112 | .chrome-toggles {
113 | flex: 1;
114 | }
115 | .chrome-hue {
116 | height: 10px;
117 | position: relative;
118 | margin-bottom: 8px;
119 | }
120 | .chrome-alpha {
121 | height: 10px;
122 | position: relative;
123 | }
124 | `,
125 | ],
126 | changeDetection: ChangeDetectionStrategy.OnPush,
127 | preserveWhitespaces: false,
128 | providers: [
129 | {
130 | provide: NG_VALUE_ACCESSOR,
131 | useExisting: forwardRef(() => ChromeComponent),
132 | multi: true,
133 | },
134 | {
135 | provide: ColorWrap,
136 | useExisting: forwardRef(() => ChromeComponent),
137 | },
138 | ],
139 | standalone: false,
140 | })
141 | export class ChromeComponent extends ColorWrap {
142 | /** Remove alpha slider and options from picker */
143 | @Input() disableAlpha = false;
144 | circle: Record = {
145 | width: '12px',
146 | height: '12px',
147 | borderRadius: '6px',
148 | boxShadow: 'rgb(255, 255, 255) 0px 0px 0px 1px inset',
149 | transform: 'translate(-6px, -8px)',
150 | };
151 | pointer: Record = {
152 | width: '12px',
153 | height: '12px',
154 | borderRadius: '6px',
155 | transform: 'translate(-6px, -2px)',
156 | backgroundColor: 'rgb(248, 248, 248)',
157 | boxShadow: '0 1px 4px 0 rgba(0, 0, 0, 0.37)',
158 | };
159 | activeBackground!: string;
160 |
161 | constructor() {
162 | super();
163 | }
164 |
165 | afterValidChange() {
166 | const alpha = this.disableAlpha ? 1 : this.rgb.a;
167 | this.activeBackground = `rgba(${this.rgb.r}, ${this.rgb.g}, ${this.rgb.b}, ${alpha})`;
168 | }
169 | handleValueChange({ data, $event }) {
170 | this.handleChange(data, $event);
171 | }
172 | }
173 |
174 | @NgModule({
175 | declarations: [ChromeComponent, ChromeFieldsComponent],
176 | exports: [ChromeComponent, ChromeFieldsComponent],
177 | imports: [
178 | CommonModule,
179 | AlphaModule,
180 | CheckboardModule,
181 | EditableInputModule,
182 | HueModule,
183 | SaturationModule,
184 | ],
185 | })
186 | export class ColorChromeModule {}
187 |
--------------------------------------------------------------------------------
/src/lib/chrome/chrome.spec.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 | import { TestBed, waitForAsync } from '@angular/core/testing';
3 | import { By } from '@angular/platform-browser';
4 |
5 | import { ColorChromeModule } from './chrome.component';
6 |
7 | describe('BlockComponent', () => {
8 | beforeEach(waitForAsync(() => {
9 | TestBed.configureTestingModule({
10 | declarations: [ChromeTestApp],
11 | imports: [ColorChromeModule],
12 | }).compileComponents();
13 | }));
14 | it(`should apply className to root element`, () => {
15 | const fixture = TestBed.createComponent(ChromeTestApp);
16 | fixture.detectChanges();
17 | const divDebugElement = fixture.debugElement.query(By.css('.chrome-picker'));
18 | expect(divDebugElement.nativeElement.classList.contains('classy')).toBe(true);
19 | });
20 | });
21 |
22 | @Component({
23 | selector: 'test-app',
24 | template: ``,
25 | standalone: false,
26 | })
27 | class ChromeTestApp {
28 | className = 'classy';
29 | }
30 |
--------------------------------------------------------------------------------
/src/lib/chrome/ng-package.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "../../../node_modules/ng-packagr/ng-package.schema.json",
3 | "lib": {
4 | "entryFile": "public_api.ts"
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/src/lib/chrome/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ngx-color/chrome",
3 | "author": "scttcper",
4 | "repository": {
5 | "type": "git",
6 | "url": "git+https://github.com/scttcper/ngx-color.git"
7 | },
8 | "license": "MIT"
9 | }
10 |
--------------------------------------------------------------------------------
/src/lib/chrome/public_api.ts:
--------------------------------------------------------------------------------
1 | export { ChromeFieldsComponent } from './chrome-fields.component';
2 | export { ChromeComponent, ColorChromeModule } from './chrome.component';
3 |
--------------------------------------------------------------------------------
/src/lib/circle/circle-swatch.component.ts:
--------------------------------------------------------------------------------
1 | import {
2 | ChangeDetectionStrategy,
3 | Component,
4 | EventEmitter,
5 | Input,
6 | OnChanges,
7 | Output,
8 | } from '@angular/core';
9 |
10 | @Component({
11 | selector: 'color-circle-swatch',
12 | template: `
13 |
31 | `,
32 | styles: [
33 | `
34 | .circle-swatch {
35 | transform: scale(1);
36 | transition: transform 100ms ease;
37 | }
38 | .circle-swatch:hover {
39 | transform: scale(1.2);
40 | }
41 | `,
42 | ],
43 | changeDetection: ChangeDetectionStrategy.OnPush,
44 | preserveWhitespaces: false,
45 | standalone: false,
46 | })
47 | export class CircleSwatchComponent implements OnChanges {
48 | @Input() color!: string;
49 | @Input() circleSize = 28;
50 | @Input() circleSpacing = 14;
51 | @Input() focus = false;
52 | @Output() onClick = new EventEmitter();
53 | @Output() onSwatchHover = new EventEmitter();
54 | focusStyle: Record = {};
55 | swatchStyle: Record = {
56 | borderRadius: '50%',
57 | background: 'transparent',
58 | transition: '100ms box-shadow ease 0s',
59 | };
60 |
61 | ngOnChanges() {
62 | this.swatchStyle.boxShadow = `inset 0 0 0 ${this.circleSize / 2}px ${this.color}`;
63 | this.focusStyle.boxShadow = `inset 0 0 0 ${this.circleSize / 2}px ${this.color}, 0 0 5px ${this.color}`;
64 | if (this.focus) {
65 | this.focusStyle.boxShadow = `inset 0 0 0 3px ${this.color}, 0 0 5px ${this.color}`;
66 | }
67 | }
68 | handleClick({ hex, $event }) {
69 | this.onClick.emit({ hex, $event });
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/src/lib/circle/circle.component.ts:
--------------------------------------------------------------------------------
1 | import { CommonModule } from '@angular/common';
2 | import { ChangeDetectionStrategy, Component, forwardRef, Input, NgModule } from '@angular/core';
3 | import {
4 | amber,
5 | blue,
6 | blueGrey,
7 | brown,
8 | cyan,
9 | deepOrange,
10 | deepPurple,
11 | green,
12 | indigo,
13 | lightBlue,
14 | lightGreen,
15 | lime,
16 | orange,
17 | pink,
18 | purple,
19 | red,
20 | teal,
21 | yellow,
22 | } from 'material-colors';
23 | import { TinyColor } from '@ctrl/tinycolor';
24 |
25 | import { ColorWrap, isValidHex, SwatchModule } from 'ngx-color';
26 | import { CircleSwatchComponent } from './circle-swatch.component';
27 | import { NG_VALUE_ACCESSOR } from '@angular/forms';
28 |
29 | @Component({
30 | selector: 'color-circle',
31 | template: `
32 |
38 | @for (color of colors; track color) {
39 |
47 | }
48 |
49 | `,
50 | styles: [
51 | `
52 | .circle-picker {
53 | display: flex;
54 | flex-wrap: wrap;
55 | }
56 | `,
57 | ],
58 | changeDetection: ChangeDetectionStrategy.OnPush,
59 | preserveWhitespaces: false,
60 | providers: [
61 | {
62 | provide: NG_VALUE_ACCESSOR,
63 | useExisting: forwardRef(() => CircleComponent),
64 | multi: true,
65 | },
66 | {
67 | provide: ColorWrap,
68 | useExisting: forwardRef(() => CircleComponent),
69 | },
70 | ],
71 | standalone: false,
72 | })
73 | export class CircleComponent extends ColorWrap {
74 | /** Pixel value for picker width */
75 | @Input() width: string | number = 252;
76 | /** Color squares to display */
77 | @Input()
78 | colors: string[] = [
79 | red['500'],
80 | pink['500'],
81 | purple['500'],
82 | deepPurple['500'],
83 | indigo['500'],
84 | blue['500'],
85 | lightBlue['500'],
86 | cyan['500'],
87 | teal['500'],
88 | green['500'],
89 | lightGreen['500'],
90 | lime['500'],
91 | yellow['500'],
92 | amber['500'],
93 | orange['500'],
94 | deepOrange['500'],
95 | brown['500'],
96 | blueGrey['500'],
97 | ];
98 | /** Value for circle size */
99 | @Input() circleSize = 28;
100 | /** Value for spacing between circles */
101 | @Input() circleSpacing = 14;
102 |
103 | constructor() {
104 | super();
105 | }
106 | isActive(color: string) {
107 | return new TinyColor(this.hex).equals(color);
108 | }
109 | handleBlockChange({ hex, $event }: { hex: string; $event: Event }) {
110 | if (isValidHex(hex)) {
111 | this.handleChange({ hex, source: 'hex' }, $event);
112 | }
113 | }
114 | handleValueChange({ data, $event }) {
115 | this.handleChange(data, $event);
116 | }
117 | }
118 |
119 | @NgModule({
120 | declarations: [CircleComponent, CircleSwatchComponent],
121 | exports: [CircleComponent, CircleSwatchComponent],
122 | imports: [CommonModule, SwatchModule],
123 | })
124 | export class ColorCircleModule {}
125 |
--------------------------------------------------------------------------------
/src/lib/circle/circle.spec.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 | import { TestBed, waitForAsync } from '@angular/core/testing';
3 | import { By } from '@angular/platform-browser';
4 |
5 | import { ColorCircleModule } from './circle.component';
6 |
7 | describe('BlockComponent', () => {
8 | beforeEach(waitForAsync(() => {
9 | TestBed.configureTestingModule({
10 | declarations: [CircleTestApp],
11 | imports: [ColorCircleModule],
12 | }).compileComponents();
13 | }));
14 | it(`should apply className to root element`, () => {
15 | const fixture = TestBed.createComponent(CircleTestApp);
16 | fixture.detectChanges();
17 | const divDebugElement = fixture.debugElement.query(By.css('.circle-picker'));
18 | expect(divDebugElement.nativeElement.classList.contains('classy')).toBe(true);
19 | });
20 | });
21 |
22 | @Component({
23 | selector: 'test-app',
24 | template: ``,
25 | standalone: false,
26 | })
27 | class CircleTestApp {
28 | className = 'classy';
29 | }
30 |
--------------------------------------------------------------------------------
/src/lib/circle/ng-package.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "../../../node_modules/ng-packagr/ng-package.schema.json",
3 | "lib": {
4 | "entryFile": "public_api.ts"
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/src/lib/circle/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ngx-color/circle",
3 | "author": "scttcper",
4 | "repository": {
5 | "type": "git",
6 | "url": "git+https://github.com/scttcper/ngx-color.git"
7 | },
8 | "license": "MIT",
9 | "dependencies": {
10 | "material-colors": "^1.2.6"
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/lib/circle/public_api.ts:
--------------------------------------------------------------------------------
1 | export { CircleSwatchComponent } from './circle-swatch.component';
2 | export { CircleComponent, ColorCircleModule } from './circle.component';
3 |
--------------------------------------------------------------------------------
/src/lib/color-wrap.component.ts:
--------------------------------------------------------------------------------
1 | import { CommonModule } from '@angular/common';
2 | import {
3 | Component,
4 | EventEmitter,
5 | forwardRef,
6 | Input,
7 | isDevMode,
8 | NgModule,
9 | OnChanges,
10 | OnDestroy,
11 | OnInit,
12 | Output,
13 | } from '@angular/core';
14 |
15 | import { Subscription } from 'rxjs';
16 | import { debounceTime, tap } from 'rxjs/operators';
17 |
18 | import { simpleCheckForValidColor, toState } from './helpers/color';
19 | import { Color, HSLA, HSVA, RGBA } from './helpers/color.interfaces';
20 | import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
21 |
22 | export interface ColorEvent {
23 | $event: Event;
24 | color: Color;
25 | }
26 |
27 | export enum ColorMode {
28 | HEX = 'hex',
29 | HSL = 'hsl',
30 | HSV = 'hsv',
31 | RGB = 'rgb',
32 | }
33 |
34 | @Component({
35 | // create seletor base for test override property
36 | selector: 'color-wrap',
37 | template: ``,
38 | providers: [
39 | {
40 | provide: NG_VALUE_ACCESSOR,
41 | useExisting: forwardRef(() => ColorWrap),
42 | multi: true,
43 | },
44 | ],
45 | standalone: false,
46 | })
47 | export class ColorWrap implements OnInit, OnChanges, OnDestroy, ControlValueAccessor {
48 | @Input() className?: string;
49 |
50 | /**
51 | * Descriptors the return color format if the component is used with two-way binding
52 | */
53 | @Input() mode: ColorMode = ColorMode.HEX;
54 |
55 | @Input() color: HSLA | HSVA | RGBA | string = {
56 | h: 250,
57 | s: 0.5,
58 | l: 0.2,
59 | a: 1,
60 | };
61 | @Output() colorChange = new EventEmitter();
62 | @Output() onChange = new EventEmitter();
63 | @Output() onChangeComplete = new EventEmitter();
64 | @Output() onSwatchHover = new EventEmitter();
65 | oldHue!: number;
66 | hsl!: HSLA;
67 | hsv!: HSVA;
68 | rgb!: RGBA;
69 | hex!: string;
70 | source!: string;
71 | currentColor!: string;
72 | changes?: Subscription;
73 | disableAlpha?: boolean;
74 |
75 | private _onChangeCompleteSubscription = new Subscription();
76 | private _onSwatchHoverSubscription = new Subscription();
77 |
78 | ngOnInit() {
79 | this.changes = this.onChange
80 | .pipe(
81 | debounceTime(100),
82 | tap(event => {
83 | this.onChangeComplete.emit(event);
84 | switch (this.mode) {
85 | case ColorMode.HEX:
86 | this.colorChange.emit(event.color.hex);
87 | break;
88 | case ColorMode.HSL:
89 | this.colorChange.emit(event.color.hsl);
90 | break;
91 | case ColorMode.HSV:
92 | this.colorChange.emit(event.color.hsv);
93 | break;
94 | case ColorMode.RGB:
95 | this.colorChange.emit(event.color.rgb);
96 | break;
97 | default:
98 | const msg = `The mode '${this.mode}' is not supported`;
99 | if (isDevMode()) {
100 | throw new Error(msg);
101 | } else {
102 | console.warn(msg);
103 | }
104 | break;
105 | }
106 | }),
107 | )
108 | .subscribe();
109 | this.setState(toState(this.color, 0));
110 | this.currentColor = this.hex;
111 | }
112 | ngOnChanges() {
113 | this.setState(toState(this.color, this.oldHue));
114 | }
115 | ngOnDestroy() {
116 | this.changes?.unsubscribe();
117 | this._onChangeCompleteSubscription?.unsubscribe();
118 | this._onSwatchHoverSubscription?.unsubscribe();
119 | }
120 | setState(data) {
121 | this.oldHue = data.oldHue;
122 | this.hsl = data.hsl;
123 | this.hsv = data.hsv;
124 | this.rgb = data.rgb;
125 | this.hex = data.hex;
126 | this.source = data.source;
127 | this.afterValidChange();
128 | }
129 | handleChange(data, $event) {
130 | const isValidColor = simpleCheckForValidColor(data);
131 | if (isValidColor) {
132 | const color = toState(data, data.h || this.oldHue, this.disableAlpha);
133 | this.setState(color);
134 | this.onChange.emit({ color, $event });
135 | this.afterValidChange();
136 | }
137 | }
138 | /** hook for components after a complete change */
139 | afterValidChange() {}
140 |
141 | handleSwatchHover(data, $event) {
142 | const isValidColor = simpleCheckForValidColor(data);
143 | if (isValidColor) {
144 | const color = toState(data, data.h || this.oldHue);
145 | this.setState(color);
146 | this.onSwatchHover.emit({ color, $event });
147 | }
148 | }
149 |
150 | registerOnChange(fn: (hex: string) => void): void {
151 | this._onChangeCompleteSubscription.add(
152 | this.onChangeComplete.pipe(tap(event => fn(event.color.hex))).subscribe(),
153 | );
154 | }
155 |
156 | registerOnTouched(fn: () => void): void {
157 | this._onSwatchHoverSubscription.add(this.onSwatchHover.pipe(tap(() => fn())).subscribe());
158 | }
159 |
160 | setDisabledState(isDisabled: boolean): void {}
161 |
162 | writeValue(hex: string): void {
163 | this.color = hex;
164 | }
165 | }
166 |
167 | @NgModule({
168 | declarations: [ColorWrap],
169 | exports: [ColorWrap],
170 | imports: [CommonModule],
171 | })
172 | export class ColorWrapModule {}
173 |
--------------------------------------------------------------------------------
/src/lib/compact/compact-color.component.ts:
--------------------------------------------------------------------------------
1 | import {
2 | ChangeDetectionStrategy,
3 | Component,
4 | EventEmitter,
5 | Input,
6 | OnChanges,
7 | Output,
8 | } from '@angular/core';
9 |
10 | import { getContrastingColor } from 'ngx-color';
11 |
12 | @Component({
13 | selector: 'color-compact-color',
14 | template: `
15 |
31 | `,
32 | styles: [
33 | `
34 | .compact-dot {
35 | position: absolute;
36 | top: 5px;
37 | right: 5px;
38 | bottom: 5px;
39 | left: 5px;
40 | border-radius: 50%;
41 | opacity: 0;
42 | }
43 | .compact-dot.active {
44 | opacity: 1;
45 | }
46 | `,
47 | ],
48 | changeDetection: ChangeDetectionStrategy.OnPush,
49 | preserveWhitespaces: false,
50 | standalone: false,
51 | })
52 | export class CompactColorComponent implements OnChanges {
53 | @Input() color!: string;
54 | @Input() active!: boolean;
55 | @Output() onClick = new EventEmitter();
56 | @Output() onSwatchHover = new EventEmitter();
57 | swatchStyle: Record = {
58 | width: '15px',
59 | height: '15px',
60 | float: 'left',
61 | marginRight: '5px',
62 | marginBottom: '5px',
63 | position: 'relative',
64 | cursor: 'pointer',
65 | };
66 | swatchFocus: Record = {};
67 | getContrastingColor = getContrastingColor;
68 |
69 | ngOnChanges() {
70 | this.swatchStyle.background = this.color;
71 | this.swatchFocus.boxShadow = `0 0 4px ${this.color}`;
72 | if (this.color.toLowerCase() === '#ffffff') {
73 | this.swatchStyle.boxShadow = 'inset 0 0 0 1px #ddd';
74 | }
75 | }
76 | handleClick({ hex, $event }) {
77 | this.onClick.emit({ hex, $event });
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/src/lib/compact/compact-fields.component.ts:
--------------------------------------------------------------------------------
1 | import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
2 |
3 | import { isValidHex, RGBA } from 'ngx-color';
4 |
5 | @Component({
6 | selector: 'color-compact-fields',
7 | template: `
8 |
9 |
10 |
11 |
17 |
18 |
19 |
25 |
26 |
27 |
33 |
34 |
35 |
41 |
42 |
43 | `,
44 | styles: [
45 | `
46 | .compact-fields {
47 | display: flex;
48 | padding-bottom: 6px;
49 | padding-right: 5px;
50 | position: relative;
51 | }
52 | .compact-active {
53 | position: absolute;
54 | top: 6px;
55 | left: 5px;
56 | height: 9px;
57 | width: 9px;
58 | }
59 | `,
60 | ],
61 | changeDetection: ChangeDetectionStrategy.OnPush,
62 | preserveWhitespaces: false,
63 | standalone: false,
64 | })
65 | export class CompactFieldsComponent {
66 | @Input() hex!: string;
67 | @Input() rgb!: RGBA;
68 | @Output() onChange = new EventEmitter();
69 | HEXWrap: { [key: string]: string } = {
70 | marginTop: '-3px',
71 | marginBottom: '-3px',
72 | // flex: '6 1 0%',
73 | position: 'relative',
74 | };
75 | HEXinput: { [key: string]: string } = {
76 | width: '80%',
77 | padding: '0px',
78 | paddingLeft: '20%',
79 | border: 'none',
80 | outline: 'none',
81 | background: 'none',
82 | fontSize: '12px',
83 | color: '#333',
84 | height: '16px',
85 | };
86 | HEXlabel: { [key: string]: string } = {
87 | display: 'none',
88 | };
89 | RGBwrap: { [key: string]: string } = {
90 | marginTop: '-3px',
91 | marginBottom: '-3px',
92 | // flex: '3 1 0%',
93 | position: 'relative',
94 | };
95 | RGBinput: { [key: string]: string } = {
96 | width: '80%',
97 | padding: '0px',
98 | paddingLeft: '30%',
99 | border: 'none',
100 | outline: 'none',
101 | background: 'none',
102 | fontSize: '12px',
103 | color: '#333',
104 | height: '16px',
105 | };
106 | RGBlabel: { [key: string]: string } = {
107 | position: 'absolute',
108 | top: '6px',
109 | left: '0px',
110 | 'line-height': '16px',
111 | 'text-transform': 'uppercase',
112 | fontSize: '12px',
113 | color: '#999',
114 | };
115 |
116 | handleChange({ data, $event }) {
117 | if (data.hex) {
118 | if (isValidHex(data.hex)) {
119 | this.onChange.emit({
120 | data: {
121 | hex: data.hex,
122 | source: 'hex',
123 | },
124 | $event,
125 | });
126 | }
127 | } else {
128 | this.onChange.emit({
129 | data: {
130 | r: data.r || this.rgb.r,
131 | g: data.g || this.rgb.g,
132 | b: data.b || this.rgb.b,
133 | source: 'rgb',
134 | },
135 | $event,
136 | });
137 | }
138 | }
139 | }
140 |
--------------------------------------------------------------------------------
/src/lib/compact/compact.component.ts:
--------------------------------------------------------------------------------
1 | import { CommonModule } from '@angular/common';
2 | import { ChangeDetectionStrategy, Component, forwardRef, Input, NgModule } from '@angular/core';
3 |
4 | import {
5 | ColorWrap,
6 | EditableInputModule,
7 | isValidHex,
8 | RaisedModule,
9 | SwatchModule,
10 | zDepth,
11 | } from 'ngx-color';
12 | import { CompactColorComponent } from './compact-color.component';
13 | import { CompactFieldsComponent } from './compact-fields.component';
14 | import { NG_VALUE_ACCESSOR } from '@angular/forms';
15 |
16 | @Component({
17 | selector: 'color-compact',
18 | template: `
19 |
25 |
26 |
27 | @for (color of colors; track color) {
28 |
33 | }
34 |
35 |
36 |
41 |
42 |
43 | `,
44 | styles: [
45 | `
46 | .color-compact {
47 | background: #f6f6f6;
48 | radius: 4px;
49 | }
50 | .compact-picker {
51 | padding-top: 5px;
52 | padding-left: 5px;
53 | box-sizing: border-box;
54 | width: 245px;
55 | }
56 | .compact-clear {
57 | clear: both;
58 | }
59 | `,
60 | ],
61 | changeDetection: ChangeDetectionStrategy.OnPush,
62 | preserveWhitespaces: false,
63 | providers: [
64 | {
65 | provide: NG_VALUE_ACCESSOR,
66 | useExisting: forwardRef(() => CompactComponent),
67 | multi: true,
68 | },
69 | {
70 | provide: ColorWrap,
71 | useExisting: forwardRef(() => CompactComponent),
72 | },
73 | ],
74 | standalone: false,
75 | })
76 | export class CompactComponent extends ColorWrap {
77 | /** Color squares to display */
78 | @Input() colors = [
79 | '#4D4D4D',
80 | '#999999',
81 | '#FFFFFF',
82 | '#F44E3B',
83 | '#FE9200',
84 | '#FCDC00',
85 | '#DBDF00',
86 | '#A4DD00',
87 | '#68CCCA',
88 | '#73D8FF',
89 | '#AEA1FF',
90 | '#FDA1FF',
91 | '#333333',
92 | '#808080',
93 | '#cccccc',
94 | '#D33115',
95 | '#E27300',
96 | '#FCC400',
97 | '#B0BC00',
98 | '#68BC00',
99 | '#16A5A5',
100 | '#009CE0',
101 | '#7B64FF',
102 | '#FA28FF',
103 | '#000000',
104 | '#666666',
105 | '#B3B3B3',
106 | '#9F0500',
107 | '#C45100',
108 | '#FB9E00',
109 | '#808900',
110 | '#194D33',
111 | '#0C797D',
112 | '#0062B1',
113 | '#653294',
114 | '#AB149E',
115 | ];
116 | @Input() zDepth: zDepth = 1;
117 | @Input() radius = 1;
118 | @Input() background = '#fff';
119 | disableAlpha = true;
120 |
121 | constructor() {
122 | super();
123 | }
124 | handleBlockChange({ hex, $event }) {
125 | if (isValidHex(hex)) {
126 | this.handleChange({ hex, source: 'hex' }, $event);
127 | }
128 | }
129 | handleValueChange({ data, $event }) {
130 | this.handleChange(data, $event);
131 | }
132 | }
133 |
134 | @NgModule({
135 | declarations: [CompactComponent, CompactColorComponent, CompactFieldsComponent],
136 | exports: [CompactComponent, CompactColorComponent, CompactFieldsComponent],
137 | imports: [CommonModule, EditableInputModule, SwatchModule, RaisedModule],
138 | })
139 | export class ColorCompactModule {}
140 |
--------------------------------------------------------------------------------
/src/lib/compact/compact.spec.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 | import { TestBed, waitForAsync } from '@angular/core/testing';
3 | import { By } from '@angular/platform-browser';
4 |
5 | import { ColorCompactModule } from './compact.component';
6 |
7 | describe('CompactComponent', () => {
8 | beforeEach(waitForAsync(() => {
9 | TestBed.configureTestingModule({
10 | declarations: [CompactTestApp],
11 | imports: [ColorCompactModule],
12 | }).compileComponents();
13 | }));
14 | it(`should apply className to root element`, () => {
15 | const fixture = TestBed.createComponent(CompactTestApp);
16 | fixture.detectChanges();
17 | const divDebugElement = fixture.debugElement.query(By.css('.compact-picker'));
18 | expect(divDebugElement.nativeElement.classList.contains('classy')).toBe(true);
19 | });
20 | });
21 |
22 | @Component({
23 | selector: 'test-app',
24 | template: ``,
25 | standalone: false,
26 | })
27 | class CompactTestApp {
28 | className = 'classy';
29 | }
30 |
--------------------------------------------------------------------------------
/src/lib/compact/ng-package.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "../../../node_modules/ng-packagr/ng-package.schema.json",
3 | "lib": {
4 | "entryFile": "public_api.ts"
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/src/lib/compact/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ngx-color/compact",
3 | "author": "scttcper",
4 | "repository": {
5 | "type": "git",
6 | "url": "git+https://github.com/scttcper/ngx-color.git"
7 | },
8 | "license": "MIT"
9 | }
10 |
--------------------------------------------------------------------------------
/src/lib/compact/public_api.ts:
--------------------------------------------------------------------------------
1 | export { CompactColorComponent } from './compact-color.component';
2 | export { CompactFieldsComponent } from './compact-fields.component';
3 | export { ColorCompactModule, CompactComponent } from './compact.component';
4 |
--------------------------------------------------------------------------------
/src/lib/coordinates.directive.ts:
--------------------------------------------------------------------------------
1 | import {
2 | Directive,
3 | ElementRef,
4 | HostListener,
5 | NgModule,
6 | OnDestroy,
7 | OnInit,
8 | Output,
9 | inject,
10 | } from '@angular/core';
11 |
12 | import { Subject, Subscription } from 'rxjs';
13 | import { distinctUntilChanged } from 'rxjs/operators';
14 |
15 | @Directive({
16 | selector: '[ngx-color-coordinates]',
17 | standalone: false,
18 | })
19 | export class CoordinatesDirective implements OnInit, OnDestroy {
20 | private el = inject(ElementRef);
21 |
22 | @Output()
23 | coordinatesChange = new Subject<{
24 | x: number;
25 | y: number;
26 | top: number;
27 | left: number;
28 | containerWidth: number;
29 | containerHeight: number;
30 | $event: any;
31 | }>();
32 | private mousechange = new Subject<{
33 | x: number;
34 | y: number;
35 | $event: any;
36 | isTouch: boolean;
37 | }>();
38 |
39 | private mouseListening = false;
40 | private sub?: Subscription;
41 | @HostListener('window:mousemove', ['$event', '$event.pageX', '$event.pageY'])
42 | @HostListener('window:touchmove', [
43 | '$event',
44 | '$event.touches[0].clientX',
45 | '$event.touches[0].clientY',
46 | 'true',
47 | ])
48 | mousemove($event: Event, x: number, y: number, isTouch = false) {
49 | if (this.mouseListening) {
50 | $event.preventDefault();
51 | this.mousechange.next({ $event, x, y, isTouch });
52 | }
53 | }
54 | @HostListener('window:mouseup')
55 | @HostListener('window:touchend')
56 | mouseup() {
57 | this.mouseListening = false;
58 | }
59 | @HostListener('mousedown', ['$event', '$event.pageX', '$event.pageY'])
60 | @HostListener('touchstart', [
61 | '$event',
62 | '$event.touches[0].clientX',
63 | '$event.touches[0].clientY',
64 | 'true',
65 | ])
66 | mousedown($event: Event, x: number, y: number, isTouch = false) {
67 | $event.preventDefault();
68 | this.mouseListening = true;
69 | this.mousechange.next({ $event, x, y, isTouch });
70 | }
71 |
72 | ngOnInit() {
73 | this.sub = this.mousechange
74 | .pipe(
75 | // limit times it is updated for the same area
76 | distinctUntilChanged((p, q) => p.x === q.x && p.y === q.y),
77 | )
78 | .subscribe(n => this.handleChange(n.x, n.y, n.$event, n.isTouch));
79 | }
80 |
81 | ngOnDestroy() {
82 | this.sub?.unsubscribe();
83 | }
84 |
85 | handleChange(x: number, y: number, $event: Event, isTouch: boolean) {
86 | const containerWidth = this.el.nativeElement.clientWidth;
87 | const containerHeight = this.el.nativeElement.clientHeight;
88 | const left = x - (this.el.nativeElement.getBoundingClientRect().left + window.pageXOffset);
89 | let top = y - this.el.nativeElement.getBoundingClientRect().top;
90 |
91 | if (!isTouch) {
92 | top = top - window.pageYOffset;
93 | }
94 | this.coordinatesChange.next({
95 | x,
96 | y,
97 | top,
98 | left,
99 | containerWidth,
100 | containerHeight,
101 | $event,
102 | });
103 | }
104 | }
105 |
106 | @NgModule({
107 | declarations: [CoordinatesDirective],
108 | exports: [CoordinatesDirective],
109 | })
110 | export class CoordinatesModule {}
111 |
--------------------------------------------------------------------------------
/src/lib/editable-input.component.ts:
--------------------------------------------------------------------------------
1 | import { CommonModule } from '@angular/common';
2 | import {
3 | ChangeDetectionStrategy,
4 | Component,
5 | EventEmitter,
6 | Input,
7 | NgModule,
8 | OnChanges,
9 | OnDestroy,
10 | OnInit,
11 | Output,
12 | } from '@angular/core';
13 |
14 | import { fromEvent, Subscription } from 'rxjs';
15 |
16 | let nextUniqueId = 0;
17 |
18 | @Component({
19 | selector: 'color-editable-input',
20 | template: `
21 |
22 |
33 | @if (label) {
34 |
35 | {{ label }}
36 |
37 | }
38 |
39 | `,
40 | styles: [
41 | `
42 | :host {
43 | display: flex;
44 | }
45 | .wrap {
46 | position: relative;
47 | }
48 | `,
49 | ],
50 | changeDetection: ChangeDetectionStrategy.OnPush,
51 | standalone: false,
52 | })
53 | export class EditableInputComponent implements OnInit, OnChanges, OnDestroy {
54 | @Input() style!: {
55 | wrap?: Record;
56 | input?: Record;
57 | label?: Record;
58 | };
59 | @Input() label!: string;
60 | @Input() value!: string | number;
61 | @Input() arrowOffset!: number;
62 | @Input() dragLabel!: boolean;
63 | @Input() dragMax!: number;
64 | @Input() placeholder = '';
65 | @Output() onChange = new EventEmitter();
66 | currentValue!: string | number;
67 | blurValue!: string;
68 | wrapStyle!: Record;
69 | inputStyle!: Record;
70 | labelStyle!: Record;
71 | focus = false;
72 | mousemove!: Subscription;
73 | mouseup!: Subscription;
74 | uniqueId: string = `editableInput-${++nextUniqueId}`;
75 |
76 | ngOnInit() {
77 | this.wrapStyle = this.style && this.style.wrap ? this.style.wrap : {};
78 | this.inputStyle = this.style && this.style.input ? this.style.input : {};
79 | this.labelStyle = this.style && this.style.label ? this.style.label : {};
80 | if (this.dragLabel) {
81 | this.labelStyle.cursor = 'ew-resize';
82 | }
83 | }
84 | handleFocus($event) {
85 | this.focus = true;
86 | }
87 | handleFocusOut($event) {
88 | this.focus = false;
89 | this.currentValue = this.blurValue;
90 | }
91 | handleKeydown($event) {
92 | // In case `e.target.value` is a percentage remove the `%` character
93 | // and update accordingly with a percentage
94 | // https://github.com/casesandberg/react-color/issues/383
95 | const stringValue = String($event.target.value);
96 | const isPercentage = stringValue.indexOf('%') > -1;
97 | const num = Number(stringValue.replace(/%/g, ''));
98 | if (isNaN(num)) {
99 | return;
100 | }
101 | const amount = this.arrowOffset || 1;
102 |
103 | // Up
104 | if ($event.keyCode === 38) {
105 | if (this.label) {
106 | this.onChange.emit({
107 | data: { [this.label]: num + amount },
108 | $event,
109 | });
110 | } else {
111 | this.onChange.emit({ data: num + amount, $event });
112 | }
113 |
114 | if (isPercentage) {
115 | this.currentValue = `${num + amount}%`;
116 | } else {
117 | this.currentValue = num + amount;
118 | }
119 | }
120 |
121 | // Down
122 | if ($event.keyCode === 40) {
123 | if (this.label) {
124 | this.onChange.emit({
125 | data: { [this.label]: num - amount },
126 | $event,
127 | });
128 | } else {
129 | this.onChange.emit({ data: num - amount, $event });
130 | }
131 |
132 | if (isPercentage) {
133 | this.currentValue = `${num - amount}%`;
134 | } else {
135 | this.currentValue = num - amount;
136 | }
137 | }
138 | }
139 | handleKeyup($event) {
140 | if ($event.keyCode === 40 || $event.keyCode === 38) {
141 | return;
142 | }
143 | if (`${this.currentValue}` === $event.target.value) {
144 | return;
145 | }
146 |
147 | if (this.label) {
148 | this.onChange.emit({
149 | data: { [this.label]: $event.target.value },
150 | $event,
151 | });
152 | } else {
153 | this.onChange.emit({ data: $event.target.value, $event });
154 | }
155 | }
156 | ngOnChanges() {
157 | if (!this.focus) {
158 | this.currentValue = String(this.value).toUpperCase();
159 | this.blurValue = String(this.value).toUpperCase();
160 | } else {
161 | this.blurValue = String(this.value).toUpperCase();
162 | }
163 | }
164 | ngOnDestroy() {
165 | this.unsubscribe();
166 | }
167 | subscribe() {
168 | this.mousemove = fromEvent(document, 'mousemove').subscribe((ev: Event) => this.handleDrag(ev));
169 | this.mouseup = fromEvent(document, 'mouseup').subscribe(() => this.unsubscribe());
170 | }
171 | unsubscribe() {
172 | this.mousemove?.unsubscribe();
173 | this.mouseup?.unsubscribe();
174 | }
175 | handleMousedown($event: Event) {
176 | if (this.dragLabel) {
177 | $event.preventDefault();
178 | this.handleDrag($event);
179 | this.subscribe();
180 | }
181 | }
182 | handleDrag($event) {
183 | if (this.dragLabel) {
184 | const newValue = Math.round(this.value + $event.movementX);
185 | if (newValue >= 0 && newValue <= this.dragMax) {
186 | this.onChange.emit({ data: { [this.label]: newValue }, $event });
187 | }
188 | }
189 | }
190 | }
191 |
192 | @NgModule({
193 | declarations: [EditableInputComponent],
194 | exports: [EditableInputComponent],
195 | imports: [CommonModule],
196 | })
197 | export class EditableInputModule {}
198 |
--------------------------------------------------------------------------------
/src/lib/github/github-swatch.component.ts:
--------------------------------------------------------------------------------
1 | import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'color-github-swatch',
5 | template: `
6 |
16 | `,
17 | styles: [
18 | `
19 | .github-swatch {
20 | width: 25px;
21 | height: 25px;
22 | font-size: 0;
23 | }
24 | `,
25 | ],
26 | changeDetection: ChangeDetectionStrategy.OnPush,
27 | preserveWhitespaces: false,
28 | standalone: false,
29 | })
30 | export class GithubSwatchComponent {
31 | @Input() color!: string;
32 | @Output() onClick = new EventEmitter();
33 | @Output() onSwatchHover = new EventEmitter();
34 | focusStyle = {
35 | position: 'relative',
36 | 'z-index': '2',
37 | outline: '2px solid #fff',
38 | 'box-shadow': '0 0 5px 2px rgba(0,0,0,0.25)',
39 | };
40 |
41 | handleClick({ hex, $event }) {
42 | this.onClick.emit({ hex, $event });
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/lib/github/github.component.ts:
--------------------------------------------------------------------------------
1 | import { CommonModule } from '@angular/common';
2 | import { ChangeDetectionStrategy, Component, forwardRef, Input, NgModule } from '@angular/core';
3 |
4 | import { ColorWrap, isValidHex, SwatchModule } from 'ngx-color';
5 | import { GithubSwatchComponent } from './github-swatch.component';
6 | import { NG_VALUE_ACCESSOR } from '@angular/forms';
7 |
8 | @Component({
9 | selector: 'color-github',
10 | template: `
11 |
12 |
13 |
14 | @for (color of colors; track color) {
15 |
20 | }
21 |
22 | `,
23 | styles: [
24 | `
25 | .github-picker {
26 | background: rgb(255, 255, 255);
27 | border: 1px solid rgba(0, 0, 0, 0.2);
28 | box-shadow: rgba(0, 0, 0, 0.15) 0px 3px 12px;
29 | border-radius: 4px;
30 | position: relative;
31 | padding: 5px;
32 | display: flex;
33 | flex-wrap: wrap;
34 | box-sizing: border-box;
35 | }
36 | .triangleShadow {
37 | position: absolute;
38 | border-width: 8px;
39 | border-style: solid;
40 | border-color: transparent transparent rgba(0, 0, 0, 0.15);
41 | border-image: initial;
42 | }
43 | .triangle {
44 | position: absolute;
45 | border-width: 7px;
46 | border-style: solid;
47 | border-color: transparent transparent rgb(255, 255, 255);
48 | border-image: initial;
49 | }
50 | .hide-triangle > .triangle {
51 | display: none;
52 | }
53 | .hide-triangle > .triangleShadow {
54 | display: none;
55 | }
56 | .top-left-triangle > .triangle {
57 | top: -14px;
58 | left: 10px;
59 | }
60 | .top-left-triangle > .triangleShadow {
61 | top: -16px;
62 | left: 9px;
63 | }
64 | .top-right-triangle > .triangle {
65 | top: -14px;
66 | right: 10px;
67 | }
68 | .top-right-triangle > .triangleShadow {
69 | top: -16px;
70 | right: 9px;
71 | }
72 | .bottom-right-triangle > .triangle {
73 | top: 35px;
74 | right: 10px;
75 | transform: rotate(180deg);
76 | }
77 | .bottom-right-triangle > .triangleShadow {
78 | top: 37px;
79 | right: 9px;
80 | transform: rotate(180deg);
81 | }
82 | `,
83 | ],
84 | changeDetection: ChangeDetectionStrategy.OnPush,
85 | preserveWhitespaces: false,
86 | providers: [
87 | {
88 | provide: NG_VALUE_ACCESSOR,
89 | useExisting: forwardRef(() => GithubComponent),
90 | multi: true,
91 | },
92 | {
93 | provide: ColorWrap,
94 | useExisting: forwardRef(() => GithubComponent),
95 | },
96 | ],
97 | standalone: false,
98 | })
99 | export class GithubComponent extends ColorWrap {
100 | /** Pixel value for picker width */
101 | @Input() width: string | number = 212;
102 | /** Color squares to display */
103 | @Input() colors = [
104 | '#B80000',
105 | '#DB3E00',
106 | '#FCCB00',
107 | '#008B02',
108 | '#006B76',
109 | '#1273DE',
110 | '#004DCF',
111 | '#5300EB',
112 | '#EB9694',
113 | '#FAD0C3',
114 | '#FEF3BD',
115 | '#C1E1C5',
116 | '#BEDADC',
117 | '#C4DEF6',
118 | '#BED3F3',
119 | '#D4C4FB',
120 | ];
121 | @Input() triangle: 'hide' | 'top-left' | 'top-right' | 'bottom-right' = 'top-left';
122 |
123 | constructor() {
124 | super();
125 | }
126 |
127 | handleBlockChange({ hex, $event }: { hex: string; $event: Event }) {
128 | if (isValidHex(hex)) {
129 | this.handleChange({ hex, source: 'hex' }, $event);
130 | }
131 | }
132 | handleValueChange({ data, $event }) {
133 | this.handleChange(data, $event);
134 | }
135 | }
136 |
137 | @NgModule({
138 | declarations: [GithubComponent, GithubSwatchComponent],
139 | exports: [GithubComponent, GithubSwatchComponent],
140 | imports: [CommonModule, SwatchModule],
141 | })
142 | export class ColorGithubModule {}
143 |
--------------------------------------------------------------------------------
/src/lib/github/github.spec.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 | import { TestBed, waitForAsync } from '@angular/core/testing';
3 | import { By } from '@angular/platform-browser';
4 |
5 | import { ColorGithubModule } from './github.component';
6 |
7 | describe('BlockComponent', () => {
8 | beforeEach(waitForAsync(() => {
9 | TestBed.configureTestingModule({
10 | declarations: [GithubTestApp],
11 | imports: [ColorGithubModule],
12 | }).compileComponents();
13 | }));
14 | it(`should apply className to root element`, () => {
15 | const fixture = TestBed.createComponent(GithubTestApp);
16 | fixture.detectChanges();
17 | const divDebugElement = fixture.debugElement.query(By.css('.github-picker'));
18 | expect(divDebugElement.nativeElement.classList.contains('classy')).toBe(true);
19 | });
20 | });
21 |
22 | @Component({
23 | selector: 'test-app',
24 | template: ` `,
25 | standalone: false,
26 | })
27 | class GithubTestApp {
28 | className = 'classy';
29 | }
30 |
--------------------------------------------------------------------------------
/src/lib/github/ng-package.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "../../../node_modules/ng-packagr/ng-package.schema.json",
3 | "lib": {
4 | "entryFile": "public_api.ts"
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/src/lib/github/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ngx-color/github",
3 | "author": "scttcper",
4 | "repository": {
5 | "type": "git",
6 | "url": "git+https://github.com/scttcper/ngx-color.git"
7 | },
8 | "license": "MIT"
9 | }
10 |
--------------------------------------------------------------------------------
/src/lib/github/public_api.ts:
--------------------------------------------------------------------------------
1 | export { GithubSwatchComponent } from './github-swatch.component';
2 | export { ColorGithubModule, GithubComponent } from './github.component';
3 |
--------------------------------------------------------------------------------
/src/lib/helpers/checkboard.ts:
--------------------------------------------------------------------------------
1 | const checkboardCache: { [key: string]: string } = {};
2 |
3 | export function render(c1: string, c2: string, size: number) {
4 | if (typeof document === 'undefined') {
5 | return null;
6 | }
7 | const canvas = document.createElement('canvas');
8 | canvas.width = size * 2;
9 | canvas.height = size * 2;
10 | const ctx = canvas.getContext('2d');
11 | if (!ctx) {
12 | return null;
13 | } // If no context can be found, return early.
14 | ctx.fillStyle = c1;
15 | ctx.fillRect(0, 0, canvas.width, canvas.height);
16 | ctx.fillStyle = c2;
17 | ctx.fillRect(0, 0, size, size);
18 | ctx.translate(size, size);
19 | ctx.fillRect(0, 0, size, size);
20 | return canvas.toDataURL();
21 | }
22 |
23 | export function getCheckerboard(c1: string, c2: string, size: number) {
24 | const key = `${c1}-${c2}-${size}`;
25 | if (checkboardCache[key]) {
26 | return checkboardCache[key];
27 | }
28 | const checkboard = render(c1, c2, size);
29 | if (!checkboard) {
30 | return null;
31 | }
32 | checkboardCache[key] = checkboard;
33 | return checkboard;
34 | }
35 |
--------------------------------------------------------------------------------
/src/lib/helpers/color.interfaces.ts:
--------------------------------------------------------------------------------
1 | export interface RGB {
2 | r: number;
3 | g: number;
4 | b: number;
5 | }
6 |
7 | export interface RGBA extends RGB {
8 | a: number;
9 | }
10 |
11 | export interface HSL {
12 | h: number;
13 | s: number;
14 | l: number;
15 | }
16 |
17 | export interface HSLA extends HSL {
18 | a: number;
19 | }
20 |
21 | export interface HSV {
22 | h: number;
23 | s: number;
24 | v: number;
25 | }
26 |
27 | export interface HSVA extends HSV {
28 | a: number;
29 | }
30 |
31 | export interface HSVAsource extends HSVA {
32 | source: string;
33 | }
34 |
35 | export interface HSLAsource extends HSLA {
36 | source: string;
37 | }
38 |
39 | export interface Color {
40 | hex: string;
41 | rgb: RGBA;
42 | hsl: HSLA;
43 | hsv: HSVA;
44 | oldHue: number;
45 | source: string;
46 | }
47 |
48 | export interface Shape {
49 | color: string;
50 | title: string;
51 | }
52 |
--------------------------------------------------------------------------------
/src/lib/helpers/color.ts:
--------------------------------------------------------------------------------
1 | import { TinyColor } from '@ctrl/tinycolor';
2 |
3 | import { Color } from './color.interfaces';
4 |
5 | export function simpleCheckForValidColor(data) {
6 | const keysToCheck = ['r', 'g', 'b', 'a', 'h', 's', 'l', 'v'];
7 | let checked = 0;
8 | let passed = 0;
9 | keysToCheck.forEach(letter => {
10 | if (!data[letter]) {
11 | return;
12 | }
13 | checked += 1;
14 | if (!isNaN(data[letter])) {
15 | passed += 1;
16 | }
17 | if (letter === 's' || letter === 'l') {
18 | const percentPatt = /^\d+%$/;
19 | if (percentPatt.test(data[letter])) {
20 | passed += 1;
21 | }
22 | }
23 | });
24 | return checked === passed ? data : false;
25 | }
26 |
27 | export function toState(data, oldHue?: number, disableAlpha?: boolean): Color {
28 | const color = data.hex ? new TinyColor(data.hex) : new TinyColor(data);
29 | if (disableAlpha) {
30 | color.setAlpha(1);
31 | }
32 |
33 | const hsl = color.toHsl();
34 | const hsv = color.toHsv();
35 | const rgb = color.toRgb();
36 | const hex = color.toHex();
37 | if (hsl.s === 0) {
38 | hsl.h = oldHue || 0;
39 | hsv.h = oldHue || 0;
40 | }
41 | const transparent = hex === '000000' && rgb.a === 0;
42 |
43 | return {
44 | hsl,
45 | hex: transparent ? 'transparent' : color.toHexString(),
46 | rgb,
47 | hsv,
48 | oldHue: data.h || oldHue || hsl.h,
49 | source: data.source,
50 | };
51 | }
52 |
53 | export function isValidHex(hex: string) {
54 | return new TinyColor(hex).isValid;
55 | }
56 |
57 | export function getContrastingColor(data) {
58 | if (!data) {
59 | return '#fff';
60 | }
61 | const col = toState(data);
62 | if (col.hex === 'transparent') {
63 | return 'rgba(0,0,0,0.4)';
64 | }
65 | const yiq = (col.rgb.r * 299 + col.rgb.g * 587 + col.rgb.b * 114) / 1000;
66 | return yiq >= 128 ? '#000' : '#fff';
67 | }
68 |
--------------------------------------------------------------------------------
/src/lib/hue.component.ts:
--------------------------------------------------------------------------------
1 | import { CommonModule } from '@angular/common';
2 | import {
3 | ChangeDetectionStrategy,
4 | Component,
5 | EventEmitter,
6 | Input,
7 | NgModule,
8 | OnChanges,
9 | Output,
10 | } from '@angular/core';
11 |
12 | import { CoordinatesModule } from './coordinates.directive';
13 | import { HSLA, HSLAsource } from './helpers/color.interfaces';
14 |
15 | @Component({
16 | selector: 'color-hue',
17 | template: `
18 |
23 |
28 | @if (!hidePointer) {
29 |
32 | }
33 |
34 |
35 | `,
36 | styles: [
37 | `
38 | .color-hue {
39 | position: absolute;
40 | top: 0;
41 | bottom: 0;
42 | left: 0;
43 | right: 0;
44 | }
45 | .color-hue-container {
46 | margin: 0 2px;
47 | position: relative;
48 | height: 100%;
49 | }
50 | .color-hue-pointer {
51 | position: absolute;
52 | }
53 | .color-hue-slider {
54 | margin-top: 1px;
55 | width: 4px;
56 | border-radius: 1px;
57 | height: 8px;
58 | box-shadow: 0 0 2px rgba(0, 0, 0, 0.6);
59 | background: #fff;
60 | transform: translateX(-2px);
61 | }
62 | .color-hue-horizontal {
63 | background: linear-gradient(
64 | to right,
65 | #f00 0%,
66 | #ff0 17%,
67 | #0f0 33%,
68 | #0ff 50%,
69 | #00f 67%,
70 | #f0f 83%,
71 | #f00 100%
72 | );
73 | }
74 | .color-hue-vertical {
75 | background: linear-gradient(
76 | to top,
77 | #f00 0%,
78 | #ff0 17%,
79 | #0f0 33%,
80 | #0ff 50%,
81 | #00f 67%,
82 | #f0f 83%,
83 | #f00 100%
84 | );
85 | }
86 | `,
87 | ],
88 | preserveWhitespaces: false,
89 | changeDetection: ChangeDetectionStrategy.OnPush,
90 | standalone: false,
91 | })
92 | export class HueComponent implements OnChanges {
93 | @Input() hsl!: HSLA;
94 | @Input() pointer!: Record;
95 | @Input() radius!: number;
96 | @Input() shadow!: string;
97 | @Input() hidePointer = false;
98 | @Input() direction: 'horizontal' | 'vertical' = 'horizontal';
99 | @Output() onChange = new EventEmitter<{ data: HSLAsource; $event: Event }>();
100 | left = '0px';
101 | top = '';
102 |
103 | ngOnChanges(): void {
104 | if (this.direction === 'horizontal') {
105 | this.left = `${(this.hsl.h * 100) / 360}%`;
106 | } else {
107 | this.top = `${-((this.hsl.h * 100) / 360) + 100}%`;
108 | }
109 | }
110 | handleChange({ top, left, containerHeight, containerWidth, $event }): void {
111 | let data: HSLAsource | undefined;
112 | if (this.direction === 'vertical') {
113 | let h: number;
114 | if (top < 0) {
115 | h = 359;
116 | } else if (top > containerHeight) {
117 | h = 0;
118 | } else {
119 | const percent = -((top * 100) / containerHeight) + 100;
120 | h = (360 * percent) / 100;
121 | }
122 |
123 | if (this.hsl.h !== h) {
124 | data = {
125 | h,
126 | s: this.hsl.s,
127 | l: this.hsl.l,
128 | a: this.hsl.a,
129 | source: 'rgb',
130 | };
131 | }
132 | } else {
133 | let h: number;
134 | if (left < 0) {
135 | h = 0;
136 | } else if (left > containerWidth) {
137 | h = 359;
138 | } else {
139 | const percent = (left * 100) / containerWidth;
140 | h = (360 * percent) / 100;
141 | }
142 |
143 | if (this.hsl.h !== h) {
144 | data = {
145 | h,
146 | s: this.hsl.s,
147 | l: this.hsl.l,
148 | a: this.hsl.a,
149 | source: 'rgb',
150 | };
151 | }
152 | }
153 |
154 | if (!data) {
155 | return;
156 | }
157 |
158 | this.onChange.emit({ data, $event });
159 | }
160 | }
161 |
162 | @NgModule({
163 | declarations: [HueComponent],
164 | exports: [HueComponent],
165 | imports: [CommonModule, CoordinatesModule],
166 | })
167 | export class HueModule {}
168 |
--------------------------------------------------------------------------------
/src/lib/hue/hue-picker.component.ts:
--------------------------------------------------------------------------------
1 | import { CommonModule } from '@angular/common';
2 | import {
3 | ChangeDetectionStrategy,
4 | Component,
5 | forwardRef,
6 | Input,
7 | NgModule,
8 | OnChanges,
9 | } from '@angular/core';
10 |
11 | import { ColorWrap, HueModule, toState } from 'ngx-color';
12 | import { NG_VALUE_ACCESSOR } from '@angular/forms';
13 |
14 | @Component({
15 | selector: 'color-hue-picker',
16 | template: `
17 |
18 |
25 |
26 | `,
27 | styles: [
28 | `
29 | .hue-picker {
30 | position: relative;
31 | }
32 | `,
33 | ],
34 | changeDetection: ChangeDetectionStrategy.OnPush,
35 | preserveWhitespaces: false,
36 | providers: [
37 | {
38 | provide: NG_VALUE_ACCESSOR,
39 | useExisting: forwardRef(() => HuePickerComponent),
40 | multi: true,
41 | },
42 | {
43 | provide: ColorWrap,
44 | useExisting: forwardRef(() => HuePickerComponent),
45 | },
46 | ],
47 | standalone: false,
48 | })
49 | export class HuePickerComponent extends ColorWrap implements OnChanges {
50 | /** Pixel value for picker width */
51 | @Input() width: string | number = 316;
52 | /** Pixel value for picker height */
53 | @Input() height: string | number = 16;
54 | @Input() radius = 2;
55 | @Input() direction: 'horizontal' | 'vertical' = 'horizontal';
56 | pointer: { [key: string]: string } = {
57 | width: '18px',
58 | height: '18px',
59 | borderRadius: '50%',
60 | transform: 'translate(-9px, -2px)',
61 | backgroundColor: 'rgb(248, 248, 248)',
62 | boxShadow: '0 1px 4px 0 rgba(0, 0, 0, 0.37)',
63 | };
64 |
65 | constructor() {
66 | super();
67 | }
68 |
69 | ngOnChanges() {
70 | if (this.direction === 'vertical') {
71 | this.pointer.transform = 'translate(-3px, -9px)';
72 | }
73 | this.setState(toState(this.color, this.oldHue));
74 | }
75 | handlePickerChange({ data, $event }) {
76 | this.handleChange({ a: 1, h: data.h, l: 0.5, s: 1 }, $event);
77 | }
78 | }
79 |
80 | @NgModule({
81 | declarations: [HuePickerComponent],
82 | exports: [HuePickerComponent],
83 | imports: [CommonModule, HueModule],
84 | })
85 | export class ColorHueModule {}
86 |
--------------------------------------------------------------------------------
/src/lib/hue/hue.spec.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 | import { TestBed, waitForAsync } from '@angular/core/testing';
3 | import { By } from '@angular/platform-browser';
4 |
5 | import { ColorHueModule } from './hue-picker.component';
6 |
7 | describe('HuePickerComponent', () => {
8 | beforeEach(waitForAsync(() => {
9 | TestBed.configureTestingModule({
10 | declarations: [HueTestApp],
11 | imports: [ColorHueModule],
12 | }).compileComponents();
13 | }));
14 | it(`should apply className to root element`, () => {
15 | const fixture = TestBed.createComponent(HueTestApp);
16 | fixture.detectChanges();
17 | const divDebugElement = fixture.debugElement.query(By.css('.hue-picker'));
18 | expect(divDebugElement.nativeElement.classList.contains('classy')).toBe(true);
19 | });
20 | });
21 |
22 | @Component({
23 | selector: 'test-app',
24 | template: ` `,
25 | standalone: false,
26 | })
27 | class HueTestApp {
28 | className = 'classy';
29 | }
30 |
--------------------------------------------------------------------------------
/src/lib/hue/ng-package.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "../../../node_modules/ng-packagr/ng-package.schema.json",
3 | "lib": {
4 | "entryFile": "public_api.ts"
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/src/lib/hue/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ngx-color/hue",
3 | "author": "scttcper",
4 | "repository": {
5 | "type": "git",
6 | "url": "git+https://github.com/scttcper/ngx-color.git"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/src/lib/hue/public_api.ts:
--------------------------------------------------------------------------------
1 | export { ColorHueModule, HuePickerComponent } from './hue-picker.component';
2 |
--------------------------------------------------------------------------------
/src/lib/material/material.component.ts:
--------------------------------------------------------------------------------
1 | import { CommonModule } from '@angular/common';
2 | import { ChangeDetectionStrategy, Component, forwardRef, Input, NgModule } from '@angular/core';
3 |
4 | import { ColorWrap, EditableInputModule, isValidHex, RaisedModule, zDepth } from 'ngx-color';
5 | import { NG_VALUE_ACCESSOR } from '@angular/forms';
6 |
7 | @Component({
8 | selector: 'color-material',
9 | template: `
10 |
11 |
12 |
18 |
19 |
20 |
26 |
27 |
28 |
34 |
35 |
36 |
42 |
43 |
44 |
45 |
46 | `,
47 | styles: [
48 | `
49 | .material-picker {
50 | width: 130px;
51 | height: 130px;
52 | padding: 16px;
53 | font-family: Roboto;
54 | }
55 | .material-split {
56 | display: flex;
57 | margin-right: -10px;
58 | padding-top: 11px;
59 | }
60 | .material-third {
61 | flex: 1 1 0%;
62 | padding-right: 10px;
63 | }
64 | `,
65 | ],
66 | changeDetection: ChangeDetectionStrategy.OnPush,
67 | preserveWhitespaces: false,
68 | providers: [
69 | {
70 | provide: NG_VALUE_ACCESSOR,
71 | useExisting: forwardRef(() => MaterialComponent),
72 | multi: true,
73 | },
74 | {
75 | provide: ColorWrap,
76 | useExisting: forwardRef(() => MaterialComponent),
77 | },
78 | ],
79 | standalone: false,
80 | })
81 | export class MaterialComponent extends ColorWrap {
82 | HEXinput: { [key: string]: string } = {
83 | width: '100%',
84 | marginTop: '12px',
85 | fontSize: '15px',
86 | color: 'rgb(51, 51, 51)',
87 | padding: '0px',
88 | 'border-width': '0px 0px 2px',
89 | outline: 'none',
90 | height: '30px',
91 | };
92 | HEXlabel: { [key: string]: string } = {
93 | position: 'absolute',
94 | top: '0px',
95 | left: '0px',
96 | fontSize: '11px',
97 | color: 'rgb(153, 153, 153)',
98 | 'text-transform': 'capitalize',
99 | };
100 | RGBinput: { [key: string]: string } = {
101 | width: '100%',
102 | marginTop: '12px',
103 | fontSize: '15px',
104 | color: '#333',
105 | padding: '0px',
106 | border: '0px',
107 | 'border-bottom': '1px solid #eee',
108 | outline: 'none',
109 | height: '30px',
110 | };
111 | RGBlabel: { [key: string]: string } = {
112 | position: 'absolute',
113 | top: '0px',
114 | left: '0px',
115 | fontSize: '11px',
116 | color: '#999999',
117 | 'text-transform': 'capitalize',
118 | };
119 | @Input() zDepth: zDepth = 1;
120 | @Input() radius = 1;
121 | @Input() background = '#fff';
122 | disableAlpha = true;
123 |
124 | constructor() {
125 | super();
126 | }
127 |
128 | handleValueChange({ data, $event }) {
129 | this.handleChange(data, $event);
130 | }
131 |
132 | handleInputChange({ data, $event }) {
133 | if (data.hex) {
134 | if (isValidHex(data.hex)) {
135 | this.handleValueChange({
136 | data: {
137 | hex: data.hex,
138 | source: 'hex',
139 | },
140 | $event,
141 | });
142 | }
143 | } else if (data.r || data.g || data.b) {
144 | this.handleValueChange({
145 | data: {
146 | r: data.r || this.rgb.r,
147 | g: data.g || this.rgb.g,
148 | b: data.b || this.rgb.b,
149 | source: 'rgb',
150 | },
151 | $event,
152 | });
153 | }
154 | }
155 |
156 | afterValidChange() {
157 | this.HEXinput['border-bottom-color'] = this.hex;
158 | }
159 | }
160 |
161 | @NgModule({
162 | exports: [MaterialComponent],
163 | declarations: [MaterialComponent],
164 | imports: [CommonModule, EditableInputModule, RaisedModule],
165 | })
166 | export class ColorMaterialModule {}
167 |
--------------------------------------------------------------------------------
/src/lib/material/material.spec.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 | import { TestBed, waitForAsync } from '@angular/core/testing';
3 | import { By } from '@angular/platform-browser';
4 |
5 | import { ColorMaterialModule } from './material.component';
6 |
7 | describe('MaterialComponent', () => {
8 | beforeEach(waitForAsync(() => {
9 | TestBed.configureTestingModule({
10 | declarations: [MaterialTestApp],
11 | imports: [ColorMaterialModule],
12 | }).compileComponents();
13 | }));
14 | it(`should apply className to root element`, () => {
15 | const fixture = TestBed.createComponent(MaterialTestApp);
16 | fixture.detectChanges();
17 | const divDebugElement = fixture.debugElement.query(By.css('.material-picker'));
18 | expect(divDebugElement.nativeElement.classList.contains('classy')).toBe(true);
19 | });
20 | });
21 |
22 | @Component({
23 | selector: 'test-app',
24 | template: ` `,
25 | standalone: false,
26 | })
27 | class MaterialTestApp {
28 | className = 'classy';
29 | }
30 |
--------------------------------------------------------------------------------
/src/lib/material/ng-package.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "../../../node_modules/ng-packagr/ng-package.schema.json",
3 | "lib": {
4 | "entryFile": "public_api.ts"
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/src/lib/material/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ngx-color/material",
3 | "author": "scttcper",
4 | "repository": {
5 | "type": "git",
6 | "url": "git+https://github.com/scttcper/ngx-color.git"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/src/lib/material/public_api.ts:
--------------------------------------------------------------------------------
1 | export { ColorMaterialModule, MaterialComponent } from './material.component';
2 |
--------------------------------------------------------------------------------
/src/lib/ng-package.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
3 | "lib": {
4 | "entryFile": "public_api.ts"
5 | },
6 | "allowedNonPeerDependencies": ["@ctrl/tinycolor", "material-colors"],
7 | "dest": "../../dist"
8 | }
9 |
--------------------------------------------------------------------------------
/src/lib/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ngx-color",
3 | "version": "0.0.0-placeholder",
4 | "description": "A Collection of Color Pickers from Sketch, Photoshop, Chrome & more",
5 | "dependencies": {
6 | "@ctrl/tinycolor": "^4.1.0",
7 | "material-colors": "^1.2.6"
8 | },
9 | "peerDependencies": {
10 | "@angular/core": ">=19.0.0-0",
11 | "@angular/common": ">=19.0.0-0"
12 | },
13 | "homepage": "https://github.com/scttcper/ngx-color",
14 | "repository": "scttcper/ngx-color",
15 | "license": "MIT",
16 | "keywords": [
17 | "angular",
18 | "color picker",
19 | "angular-component",
20 | "colorpicker",
21 | "picker",
22 | "sketch",
23 | "chrome",
24 | "photoshop",
25 | "material design",
26 | "popup"
27 | ],
28 | "publishConfig": {
29 | "access": "public",
30 | "provenance": true
31 | },
32 | "release": {
33 | "branches": [
34 | "master"
35 | ]
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/lib/photoshop/ng-package.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "../../../node_modules/ng-packagr/ng-package.schema.json",
3 | "lib": {
4 | "entryFile": "public_api.ts"
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/src/lib/photoshop/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ngx-color/photoshop",
3 | "author": "scttcper",
4 | "repository": {
5 | "type": "git",
6 | "url": "git+https://github.com/scttcper/ngx-color.git"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/src/lib/photoshop/photoshop-button.component.ts:
--------------------------------------------------------------------------------
1 | import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'color-photoshop-button',
5 | template: `
6 |
7 | {{ label }}
8 |
9 | `,
10 | styles: [
11 | `
12 | .photoshop-button {
13 | background-image: linear-gradient(-180deg, rgb(255, 255, 255) 0%, rgb(230, 230, 230) 100%);
14 | border: 1px solid rgb(135, 135, 135);
15 | border-radius: 2px;
16 | height: 22px;
17 | box-shadow: rgb(234, 234, 234) 0px 1px 0px 0px;
18 | font-size: 14px;
19 | color: rgb(0, 0, 0);
20 | line-height: 20px;
21 | text-align: center;
22 | margin-bottom: 10px;
23 | cursor: pointer;
24 | }
25 | .photoshop-button.active {
26 | box-shadow: 0 0 0 1px #878787;
27 | }
28 | `,
29 | ],
30 | changeDetection: ChangeDetectionStrategy.OnPush,
31 | preserveWhitespaces: false,
32 | standalone: false,
33 | })
34 | export class PhotoshopButtonComponent {
35 | @Input() label = '';
36 | @Input() active = false;
37 | @Output() onClick = new EventEmitter();
38 | }
39 |
--------------------------------------------------------------------------------
/src/lib/photoshop/photoshop-fields.component.ts:
--------------------------------------------------------------------------------
1 | import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
2 |
3 | import { isValidHex, HSV, RGB } from 'ngx-color';
4 |
5 | @Component({
6 | selector: 'color-photoshop-fields',
7 | template: `
8 |
9 |
15 |
21 |
27 |
28 |
34 |
40 |
46 |
47 |
53 |
54 |
°
55 |
%
56 |
%
57 |
58 |
59 | `,
60 | styles: [
61 | `
62 | .photoshop-fields {
63 | padding-top: 5px;
64 | padding-bottom: 9px;
65 | width: 85px;
66 | position: relative;
67 | }
68 | .photoshop-field-symbols {
69 | position: absolute;
70 | top: 5px;
71 | right: -7px;
72 | font-size: 13px;
73 | }
74 | .photoshop-symbol {
75 | height: 24px;
76 | line-height: 24px;
77 | padding-bottom: 7px;
78 | }
79 | .photoshop-divider {
80 | height: 5px;
81 | }
82 | `,
83 | ],
84 | changeDetection: ChangeDetectionStrategy.OnPush,
85 | preserveWhitespaces: false,
86 | standalone: false,
87 | })
88 | export class PhotoshopFieldsComponent {
89 | @Input() rgb!: RGB;
90 | @Input() hsv!: HSV;
91 | @Input() hex!: string;
92 | @Output() onChange = new EventEmitter();
93 | RGBinput: Record = {
94 | marginLeft: '35%',
95 | width: '40%',
96 | height: '22px',
97 | border: '1px solid rgb(136, 136, 136)',
98 | boxShadow: 'rgba(0, 0, 0, 0.1) 0px 1px 1px inset, rgb(236, 236, 236) 0px 1px 0px 0px',
99 | marginBottom: '2px',
100 | fontSize: '13px',
101 | paddingLeft: '3px',
102 | marginRight: '10px',
103 | };
104 | RGBwrap: Record = {
105 | position: 'relative',
106 | };
107 | RGBlabel: Record = {
108 | left: '0px',
109 | width: '34px',
110 | textTransform: 'uppercase',
111 | fontSize: '13px',
112 | height: '24px',
113 | lineHeight: '24px',
114 | position: 'absolute',
115 | };
116 | HEXinput: Record = {
117 | marginLeft: '20%',
118 | width: '80%',
119 | height: '22px',
120 | border: '1px solid #888888',
121 | boxShadow: 'inset 0 1px 1px rgba(0,0,0,.1), 0 1px 0 0 #ECECEC',
122 | marginBottom: '3px',
123 | fontSize: '13px',
124 | paddingLeft: '3px',
125 | };
126 | HEXwrap: Record = {
127 | position: 'relative',
128 | };
129 | HEXlabel: Record = {
130 | position: 'absolute',
131 | top: '0px',
132 | left: '0px',
133 | width: '14px',
134 | textTransform: 'uppercase',
135 | fontSize: '13px',
136 | height: '24px',
137 | lineHeight: '24px',
138 | };
139 |
140 | round(v) {
141 | return Math.round(v);
142 | }
143 | handleValueChange({ data, $event }) {
144 | if (data['#']) {
145 | if (isValidHex(data['#'])) {
146 | this.onChange.emit({
147 | data: {
148 | hex: data['#'],
149 | source: 'hex',
150 | },
151 | $event,
152 | });
153 | }
154 | } else if (data.r || data.g || data.b) {
155 | this.onChange.emit({
156 | data: {
157 | r: data.r || this.rgb.r,
158 | g: data.g || this.rgb.g,
159 | b: data.b || this.rgb.b,
160 | source: 'rgb',
161 | },
162 | $event,
163 | });
164 | } else if (data.h || data.s || data.v) {
165 | this.onChange.emit({
166 | data: {
167 | h: data.h || this.hsv.h,
168 | s: data.s || this.hsv.s,
169 | v: data.v || this.hsv.v,
170 | source: 'hsv',
171 | },
172 | $event,
173 | });
174 | }
175 | }
176 | }
177 |
--------------------------------------------------------------------------------
/src/lib/photoshop/photoshop-previews.component.ts:
--------------------------------------------------------------------------------
1 | import { ChangeDetectionStrategy, Component, Input, OnChanges } from '@angular/core';
2 | import { RGB } from 'ngx-color';
3 |
4 | @Component({
5 | selector: 'color-photoshop-previews',
6 | template: `
7 |
8 |
new
9 |
13 |
current
14 |
15 | `,
16 | styles: [
17 | `
18 | .photoshop-swatches {
19 | border: 1px solid #b3b3b3;
20 | border-bottom: 1px solid #f0f0f0;
21 | margin-bottom: 2px;
22 | margin-top: 1px;
23 | }
24 | .photoshop-new {
25 | height: 34px;
26 | box-shadow:
27 | inset 1px 0 0 #000,
28 | inset -1px 0 0 #000,
29 | inset 0 1px 0 #000;
30 | }
31 | .photoshop-current {
32 | height: 34px;
33 | box-shadow:
34 | inset 1px 0 0 #000,
35 | inset -1px 0 0 #000,
36 | inset 0 -1px 0 #000;
37 | }
38 | .photoshop-label {
39 | font-size: 14px;
40 | color: #000;
41 | text-align: center;
42 | }
43 | `,
44 | ],
45 | changeDetection: ChangeDetectionStrategy.OnPush,
46 | preserveWhitespaces: false,
47 | standalone: false,
48 | })
49 | export class PhotoshopPreviewsComponent implements OnChanges {
50 | @Input() rgb!: RGB;
51 | @Input() currentColor = '';
52 | backgroundNew = '';
53 |
54 | ngOnChanges() {
55 | this.backgroundNew = `rgb(${this.rgb.r},${this.rgb.g}, ${this.rgb.b})`;
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/lib/photoshop/photoshop.component.ts:
--------------------------------------------------------------------------------
1 | import { CommonModule } from '@angular/common';
2 | import {
3 | ChangeDetectionStrategy,
4 | Component,
5 | EventEmitter,
6 | forwardRef,
7 | Input,
8 | NgModule,
9 | Output,
10 | } from '@angular/core';
11 |
12 | import {
13 | AlphaModule,
14 | ColorWrap,
15 | EditableInputModule,
16 | HueModule,
17 | SaturationModule,
18 | SwatchModule,
19 | } from 'ngx-color';
20 | import { PhotoshopButtonComponent } from './photoshop-button.component';
21 | import { PhotoshopFieldsComponent } from './photoshop-fields.component';
22 | import { PhotoshopPreviewsComponent } from './photoshop-previews.component';
23 | import { NG_VALUE_ACCESSOR } from '@angular/forms';
24 |
25 | @Component({
26 | selector: 'color-photoshop',
27 | template: `
28 |
29 |
{{ header }}
30 |
31 |
32 |
38 |
39 |
40 |
46 |
47 |
48 |
49 |
50 |
54 |
55 |
56 |
61 |
62 |
63 |
69 |
70 |
71 |
72 |
73 |
74 | `,
75 | styles: [
76 | `
77 | .photoshop-picker {
78 | background: rgb(220, 220, 220);
79 | border-radius: 4px;
80 | box-shadow:
81 | rgba(0, 0, 0, 0.25) 0px 0px 0px 1px,
82 | rgba(0, 0, 0, 0.15) 0px 8px 16px;
83 | box-sizing: initial;
84 | width: 513px;
85 | }
86 | .photoshop-head {
87 | background-image: linear-gradient(-180deg, rgb(240, 240, 240) 0%, rgb(212, 212, 212) 100%);
88 | border-bottom: 1px solid rgb(177, 177, 177);
89 | box-shadow:
90 | rgba(255, 255, 255, 0.2) 0px 1px 0px 0px inset,
91 | rgba(0, 0, 0, 0.02) 0px -1px 0px 0px inset;
92 | height: 23px;
93 | line-height: 24px;
94 | border-radius: 4px 4px 0px 0px;
95 | font-size: 13px;
96 | color: rgb(77, 77, 77);
97 | text-align: center;
98 | }
99 | .photoshop-body {
100 | padding: 15px 15px 0px;
101 | display: flex;
102 | }
103 | .photoshop-saturation {
104 | width: 256px;
105 | height: 256px;
106 | position: relative;
107 | border-width: 2px;
108 | border-style: solid;
109 | border-color: rgb(179, 179, 179) rgb(179, 179, 179) rgb(240, 240, 240);
110 | border-image: initial;
111 | overflow: hidden;
112 | }
113 | .photoshop-hue {
114 | position: relative;
115 | height: 256px;
116 | width: 23px;
117 | margin-left: 10px;
118 | border-width: 2px;
119 | border-style: solid;
120 | border-color: rgb(179, 179, 179) rgb(179, 179, 179) rgb(240, 240, 240);
121 | border-image: initial;
122 | }
123 | .photoshop-controls {
124 | width: 180px;
125 | margin-left: 10px;
126 | }
127 | .photoshop-top {
128 | display: flex;
129 | }
130 | .photoshop-previews {
131 | width: 60px;
132 | }
133 | .photoshop-actions {
134 | -webkit-box-flex: 1;
135 | flex: 1 1 0%;
136 | margin-left: 20px;
137 | }
138 | `,
139 | ],
140 | changeDetection: ChangeDetectionStrategy.OnPush,
141 | preserveWhitespaces: false,
142 | providers: [
143 | {
144 | provide: NG_VALUE_ACCESSOR,
145 | useExisting: forwardRef(() => PhotoshopComponent),
146 | multi: true,
147 | },
148 | {
149 | provide: ColorWrap,
150 | useExisting: forwardRef(() => PhotoshopComponent),
151 | },
152 | ],
153 | standalone: false,
154 | })
155 | export class PhotoshopComponent extends ColorWrap {
156 | /** Title text */
157 | @Input() header = 'Color Picker';
158 | @Output() onAccept = new EventEmitter();
159 | @Output() onCancel = new EventEmitter();
160 | circle = {
161 | width: '12px',
162 | height: '12px',
163 | borderRadius: '6px',
164 | boxShadow: 'rgb(255, 255, 255) 0px 0px 0px 1px inset',
165 | transform: 'translate(-6px, -10px)',
166 | };
167 | constructor() {
168 | super();
169 | }
170 | handleValueChange({ data, $event }) {
171 | this.handleChange(data, $event);
172 | }
173 | }
174 |
175 | @NgModule({
176 | declarations: [
177 | PhotoshopComponent,
178 | PhotoshopPreviewsComponent,
179 | PhotoshopButtonComponent,
180 | PhotoshopFieldsComponent,
181 | ],
182 | exports: [
183 | PhotoshopComponent,
184 | PhotoshopPreviewsComponent,
185 | PhotoshopButtonComponent,
186 | PhotoshopFieldsComponent,
187 | ],
188 | imports: [
189 | CommonModule,
190 | EditableInputModule,
191 | HueModule,
192 | AlphaModule,
193 | SwatchModule,
194 | SaturationModule,
195 | ],
196 | })
197 | export class ColorPhotoshopModule {}
198 |
--------------------------------------------------------------------------------
/src/lib/photoshop/photoshop.spec.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 | import { TestBed, waitForAsync } from '@angular/core/testing';
3 | import { By } from '@angular/platform-browser';
4 |
5 | import { ColorPhotoshopModule } from './photoshop.component';
6 |
7 | describe('PhotoshopComponent', () => {
8 | beforeEach(waitForAsync(() => {
9 | TestBed.configureTestingModule({
10 | declarations: [PhotoshopTestApp],
11 | imports: [ColorPhotoshopModule],
12 | }).compileComponents();
13 | }));
14 | it('should apply className to root element', () => {
15 | const fixture = TestBed.createComponent(PhotoshopTestApp);
16 | fixture.detectChanges();
17 | const divDebugElement = fixture.debugElement.query(By.css('.photoshop-picker'));
18 | expect(divDebugElement.nativeElement.classList.contains('classy')).toBe(true);
19 | });
20 | });
21 |
22 | @Component({
23 | selector: 'test-app',
24 | template: ` `,
25 | standalone: false,
26 | })
27 | class PhotoshopTestApp {
28 | className = 'classy';
29 | }
30 |
--------------------------------------------------------------------------------
/src/lib/photoshop/public_api.ts:
--------------------------------------------------------------------------------
1 | export { PhotoshopButtonComponent } from './photoshop-button.component';
2 | export { PhotoshopFieldsComponent } from './photoshop-fields.component';
3 | export { PhotoshopPreviewsComponent } from './photoshop-previews.component';
4 | export { ColorPhotoshopModule, PhotoshopComponent } from './photoshop.component';
5 |
--------------------------------------------------------------------------------
/src/lib/public_api.ts:
--------------------------------------------------------------------------------
1 | export * from './alpha.component';
2 | export * from './checkboard.component';
3 | export * from './color-wrap.component';
4 | export * from './editable-input.component';
5 | export * from './hue.component';
6 | export * from './raised.component';
7 | export * from './saturation.component';
8 | export * from './swatch.component';
9 | export * from './coordinates.directive';
10 | export * from './shade.component';
11 |
12 | export * from './helpers/checkboard';
13 | export * from './helpers/color';
14 | export * from './helpers/color.interfaces';
15 |
--------------------------------------------------------------------------------
/src/lib/raised.component.ts:
--------------------------------------------------------------------------------
1 | import { CommonModule } from '@angular/common';
2 | import { ChangeDetectionStrategy, Component, Input, NgModule } from '@angular/core';
3 |
4 | export type zDepth = 0 | 1 | 2 | 3 | 4 | 5;
5 |
6 | @Component({
7 | selector: 'color-raised',
8 | template: `
9 |
15 | `,
16 | styles: [
17 | `
18 | .raised-wrap {
19 | position: relative;
20 | display: inline-block;
21 | }
22 | .raised-bg {
23 | position: absolute;
24 | top: 0px;
25 | right: 0px;
26 | bottom: 0px;
27 | left: 0px;
28 | }
29 | .raised-content {
30 | position: relative;
31 | }
32 | .zDepth-0 {
33 | box-shadow: none;
34 | }
35 | .zDepth-1 {
36 | box-shadow:
37 | 0 2px 10px rgba(0, 0, 0, 0.12),
38 | 0 2px 5px rgba(0, 0, 0, 0.16);
39 | }
40 | .zDepth-2 {
41 | box-shadow:
42 | 0 6px 20px rgba(0, 0, 0, 0.19),
43 | 0 8px 17px rgba(0, 0, 0, 0.2);
44 | }
45 | .zDepth-3 {
46 | box-shadow:
47 | 0 17px 50px rgba(0, 0, 0, 0.19),
48 | 0 12px 15px rgba(0, 0, 0, 0.24);
49 | }
50 | .zDepth-4 {
51 | box-shadow:
52 | 0 25px 55px rgba(0, 0, 0, 0.21),
53 | 0 16px 28px rgba(0, 0, 0, 0.22);
54 | }
55 | .zDepth-5 {
56 | box-shadow:
57 | 0 40px 77px rgba(0, 0, 0, 0.22),
58 | 0 27px 24px rgba(0, 0, 0, 0.2);
59 | }
60 | `,
61 | ],
62 | preserveWhitespaces: false,
63 | changeDetection: ChangeDetectionStrategy.OnPush,
64 | standalone: false,
65 | })
66 | export class RaisedComponent {
67 | @Input() zDepth: zDepth = 1;
68 | @Input() radius = 1;
69 | @Input() background = '#fff';
70 | }
71 |
72 | @NgModule({
73 | declarations: [RaisedComponent],
74 | exports: [RaisedComponent],
75 | imports: [CommonModule],
76 | })
77 | export class RaisedModule {}
78 |
--------------------------------------------------------------------------------
/src/lib/saturation.component.ts:
--------------------------------------------------------------------------------
1 | import { CommonModule } from '@angular/common';
2 | import {
3 | ChangeDetectionStrategy,
4 | Component,
5 | EventEmitter,
6 | Input,
7 | NgModule,
8 | OnChanges,
9 | Output,
10 | } from '@angular/core';
11 |
12 | import { CoordinatesModule } from './coordinates.directive';
13 | import { HSLA, HSVA, HSVAsource } from './helpers/color.interfaces';
14 |
15 | @Component({
16 | selector: 'color-saturation',
17 | template: `
18 |
36 | `,
37 | styles: [
38 | `
39 | .saturation-white {
40 | background: linear-gradient(to right, #fff, rgba(255, 255, 255, 0));
41 | position: absolute;
42 | top: 0;
43 | bottom: 0;
44 | left: 0;
45 | right: 0;
46 | }
47 | .saturation-black {
48 | background: linear-gradient(to top, #000, rgba(0, 0, 0, 0));
49 | position: absolute;
50 | top: 0;
51 | bottom: 0;
52 | left: 0;
53 | right: 0;
54 | }
55 | .color-saturation {
56 | position: absolute;
57 | top: 0;
58 | bottom: 0;
59 | left: 0;
60 | right: 0;
61 | }
62 | .saturation-pointer {
63 | position: absolute;
64 | cursor: default;
65 | }
66 | .saturation-circle {
67 | width: 4px;
68 | height: 4px;
69 | box-shadow:
70 | 0 0 0 1.5px #fff,
71 | inset 0 0 1px 1px rgba(0, 0, 0, 0.3),
72 | 0 0 1px 2px rgba(0, 0, 0, 0.4);
73 | border-radius: 50%;
74 | cursor: hand;
75 | transform: translate(-2px, -4px);
76 | }
77 | `,
78 | ],
79 | preserveWhitespaces: false,
80 | changeDetection: ChangeDetectionStrategy.OnPush,
81 | standalone: false,
82 | })
83 | export class SaturationComponent implements OnChanges {
84 | @Input() hsl!: HSLA;
85 | @Input() hsv!: HSVA;
86 | @Input() radius!: number;
87 | @Input() pointer!: Record;
88 | @Input() circle!: Record;
89 | @Output() onChange = new EventEmitter<{ data: HSVAsource; $event: Event }>();
90 | background!: string;
91 | pointerTop!: string;
92 | pointerLeft!: string;
93 |
94 | ngOnChanges() {
95 | this.background = `hsl(${this.hsl.h}, 100%, 50%)`;
96 | this.pointerTop = -(this.hsv.v * 100) + 1 + 100 + '%';
97 | this.pointerLeft = this.hsv.s * 100 + '%';
98 | }
99 | handleChange({ top, left, containerHeight, containerWidth, $event }) {
100 | if (left < 0) {
101 | left = 0;
102 | } else if (left > containerWidth) {
103 | left = containerWidth;
104 | } else if (top < 0) {
105 | top = 0;
106 | } else if (top > containerHeight) {
107 | top = containerHeight;
108 | }
109 |
110 | const saturation = left / containerWidth;
111 | let bright = -(top / containerHeight) + 1;
112 | bright = bright > 0 ? bright : 0;
113 | bright = bright > 1 ? 1 : bright;
114 |
115 | const data: HSVAsource = {
116 | h: this.hsl.h,
117 | s: saturation,
118 | v: bright,
119 | a: this.hsl.a,
120 | source: 'hsva',
121 | };
122 | this.onChange.emit({ data, $event });
123 | }
124 | }
125 |
126 | @NgModule({
127 | declarations: [SaturationComponent],
128 | exports: [SaturationComponent],
129 | imports: [CommonModule, CoordinatesModule],
130 | })
131 | export class SaturationModule {}
132 |
--------------------------------------------------------------------------------
/src/lib/shade.component.ts:
--------------------------------------------------------------------------------
1 | import { CommonModule } from '@angular/common';
2 | import {
3 | ChangeDetectionStrategy,
4 | Component,
5 | EventEmitter,
6 | Input,
7 | NgModule,
8 | OnChanges,
9 | Output,
10 | } from '@angular/core';
11 | import { CoordinatesModule } from './coordinates.directive';
12 | import { HSLA, RGBA } from './helpers/color.interfaces';
13 | import { TinyColor } from '@ctrl/tinycolor';
14 |
15 | @Component({
16 | selector: 'color-shade',
17 | template: `
18 |
31 | `,
32 | styles: [
33 | `
34 | .shade {
35 | position: absolute;
36 | top: 0;
37 | bottom: 0;
38 | left: 0;
39 | right: 0;
40 | }
41 | .shade-gradient {
42 | position: absolute;
43 | top: 0;
44 | bottom: 0;
45 | left: 0;
46 | right: 0;
47 | }
48 | .shade-container {
49 | position: relative;
50 | height: 100%;
51 | margin: 0 3px;
52 | }
53 | .shade-pointer {
54 | position: absolute;
55 | }
56 | .shade-slider {
57 | width: 4px;
58 | border-radius: 1px;
59 | height: 8px;
60 | box-shadow: 0 0 2px rgba(0, 0, 0, 0.6);
61 | background: #fff;
62 | margin-top: 1px;
63 | transform: translateX(-2px);
64 | }
65 | `,
66 | ],
67 | changeDetection: ChangeDetectionStrategy.OnPush,
68 | preserveWhitespaces: false,
69 | standalone: false,
70 | })
71 | export class ShadeComponent implements OnChanges {
72 | @Input() hsl!: HSLA;
73 | @Input() rgb!: RGBA;
74 | @Input() pointer!: Record;
75 | @Input() shadow!: string;
76 | @Input() radius!: string;
77 | @Output() onChange = new EventEmitter();
78 | gradient!: Record;
79 | pointerLeft!: number;
80 | pointerTop?: number;
81 |
82 | ngOnChanges() {
83 | this.gradient = {
84 | background: `linear-gradient(to right,
85 | hsl(${this.hsl.h}, 90%, 55%),
86 | #000)`,
87 | };
88 | const hsv = new TinyColor(this.hsl).toHsv();
89 | this.pointerLeft = 100 - hsv.v * 100;
90 | }
91 |
92 | handleChange({ left, containerWidth, $event }): void {
93 | let data;
94 | let v: number;
95 | if (left < 0) {
96 | v = 0;
97 | } else if (left > containerWidth) {
98 | v = 1;
99 | } else {
100 | v = Math.round((left * 100) / containerWidth) / 100;
101 | }
102 |
103 | const hsv = new TinyColor(this.hsl).toHsv();
104 | if (hsv.v !== v) {
105 | data = {
106 | h: this.hsl.h,
107 | s: 100,
108 | v: 1 - v,
109 | l: this.hsl.l,
110 | a: this.hsl.a,
111 | source: 'rgb',
112 | };
113 | }
114 |
115 | if (!data) {
116 | return;
117 | }
118 |
119 | this.onChange.emit({ data, $event });
120 | }
121 | }
122 |
123 | @NgModule({
124 | declarations: [ShadeComponent],
125 | exports: [ShadeComponent],
126 | imports: [CommonModule, CoordinatesModule],
127 | })
128 | export class ShadeModule {}
129 |
--------------------------------------------------------------------------------
/src/lib/shade/ng-package.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "../../../node_modules/ng-packagr/ng-package.schema.json",
3 | "lib": {
4 | "entryFile": "public_api.ts"
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/src/lib/shade/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ngx-color/shade",
3 | "author": "scttcper",
4 | "repository": {
5 | "type": "git",
6 | "url": "git+https://github.com/scttcper/ngx-color.git"
7 | },
8 | "license": "MIT"
9 | }
10 |
--------------------------------------------------------------------------------
/src/lib/shade/public_api.ts:
--------------------------------------------------------------------------------
1 | export { ColorShadeModule, ShadeSliderComponent } from './shade-picker.component';
2 |
--------------------------------------------------------------------------------
/src/lib/shade/shade-picker.component.ts:
--------------------------------------------------------------------------------
1 | import { CommonModule } from '@angular/common';
2 | import {
3 | ChangeDetectionStrategy,
4 | Component,
5 | forwardRef,
6 | Input,
7 | NgModule,
8 | OnChanges,
9 | } from '@angular/core';
10 | import { ColorWrap, ShadeModule, toState } from 'ngx-color';
11 | import { NG_VALUE_ACCESSOR } from '@angular/forms';
12 |
13 | @Component({
14 | selector: 'color-shade-picker',
15 | template: `
16 |
21 |
27 |
28 | `,
29 | styles: [
30 | `
31 | .shade-slider {
32 | position: relative;
33 | }
34 | `,
35 | ],
36 | changeDetection: ChangeDetectionStrategy.OnPush,
37 | preserveWhitespaces: false,
38 | providers: [
39 | {
40 | provide: NG_VALUE_ACCESSOR,
41 | useExisting: forwardRef(() => ShadeSliderComponent),
42 | multi: true,
43 | },
44 | {
45 | provide: ColorWrap,
46 | useExisting: forwardRef(() => ShadeSliderComponent),
47 | },
48 | ],
49 | standalone: false,
50 | })
51 | export class ShadeSliderComponent extends ColorWrap implements OnChanges {
52 | /** Pixel value for picker width */
53 | @Input() width: string | number = 316;
54 | /** Pixel value for picker height */
55 | @Input() height: string | number = 16;
56 | pointer: { [key: string]: string } = {
57 | width: '18px',
58 | height: '18px',
59 | borderRadius: '50%',
60 | transform: 'translate(-9px, -2px)',
61 | boxShadow: '0 1px 4px 0 rgba(0, 0, 0, 0.37)',
62 | };
63 |
64 | constructor() {
65 | super();
66 | }
67 | ngOnChanges() {
68 | this.setState(toState(this.color, this.oldHue));
69 | }
70 | handlePickerChange({ data, $event }) {
71 | this.handleChange(data, $event);
72 | }
73 | }
74 |
75 | @NgModule({
76 | declarations: [ShadeSliderComponent],
77 | exports: [ShadeSliderComponent],
78 | imports: [CommonModule, ShadeModule],
79 | })
80 | export class ColorShadeModule {}
81 |
--------------------------------------------------------------------------------
/src/lib/shade/shade-picker.spec.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 | import { TestBed, waitForAsync } from '@angular/core/testing';
3 |
4 | import { ColorShadeModule } from './shade-picker.component';
5 |
6 | export const red = {
7 | hsl: { a: 1, h: 0, l: 0.5, s: 1 },
8 | hex: '#ff0000',
9 | rgb: { r: 255, g: 0, b: 0, a: 1 },
10 | hsv: { h: 0, s: 1, v: 1, a: 1 },
11 | };
12 |
13 | describe('AlphaComponent', () => {
14 | beforeEach(waitForAsync(() => {
15 | TestBed.configureTestingModule({
16 | declarations: [ColorShadeSliderApp],
17 | imports: [ColorShadeModule],
18 | }).compileComponents();
19 | }));
20 | it(`should apply className to root element`, () => {
21 | const fixture = TestBed.createComponent(ColorShadeSliderApp);
22 | fixture.detectChanges();
23 | const compiled = fixture.nativeElement;
24 | expect(compiled.querySelector('.shade-slider').className).toContain('classy');
25 | });
26 | });
27 |
28 | @Component({
29 | selector: 'test-app',
30 | template: ``,
31 | standalone: false,
32 | })
33 | class ColorShadeSliderApp {
34 | className = 'classy';
35 | }
36 |
--------------------------------------------------------------------------------
/src/lib/sketch/ng-package.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "../../../node_modules/ng-packagr/ng-package.schema.json",
3 | "lib": {
4 | "entryFile": "public_api.ts"
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/src/lib/sketch/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ngx-color/sketch",
3 | "author": "scttcper",
4 | "repository": {
5 | "type": "git",
6 | "url": "git+https://github.com/scttcper/ngx-color.git"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/src/lib/sketch/public_api.ts:
--------------------------------------------------------------------------------
1 | export { SketchFieldsComponent } from './sketch-fields.component';
2 | export { SketchPresetColorsComponent } from './sketch-preset-colors.component';
3 | export { ColorSketchModule, SketchComponent } from './sketch.component';
4 |
--------------------------------------------------------------------------------
/src/lib/sketch/sketch-fields.component.ts:
--------------------------------------------------------------------------------
1 | import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
2 |
3 | import { isValidHex, HSLA, RGBA } from 'ngx-color';
4 | import { TinyColor } from '@ctrl/tinycolor';
5 |
6 | @Component({
7 | selector: 'color-sketch-fields',
8 | template: `
9 |
10 |
11 |
17 |
18 |
19 |
27 |
28 |
29 |
37 |
38 |
39 |
47 |
48 | @if (disableAlpha === false) {
49 |
50 |
58 |
59 | }
60 |
61 | `,
62 | styles: [
63 | `
64 | .sketch-fields {
65 | display: flex;
66 | padding-top: 4px;
67 | }
68 | .sketch-double {
69 | -webkit-box-flex: 2;
70 | flex: 2 1 0%;
71 | }
72 | .sketch-single {
73 | flex: 1 1 0%;
74 | padding-left: 6px;
75 | }
76 | .sketch-alpha {
77 | -webkit-box-flex: 1;
78 | flex: 1 1 0%;
79 | padding-left: 6px;
80 | }
81 | :host-context([dir='rtl']) .sketch-single {
82 | padding-right: 6px;
83 | padding-left: 0;
84 | }
85 | :host-context([dir='rtl']) .sketch-alpha {
86 | padding-right: 6px;
87 | padding-left: 0;
88 | }
89 | `,
90 | ],
91 | changeDetection: ChangeDetectionStrategy.OnPush,
92 | preserveWhitespaces: false,
93 | standalone: false,
94 | })
95 | export class SketchFieldsComponent {
96 | @Input() hsl!: HSLA;
97 | @Input() rgb!: RGBA;
98 | @Input() hex!: string;
99 | @Input() disableAlpha = false;
100 | @Output() onChange = new EventEmitter();
101 | input: { [key: string]: string } = {
102 | width: '100%',
103 | padding: '4px 10% 3px',
104 | border: 'none',
105 | boxSizing: 'border-box',
106 | boxShadow: 'inset 0 0 0 1px #ccc',
107 | fontSize: '11px',
108 | };
109 | label: { [key: string]: string } = {
110 | display: 'block',
111 | textAlign: 'center',
112 | fontSize: '11px',
113 | color: '#222',
114 | paddingTop: '3px',
115 | paddingBottom: '4px',
116 | textTransform: 'capitalize',
117 | };
118 |
119 | round(value) {
120 | return Math.round(value);
121 | }
122 | handleChange({ data, $event }) {
123 | if (data.hex) {
124 | if (isValidHex(data.hex)) {
125 | const color = new TinyColor(data.hex);
126 | this.onChange.emit({
127 | data: {
128 | hex: this.disableAlpha || data.hex.length <= 6 ? color.toHex() : color.toHex8(),
129 | source: 'hex',
130 | },
131 | $event,
132 | });
133 | }
134 | } else if (data.r || data.g || data.b) {
135 | this.onChange.emit({
136 | data: {
137 | r: data.r || this.rgb.r,
138 | g: data.g || this.rgb.g,
139 | b: data.b || this.rgb.b,
140 | source: 'rgb',
141 | },
142 | $event,
143 | });
144 | } else if (data.a) {
145 | if (data.a < 0) {
146 | data.a = 0;
147 | } else if (data.a > 100) {
148 | data.a = 100;
149 | }
150 | data.a /= 100;
151 |
152 | if (this.disableAlpha) {
153 | data.a = 1;
154 | }
155 |
156 | this.onChange.emit({
157 | data: {
158 | h: this.hsl.h,
159 | s: this.hsl.s,
160 | l: this.hsl.l,
161 | a: Math.round(data.a * 100) / 100,
162 | source: 'rgb',
163 | },
164 | $event,
165 | });
166 | } else if (data.h || data.s || data.l) {
167 | this.onChange.emit({
168 | data: {
169 | h: data.h || this.hsl.h,
170 | s: Number((data.s && data.s) || this.hsl.s),
171 | l: Number((data.l && data.l) || this.hsl.l),
172 | source: 'hsl',
173 | },
174 | $event,
175 | });
176 | }
177 | }
178 | }
179 |
--------------------------------------------------------------------------------
/src/lib/sketch/sketch-preset-colors.component.ts:
--------------------------------------------------------------------------------
1 | import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
2 |
3 | import { Shape } from 'ngx-color';
4 |
5 | @Component({
6 | selector: 'color-sketch-preset-colors',
7 | template: `
8 |
9 | @for (c of colors; track c) {
10 |
11 |
19 |
20 | }
21 |
22 | `,
23 | styles: [
24 | `
25 | .sketch-swatches {
26 | position: relative;
27 | display: flex;
28 | flex-wrap: wrap;
29 | margin: 0px -10px;
30 | padding: 10px 0px 0px 10px;
31 | border-top: 1px solid rgb(238, 238, 238);
32 | }
33 | .sketch-wrap {
34 | width: 16px;
35 | height: 16px;
36 | margin: 0px 10px 10px 0px;
37 | }
38 | :host-context([dir='rtl']) .sketch-swatches {
39 | padding-right: 10px;
40 | padding-left: 0;
41 | }
42 | :host-context([dir='rtl']) .sketch-wrap {
43 | margin-left: 10px;
44 | margin-right: 0;
45 | }
46 | `,
47 | ],
48 | changeDetection: ChangeDetectionStrategy.OnPush,
49 | preserveWhitespaces: false,
50 | standalone: false,
51 | })
52 | export class SketchPresetColorsComponent {
53 | @Input() colors!: string[];
54 | @Output() onClick = new EventEmitter();
55 | @Output() onSwatchHover = new EventEmitter();
56 | swatchStyle = {
57 | borderRadius: '3px',
58 | boxShadow: 'inset 0 0 0 1px rgba(0,0,0,.15)',
59 | };
60 |
61 | handleClick({ hex, $event }) {
62 | this.onClick.emit({ hex, $event });
63 | }
64 | normalizeValue(val: string | Shape) {
65 | if (typeof val === 'string') {
66 | return { color: val };
67 | }
68 | return val;
69 | }
70 | focusStyle(val: string | Shape) {
71 | const c = this.normalizeValue(val);
72 | return {
73 | boxShadow: `inset 0 0 0 1px rgba(0,0,0,.15), 0 0 4px ${c.color}`,
74 | };
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/src/lib/sketch/sketch.component.ts:
--------------------------------------------------------------------------------
1 | import { CommonModule } from '@angular/common';
2 | import { ChangeDetectionStrategy, Component, forwardRef, Input, NgModule } from '@angular/core';
3 |
4 | import {
5 | AlphaModule,
6 | CheckboardModule,
7 | ColorWrap,
8 | EditableInputModule,
9 | HueModule,
10 | isValidHex,
11 | SaturationModule,
12 | SwatchModule,
13 | } from 'ngx-color';
14 | import { SketchFieldsComponent } from './sketch-fields.component';
15 | import { SketchPresetColorsComponent } from './sketch-preset-colors.component';
16 | import { NG_VALUE_ACCESSOR } from '@angular/forms';
17 |
18 | @Component({
19 | selector: 'color-sketch',
20 | template: `
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | @if (disableAlpha === false) {
32 |
33 |
39 |
40 | }
41 |
42 |
46 |
47 |
48 |
55 |
56 | @if (presetColors && presetColors.length) {
57 |
58 |
63 |
64 | }
65 |
66 | `,
67 | styles: [
68 | `
69 | .sketch-picker {
70 | padding: 10px 10px 3px;
71 | box-sizing: initial;
72 | background: #fff;
73 | border-radius: 4px;
74 | box-shadow:
75 | 0 0 0 1px rgba(0, 0, 0, 0.15),
76 | 0 8px 16px rgba(0, 0, 0, 0.15);
77 | }
78 | .sketch-saturation {
79 | width: 100%;
80 | padding-bottom: 75%;
81 | position: relative;
82 | overflow: hidden;
83 | }
84 | .sketch-fields-container {
85 | display: block;
86 | }
87 | .sketch-swatches-container {
88 | display: block;
89 | }
90 | .sketch-controls {
91 | display: flex;
92 | }
93 | .sketch-sliders {
94 | padding: 4px 0px;
95 | -webkit-box-flex: 1;
96 | flex: 1 1 0%;
97 | }
98 | .sketch-hue {
99 | position: relative;
100 | height: 10px;
101 | overflow: hidden;
102 | }
103 | .sketch-alpha {
104 | position: relative;
105 | height: 10px;
106 | margin-top: 4px;
107 | overflow: hidden;
108 | }
109 | .sketch-color {
110 | width: 24px;
111 | height: 24px;
112 | position: relative;
113 | margin-top: 4px;
114 | margin-left: 4px;
115 | border-radius: 3px;
116 | }
117 | .sketch-active {
118 | position: absolute;
119 | top: 0px;
120 | right: 0px;
121 | bottom: 0px;
122 | left: 0px;
123 | border-radius: 2px;
124 | box-shadow:
125 | rgba(0, 0, 0, 0.15) 0px 0px 0px 1px inset,
126 | rgba(0, 0, 0, 0.25) 0px 0px 4px inset;
127 | }
128 | :host-context([dir='rtl']) .sketch-color {
129 | margin-right: 4px;
130 | margin-left: 0;
131 | }
132 | `,
133 | ],
134 | changeDetection: ChangeDetectionStrategy.OnPush,
135 | preserveWhitespaces: false,
136 | providers: [
137 | {
138 | provide: NG_VALUE_ACCESSOR,
139 | useExisting: forwardRef(() => SketchComponent),
140 | multi: true,
141 | },
142 | {
143 | provide: ColorWrap,
144 | useExisting: forwardRef(() => SketchComponent),
145 | },
146 | ],
147 | standalone: false,
148 | })
149 | export class SketchComponent extends ColorWrap {
150 | /** Remove alpha slider and options from picker */
151 | @Input() disableAlpha = false;
152 | /** Hex strings for default colors at bottom of picker */
153 | @Input() presetColors = [
154 | '#D0021B',
155 | '#F5A623',
156 | '#F8E71C',
157 | '#8B572A',
158 | '#7ED321',
159 | '#417505',
160 | '#BD10E0',
161 | '#9013FE',
162 | '#4A90E2',
163 | '#50E3C2',
164 | '#B8E986',
165 | '#000000',
166 | '#4A4A4A',
167 | '#9B9B9B',
168 | '#FFFFFF',
169 | ];
170 | /** Width of picker */
171 | @Input() width = 200;
172 | activeBackground!: string;
173 | constructor() {
174 | super();
175 | }
176 | afterValidChange() {
177 | const alpha = this.disableAlpha ? 1 : this.rgb.a;
178 | this.activeBackground = `rgba(${this.rgb.r}, ${this.rgb.g}, ${this.rgb.b}, ${alpha})`;
179 | }
180 | handleValueChange({ data, $event }) {
181 | this.handleChange(data, $event);
182 | }
183 | handleBlockChange({ hex, $event }) {
184 | if (isValidHex(hex)) {
185 | // this.hex = hex;
186 | this.handleChange(
187 | {
188 | hex,
189 | source: 'hex',
190 | },
191 | $event,
192 | );
193 | }
194 | }
195 | }
196 |
197 | @NgModule({
198 | declarations: [SketchComponent, SketchFieldsComponent, SketchPresetColorsComponent],
199 | exports: [SketchComponent, SketchFieldsComponent, SketchPresetColorsComponent],
200 | imports: [
201 | CommonModule,
202 | AlphaModule,
203 | CheckboardModule,
204 | EditableInputModule,
205 | HueModule,
206 | SaturationModule,
207 | SwatchModule,
208 | ],
209 | })
210 | export class ColorSketchModule {}
211 |
--------------------------------------------------------------------------------
/src/lib/sketch/sketch.spec.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 | import { TestBed, waitForAsync } from '@angular/core/testing';
3 | import { By } from '@angular/platform-browser';
4 |
5 | import { ColorSketchModule } from './sketch.component';
6 |
7 | describe('SketchComponent', () => {
8 | beforeEach(waitForAsync(() => {
9 | TestBed.configureTestingModule({
10 | declarations: [SketchTestApp],
11 | imports: [ColorSketchModule],
12 | }).compileComponents();
13 | }));
14 | it(`should apply className to root element`, () => {
15 | const fixture = TestBed.createComponent(SketchTestApp);
16 | fixture.detectChanges();
17 | const divDebugElement = fixture.debugElement.query(By.css('.sketch-picker'));
18 | expect(divDebugElement.nativeElement.classList.contains('classy')).toBe(true);
19 | });
20 | });
21 |
22 | @Component({
23 | selector: 'test-app',
24 | template: ` `,
25 | standalone: false,
26 | })
27 | class SketchTestApp {
28 | className = 'classy';
29 | }
30 |
--------------------------------------------------------------------------------
/src/lib/slider/ng-package.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "../../../node_modules/ng-packagr/ng-package.schema.json",
3 | "lib": {
4 | "entryFile": "public_api.ts"
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/src/lib/slider/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ngx-color/slider",
3 | "author": "scttcper",
4 | "repository": {
5 | "type": "git",
6 | "url": "git+https://github.com/scttcper/ngx-color.git"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/src/lib/slider/public_api.ts:
--------------------------------------------------------------------------------
1 | export { SliderSwatchComponent } from './slider-swatch.component';
2 | export { SliderSwatchesComponent } from './slider-swatches.component';
3 | export { ColorSliderModule, SliderComponent } from './slider.component';
4 |
--------------------------------------------------------------------------------
/src/lib/slider/slider-swatch.component.ts:
--------------------------------------------------------------------------------
1 | import {
2 | ChangeDetectionStrategy,
3 | Component,
4 | EventEmitter,
5 | Input,
6 | OnChanges,
7 | Output,
8 | } from '@angular/core';
9 |
10 | import { HSL } from 'ngx-color';
11 |
12 | @Component({
13 | selector: 'color-slider-swatch',
14 | template: `
15 |
23 | `,
24 | styles: [
25 | `
26 | .slider-swatch {
27 | height: 12px;
28 | background: rgb(121, 211, 166);
29 | cursor: pointer;
30 | }
31 | .slider-swatch.active {
32 | transform: scaleY(1.8);
33 | border-top-right-radius: 3.6px 2px;
34 | border-top-left-radius: 3.6px 2px;
35 | border-bottom-right-radius: 3.6px 2px;
36 | border-bottom-left-radius: 3.6px 2px;
37 | }
38 | .slider-swatch.first {
39 | border-radius: 2px 0px 0px 2px;
40 | }
41 | .slider-swatch.last {
42 | border-radius: 0px 2px 2px 0px;
43 | }
44 | `,
45 | ],
46 | changeDetection: ChangeDetectionStrategy.OnPush,
47 | preserveWhitespaces: false,
48 | standalone: false,
49 | })
50 | export class SliderSwatchComponent implements OnChanges {
51 | @Input() hsl!: HSL;
52 | @Input() active!: boolean;
53 | @Input() offset!: number;
54 | @Input() first = false;
55 | @Input() last = false;
56 | @Output() onClick = new EventEmitter();
57 | background!: string;
58 |
59 | ngOnChanges() {
60 | this.background = `hsl(${this.hsl.h}, 50%, ${this.offset * 100}%)`;
61 | }
62 | handleClick($event) {
63 | this.onClick.emit({
64 | data: {
65 | h: this.hsl.h,
66 | s: 0.5,
67 | l: this.offset,
68 | source: 'hsl',
69 | },
70 | $event,
71 | });
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/lib/slider/slider-swatches.component.ts:
--------------------------------------------------------------------------------
1 | import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
2 |
3 | import { HSL } from 'ngx-color';
4 |
5 | @Component({
6 | selector: 'color-slider-swatches',
7 | template: `
8 |
9 |
10 |
17 |
18 |
19 |
25 |
26 |
27 |
33 |
34 |
35 |
41 |
42 |
43 |
50 |
51 |
52 | `,
53 | styles: [
54 | `
55 | .slider-swatches {
56 | margin-top: 20px;
57 | }
58 | .slider-swatch-wrap {
59 | box-sizing: border-box;
60 | width: 20%;
61 | padding-right: 1px;
62 | float: left;
63 | }
64 | `,
65 | ],
66 | changeDetection: ChangeDetectionStrategy.OnPush,
67 | preserveWhitespaces: false,
68 | standalone: false,
69 | })
70 | export class SliderSwatchesComponent {
71 | @Input() hsl!: HSL;
72 | @Output() onClick = new EventEmitter();
73 | @Output() onSwatchHover = new EventEmitter();
74 |
75 | active(l: number, s: number) {
76 | return Math.round(this.hsl.l * 100) / 100 === l && Math.round(this.hsl.s * 100) / 100 === s;
77 | }
78 | handleClick({ data, $event }) {
79 | this.onClick.emit({ data, $event });
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/src/lib/slider/slider.component.ts:
--------------------------------------------------------------------------------
1 | import { CommonModule } from '@angular/common';
2 | import { ChangeDetectionStrategy, Component, forwardRef, Input, NgModule } from '@angular/core';
3 |
4 | import { ColorWrap, HueModule, SwatchModule } from 'ngx-color';
5 | import { SliderSwatchComponent } from './slider-swatch.component';
6 | import { SliderSwatchesComponent } from './slider-swatches.component';
7 | import { NG_VALUE_ACCESSOR } from '@angular/forms';
8 |
9 | @Component({
10 | selector: 'color-slider',
11 | template: `
12 |
13 |
14 |
20 |
21 |
22 |
26 |
27 |
28 | `,
29 | styles: [
30 | `
31 | .slider-hue {
32 | height: 12px;
33 | position: relative;
34 | }
35 | `,
36 | ],
37 | changeDetection: ChangeDetectionStrategy.OnPush,
38 | preserveWhitespaces: false,
39 | providers: [
40 | {
41 | provide: NG_VALUE_ACCESSOR,
42 | useExisting: forwardRef(() => SliderComponent),
43 | multi: true,
44 | },
45 | {
46 | provide: ColorWrap,
47 | useExisting: forwardRef(() => SliderComponent),
48 | },
49 | ],
50 | standalone: false,
51 | })
52 | export class SliderComponent extends ColorWrap {
53 | @Input()
54 | pointer: Record = {
55 | width: '14px',
56 | height: '14px',
57 | borderRadius: '6px',
58 | transform: 'translate(-7px, -2px)',
59 | backgroundColor: 'rgb(248, 248, 248)',
60 | boxShadow: '0 1px 4px 0 rgba(0, 0, 0, 0.37)',
61 | };
62 | @Input() radius = 2;
63 |
64 | constructor() {
65 | super();
66 | }
67 |
68 | handlePickerChange({ data, $event }) {
69 | this.handleChange(data, $event);
70 | }
71 | }
72 |
73 | @NgModule({
74 | declarations: [SliderComponent, SliderSwatchComponent, SliderSwatchesComponent],
75 | exports: [SliderComponent, SliderSwatchComponent, SliderSwatchesComponent],
76 | imports: [CommonModule, HueModule, SwatchModule],
77 | })
78 | export class ColorSliderModule {}
79 |
--------------------------------------------------------------------------------
/src/lib/slider/slider.spec.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 | import { TestBed, waitForAsync } from '@angular/core/testing';
3 | import { By } from '@angular/platform-browser';
4 |
5 | import { ColorSliderModule } from './slider.component';
6 |
7 | describe('SliderComponent', () => {
8 | beforeEach(waitForAsync(() => {
9 | TestBed.configureTestingModule({
10 | declarations: [SliderTestApp],
11 | imports: [ColorSliderModule],
12 | }).compileComponents();
13 | }));
14 | it(`should apply className to root element`, () => {
15 | const fixture = TestBed.createComponent(SliderTestApp);
16 | fixture.detectChanges();
17 | const divDebugElement = fixture.debugElement.query(By.css('.slider-picker'));
18 | expect(divDebugElement.nativeElement.classList.contains('classy')).toBe(true);
19 | });
20 | });
21 |
22 | @Component({
23 | selector: 'test-app',
24 | template: ` `,
25 | standalone: false,
26 | })
27 | class SliderTestApp {
28 | className = 'classy';
29 | }
30 |
--------------------------------------------------------------------------------
/src/lib/swatch.component.ts:
--------------------------------------------------------------------------------
1 | import { CommonModule } from '@angular/common';
2 | import {
3 | ChangeDetectionStrategy,
4 | Component,
5 | EventEmitter,
6 | Input,
7 | NgModule,
8 | OnInit,
9 | Output,
10 | } from '@angular/core';
11 |
12 | import { CheckboardModule } from './checkboard.component';
13 |
14 | @Component({
15 | selector: 'color-swatch',
16 | template: `
17 |
28 |
29 | @if (color === 'transparent') {
30 |
31 | }
32 |
33 | `,
34 | styles: [
35 | `
36 | .swatch {
37 | outline: none;
38 | height: 100%;
39 | width: 100%;
40 | cursor: pointer;
41 | position: relative;
42 | }
43 | `,
44 | ],
45 | changeDetection: ChangeDetectionStrategy.OnPush,
46 | standalone: false,
47 | })
48 | export class SwatchComponent implements OnInit {
49 | @Input() color!: string;
50 | @Input() style: Record = {};
51 | @Input() focusStyle: Record = {};
52 | @Input() focus!: boolean;
53 | @Output() onClick = new EventEmitter();
54 | @Output() onHover = new EventEmitter();
55 | divStyles: Record = {};
56 | focusStyles: Record = {};
57 | inFocus = false;
58 |
59 | ngOnInit() {
60 | this.divStyles = {
61 | background: this.color as string,
62 | ...this.style,
63 | };
64 | }
65 | currentStyles() {
66 | this.focusStyles = {
67 | ...this.divStyles,
68 | ...this.focusStyle,
69 | };
70 | return this.focus || this.inFocus ? this.focusStyles : this.divStyles;
71 | }
72 | handleFocusOut() {
73 | this.inFocus = false;
74 | }
75 | handleFocus() {
76 | this.inFocus = true;
77 | }
78 | handleHover(hex: string, $event) {
79 | this.onHover.emit({ hex, $event });
80 | }
81 | handleClick(hex: string, $event) {
82 | this.onClick.emit({ hex, $event });
83 | }
84 | }
85 |
86 | @NgModule({
87 | declarations: [SwatchComponent],
88 | exports: [SwatchComponent],
89 | imports: [CommonModule, CheckboardModule],
90 | })
91 | export class SwatchModule {}
92 |
--------------------------------------------------------------------------------
/src/lib/swatches/ng-package.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "../../../node_modules/ng-packagr/ng-package.schema.json",
3 | "lib": {
4 | "entryFile": "public_api.ts"
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/src/lib/swatches/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ngx-color/swatches",
3 | "author": "scttcper",
4 | "repository": {
5 | "type": "git",
6 | "url": "git+https://github.com/scttcper/ngx-color.git"
7 | },
8 | "dependencies": {
9 | "material-colors": "^1.2.6"
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/lib/swatches/public_api.ts:
--------------------------------------------------------------------------------
1 | export { SwatchesColorComponent } from './swatches-color.component';
2 | export { SwatchesGroupComponent } from './swatches-group.component';
3 | export { ColorSwatchesModule, SwatchesComponent } from './swatches.component';
4 |
--------------------------------------------------------------------------------
/src/lib/swatches/swatches-color.component.ts:
--------------------------------------------------------------------------------
1 | import {
2 | ChangeDetectionStrategy,
3 | Component,
4 | EventEmitter,
5 | Input,
6 | OnInit,
7 | Output,
8 | } from '@angular/core';
9 |
10 | import { getContrastingColor } from 'ngx-color';
11 |
12 | @Component({
13 | selector: 'color-swatches-color',
14 | template: `
15 |
25 | @if (active) {
26 |
35 | }
36 |
37 | `,
38 | styles: [
39 | `
40 | .swatches-group {
41 | padding-bottom: 10px;
42 | width: 40px;
43 | float: left;
44 | margin-right: 10px;
45 | }
46 | .swatch-check {
47 | display: flex;
48 | margin-left: 8px;
49 | }
50 | `,
51 | ],
52 | changeDetection: ChangeDetectionStrategy.OnPush,
53 | preserveWhitespaces: false,
54 | standalone: false,
55 | })
56 | export class SwatchesColorComponent implements OnInit {
57 | @Input() color!: string;
58 | @Input() first = false;
59 | @Input() last = false;
60 | @Input() active!: boolean;
61 | @Output() onClick = new EventEmitter();
62 | @Output() onSwatchHover = new EventEmitter();
63 | getContrastingColor = getContrastingColor;
64 | colorStyle: Record = {
65 | width: '40px',
66 | height: '24px',
67 | cursor: 'pointer',
68 | marginBottom: '1px',
69 | };
70 | focusStyle: Record = {};
71 |
72 | ngOnInit() {
73 | this.colorStyle.background = this.color;
74 | this.focusStyle.boxShadow = `0 0 4px ${this.color}`;
75 | if (this.first) {
76 | this.colorStyle.borderRadius = '2px 2px 0 0';
77 | }
78 | if (this.last) {
79 | this.colorStyle.borderRadius = '0 0 2px 2px';
80 | }
81 | if (this.color === '#FFFFFF') {
82 | this.colorStyle.boxShadow = 'inset 0 0 0 1px #ddd';
83 | }
84 | }
85 | handleClick($event) {
86 | this.onClick.emit({
87 | data: {
88 | hex: this.color,
89 | source: 'hex',
90 | },
91 | $event,
92 | });
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/src/lib/swatches/swatches-group.component.ts:
--------------------------------------------------------------------------------
1 | import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'color-swatches-group',
5 | template: `
6 |
7 | @for (color of group; track color; let idx = $index) {
8 |
15 |
16 | }
17 |
18 | `,
19 | styles: [
20 | `
21 | .swatches-group {
22 | padding-bottom: 10px;
23 | width: 40px;
24 | float: left;
25 | margin-right: 10px;
26 | }
27 | `,
28 | ],
29 | changeDetection: ChangeDetectionStrategy.OnPush,
30 | preserveWhitespaces: false,
31 | standalone: false,
32 | })
33 | export class SwatchesGroupComponent {
34 | @Input() group!: string[];
35 | @Input() active!: string;
36 | @Output() onClick = new EventEmitter();
37 | @Output() onSwatchHover = new EventEmitter();
38 | }
39 |
--------------------------------------------------------------------------------
/src/lib/swatches/swatches.component.ts:
--------------------------------------------------------------------------------
1 | import { CommonModule } from '@angular/common';
2 | import { ChangeDetectionStrategy, Component, forwardRef, Input, NgModule } from '@angular/core';
3 | import {
4 | amber,
5 | blue,
6 | blueGrey,
7 | brown,
8 | cyan,
9 | deepOrange,
10 | deepPurple,
11 | green,
12 | indigo,
13 | lightBlue,
14 | lightGreen,
15 | lime,
16 | orange,
17 | pink,
18 | purple,
19 | red,
20 | teal,
21 | yellow,
22 | } from 'material-colors';
23 |
24 | import { ColorWrap, RaisedModule, SwatchModule, zDepth } from 'ngx-color';
25 | import { SwatchesColorComponent } from './swatches-color.component';
26 | import { SwatchesGroupComponent } from './swatches-group.component';
27 | import { NG_VALUE_ACCESSOR } from '@angular/forms';
28 |
29 | @Component({
30 | selector: 'color-swatches',
31 | template: `
32 |
37 |
38 |
39 |
40 | @for (group of colors; track group) {
41 |
46 | }
47 |
48 |
49 |
50 |
51 | `,
52 | styles: [
53 | `
54 | .swatches-overflow {
55 | overflow-y: scroll;
56 | }
57 | .swatches-overflow {
58 | padding: 16px 0 6px 16px;
59 | }
60 | `,
61 | ],
62 | changeDetection: ChangeDetectionStrategy.OnPush,
63 | preserveWhitespaces: false,
64 | providers: [
65 | {
66 | provide: NG_VALUE_ACCESSOR,
67 | useExisting: forwardRef(() => SwatchesComponent),
68 | multi: true,
69 | },
70 | {
71 | provide: ColorWrap,
72 | useExisting: forwardRef(() => SwatchesComponent),
73 | },
74 | ],
75 | standalone: false,
76 | })
77 | export class SwatchesComponent extends ColorWrap {
78 | /** Pixel value for picker width */
79 | @Input() width: string | number = 320;
80 | /** Color squares to display */
81 | @Input() height: string | number = 240;
82 | /** An array of color groups, each with an array of colors */
83 | @Input()
84 | colors: string[][] = [
85 | [red['900'], red['700'], red['500'], red['300'], red['100']],
86 | [pink['900'], pink['700'], pink['500'], pink['300'], pink['100']],
87 | [purple['900'], purple['700'], purple['500'], purple['300'], purple['100']],
88 | [deepPurple['900'], deepPurple['700'], deepPurple['500'], deepPurple['300'], deepPurple['100']],
89 | [indigo['900'], indigo['700'], indigo['500'], indigo['300'], indigo['100']],
90 | [blue['900'], blue['700'], blue['500'], blue['300'], blue['100']],
91 | [lightBlue['900'], lightBlue['700'], lightBlue['500'], lightBlue['300'], lightBlue['100']],
92 | [cyan['900'], cyan['700'], cyan['500'], cyan['300'], cyan['100']],
93 | [teal['900'], teal['700'], teal['500'], teal['300'], teal['100']],
94 | ['#194D33', green['700'], green['500'], green['300'], green['100']],
95 | [lightGreen['900'], lightGreen['700'], lightGreen['500'], lightGreen['300'], lightGreen['100']],
96 | [lime['900'], lime['700'], lime['500'], lime['300'], lime['100']],
97 | [yellow['900'], yellow['700'], yellow['500'], yellow['300'], yellow['100']],
98 | [amber['900'], amber['700'], amber['500'], amber['300'], amber['100']],
99 | [orange['900'], orange['700'], orange['500'], orange['300'], orange['100']],
100 | [deepOrange['900'], deepOrange['700'], deepOrange['500'], deepOrange['300'], deepOrange['100']],
101 | [brown['900'], brown['700'], brown['500'], brown['300'], brown['100']],
102 | [blueGrey['900'], blueGrey['700'], blueGrey['500'], blueGrey['300'], blueGrey['100']],
103 | ['#000000', '#525252', '#969696', '#D9D9D9', '#FFFFFF'],
104 | ];
105 | @Input() zDepth: zDepth = 1;
106 | @Input() radius = 1;
107 | @Input() background = '#fff';
108 |
109 | constructor() {
110 | super();
111 | }
112 |
113 | handlePickerChange({ data, $event }) {
114 | this.handleChange(data, $event);
115 | }
116 | }
117 |
118 | @NgModule({
119 | declarations: [SwatchesComponent, SwatchesGroupComponent, SwatchesColorComponent],
120 | exports: [SwatchesComponent, SwatchesGroupComponent, SwatchesColorComponent],
121 | imports: [CommonModule, SwatchModule, RaisedModule],
122 | })
123 | export class ColorSwatchesModule {}
124 |
--------------------------------------------------------------------------------
/src/lib/swatches/swatches.spec.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 | import { TestBed, waitForAsync } from '@angular/core/testing';
3 |
4 | import { ColorSwatchesModule } from './swatches.component';
5 |
6 | describe('SwatchesComponent', () => {
7 | beforeEach(waitForAsync(() => {
8 | TestBed.configureTestingModule({
9 | declarations: [SwatchTestApp],
10 | imports: [ColorSwatchesModule],
11 | }).compileComponents();
12 | }));
13 | it(`should apply className to root element`, () => {
14 | const fixture = TestBed.createComponent(SwatchTestApp);
15 | fixture.detectChanges();
16 | const compiled = fixture.nativeElement;
17 | expect(compiled.querySelector('.swatches-picker').className).toContain('classy');
18 | });
19 | });
20 |
21 | @Component({
22 | selector: 'test-app',
23 | template: ``,
24 | standalone: false,
25 | })
26 | class SwatchTestApp {
27 | className = 'classy';
28 | }
29 |
--------------------------------------------------------------------------------
/src/lib/twitter/ng-package.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "../../../node_modules/ng-packagr/ng-package.schema.json",
3 | "lib": {
4 | "entryFile": "public_api.ts"
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/src/lib/twitter/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ngx-color/twitter",
3 | "author": "scttcper",
4 | "repository": {
5 | "type": "git",
6 | "url": "git+https://github.com/scttcper/ngx-color.git"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/src/lib/twitter/public_api.ts:
--------------------------------------------------------------------------------
1 | export { ColorTwitterModule, TwitterComponent } from './twitter.component';
2 |
--------------------------------------------------------------------------------
/src/lib/twitter/twitter.component.ts:
--------------------------------------------------------------------------------
1 | import { CommonModule } from '@angular/common';
2 | import { ChangeDetectionStrategy, Component, forwardRef, Input, NgModule } from '@angular/core';
3 |
4 | import { ColorWrap, EditableInputModule, isValidHex, SwatchModule } from 'ngx-color';
5 | import { NG_VALUE_ACCESSOR } from '@angular/forms';
6 |
7 | @Component({
8 | selector: 'color-twitter',
9 | template: `
10 |
37 | `,
38 | styles: [
39 | `
40 | .twitter-picker {
41 | background: rgb(255, 255, 255);
42 | border: 0px solid rgba(0, 0, 0, 0.25);
43 | box-shadow: rgba(0, 0, 0, 0.25) 0px 1px 4px;
44 | border-radius: 4px;
45 | position: relative;
46 | box-sizing: border-box;
47 | }
48 | .triangleShadow {
49 | width: 0px;
50 | height: 0px;
51 | border-style: solid;
52 | border-width: 0px 9px 10px;
53 | border-color: transparent transparent rgba(0, 0, 0, 0.1);
54 | position: absolute;
55 | }
56 | .triangle {
57 | width: 0px;
58 | height: 0px;
59 | border-style: solid;
60 | border-width: 0px 9px 10px;
61 | border-color: transparent transparent rgb(255, 255, 255);
62 | position: absolute;
63 | }
64 | .hide-triangle > .triangle {
65 | display: none;
66 | }
67 | .hide-triangle > .triangleShadow {
68 | display: none;
69 | }
70 | .top-left-triangle > .triangle {
71 | top: -10px;
72 | left: 12px;
73 | }
74 | .top-left-triangle > .triangleShadow {
75 | top: -11px;
76 | left: 12px;
77 | }
78 | .top-right-triangle > .triangle {
79 | top: -10px;
80 | right: 12px;
81 | }
82 | .top-right-triangle > .triangleShadow {
83 | top: -11px;
84 | right: 12px;
85 | }
86 | .twitter-body {
87 | padding: 15px 9px 9px 15px;
88 | }
89 | .twitter-swatch {
90 | width: 30px;
91 | height: 30px;
92 | display: inline-block;
93 | margin: 0 6px 0 0;
94 | }
95 | .twitter-hash {
96 | background: rgb(240, 240, 240);
97 | height: 30px;
98 | width: 30px;
99 | border-radius: 4px 0px 0px 4px;
100 | color: rgb(152, 161, 164);
101 | margin-left: -3px;
102 | display: inline-block;
103 | }
104 | .twitter-hash > div {
105 | position: absolute;
106 | align-items: center;
107 | justify-content: center;
108 | height: 30px;
109 | width: 30px;
110 | display: flex;
111 | }
112 | .twitter-input {
113 | position: relative;
114 | display: inline-block;
115 | margin-top: -6px;
116 | font-size: 10px;
117 | height: 27px;
118 | padding: 0;
119 | position: relative;
120 | top: 6px;
121 | vertical-align: top;
122 | width: 108px;
123 | margin-left: -4px;
124 | }
125 | `,
126 | ],
127 | changeDetection: ChangeDetectionStrategy.OnPush,
128 | preserveWhitespaces: false,
129 | providers: [
130 | {
131 | provide: NG_VALUE_ACCESSOR,
132 | useExisting: forwardRef(() => TwitterComponent),
133 | multi: true,
134 | },
135 | {
136 | provide: ColorWrap,
137 | useExisting: forwardRef(() => TwitterComponent),
138 | },
139 | ],
140 | standalone: false,
141 | })
142 | export class TwitterComponent extends ColorWrap {
143 | /** Pixel value for picker width */
144 | @Input() width: string | number = 276;
145 | /** Color squares to display */
146 | @Input() colors = [
147 | '#FF6900',
148 | '#FCB900',
149 | '#7BDCB5',
150 | '#00D084',
151 | '#8ED1FC',
152 | '#0693E3',
153 | '#ABB8C3',
154 | '#EB144C',
155 | '#F78DA7',
156 | '#9900EF',
157 | ];
158 | @Input() triangle: 'hide' | 'top-left' | 'top-right' | 'bottom-right' = 'top-left';
159 |
160 | swatchStyle: { [key: string]: string } = {
161 | width: '30px',
162 | height: '30px',
163 | borderRadius: '4px',
164 | fontSize: '0',
165 | };
166 | input: { [key: string]: string } = {
167 | borderRadius: '4px',
168 | borderBottomLeftRadius: '0',
169 | borderTopLeftRadius: '0',
170 | border: '1px solid #e6ecf0',
171 | boxSizing: 'border-box',
172 | display: 'inline',
173 | fontSize: '14px',
174 | height: '30px',
175 | padding: '0',
176 | paddingLeft: '6px',
177 | width: '100%',
178 | color: '#657786',
179 | };
180 | disableAlpha = true;
181 |
182 | constructor() {
183 | super();
184 | }
185 |
186 | focus(color: string) {
187 | return { boxShadow: `0 0 4px ${color}` };
188 | }
189 |
190 | handleBlockChange({ hex, $event }: any) {
191 | if (isValidHex(hex)) {
192 | // this.hex = hex;
193 | this.handleChange({ hex, source: 'hex' }, $event);
194 | }
195 | }
196 |
197 | handleValueChange({ data, $event }: any) {
198 | this.handleBlockChange({ hex: data, $event });
199 | }
200 | }
201 |
202 | @NgModule({
203 | declarations: [TwitterComponent],
204 | exports: [TwitterComponent],
205 | imports: [CommonModule, SwatchModule, EditableInputModule],
206 | })
207 | export class ColorTwitterModule {}
208 |
--------------------------------------------------------------------------------
/src/lib/twitter/twitter.spec.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 | import { TestBed, waitForAsync } from '@angular/core/testing';
3 | import { By } from '@angular/platform-browser';
4 |
5 | import { ColorTwitterModule } from './twitter.component';
6 |
7 | describe('TwitterComponent', () => {
8 | beforeEach(waitForAsync(() => {
9 | TestBed.configureTestingModule({
10 | declarations: [TwitterTestApp],
11 | imports: [ColorTwitterModule],
12 | }).compileComponents();
13 | }));
14 | it(`should apply className to root element`, () => {
15 | const fixture = TestBed.createComponent(TwitterTestApp);
16 | fixture.detectChanges();
17 | const divDebugElement = fixture.debugElement.query(By.css('.twitter-picker'));
18 | expect(divDebugElement.nativeElement.classList.contains('classy')).toBe(true);
19 | });
20 | });
21 |
22 | @Component({
23 | selector: 'test-app',
24 | template: ``,
25 | standalone: false,
26 | })
27 | class TwitterTestApp {
28 | className = 'classy';
29 | }
30 |
--------------------------------------------------------------------------------
/src/main.ts:
--------------------------------------------------------------------------------
1 | import { enableProdMode } from '@angular/core';
2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
3 |
4 | import { AppModule } from './app/app.module';
5 | import { environment } from './environments/environment';
6 |
7 | if (environment.production) {
8 | enableProdMode();
9 | }
10 |
11 | platformBrowserDynamic()
12 | .bootstrapModule(AppModule)
13 | .catch(err => console.log(err));
14 |
--------------------------------------------------------------------------------
/src/polyfills.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * This file includes polyfills needed by Angular and is loaded before the app.
3 | * You can add your own extra polyfills to this file.
4 | *
5 | * This file is divided into 2 sections:
6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main
8 | * file.
9 | *
10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that
11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
13 | *
14 | * Learn more in https://angular.io/guide/browser-support
15 | */
16 |
17 | /***************************************************************************************************
18 | * BROWSER POLYFILLS
19 | */
20 |
21 | /**
22 | * By default, zone.js will patch all possible macroTask and DomEvents
23 | * user can disable parts of macroTask/DomEvents patch by setting following flags
24 | * because those flags need to be set before `zone.js` being loaded, and webpack
25 | * will put import in the top of bundle, so user need to create a separate file
26 | * in this directory (for example: zone-flags.ts), and put the following flags
27 | * into that file, and then add the following code before importing zone.js.
28 | * import './zone-flags';
29 | *
30 | * The flags allowed in zone-flags.ts are listed here.
31 | *
32 | * The following flags will work for all browsers.
33 | *
34 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
35 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
36 | * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
37 | *
38 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
39 | * with the following flag, it will bypass `zone.js` patch for IE/Edge
40 | *
41 | * (window as any).__Zone_enable_cross_context_check = true;
42 | *
43 | */
44 |
45 | /***************************************************************************************************
46 | * Zone JS is required by default for Angular itself.
47 | */
48 | import 'zone.js'; // Included with Angular CLI.
49 |
50 | /***************************************************************************************************
51 | * APPLICATION IMPORTS
52 | */
53 |
--------------------------------------------------------------------------------
/src/styles.css:
--------------------------------------------------------------------------------
1 | @import 'bootstrap/dist/css/bootstrap.css';
2 |
3 | html {
4 | touch-action: manipulation;
5 | }
6 | html,
7 | body {
8 | background: #eee;
9 | }
10 | .home {
11 | font-family: 'Roboto', sans-serif;
12 | }
13 |
14 | /* fix weird issue on demo with background overlaying */
15 | .col-md-6,
16 | .col-md-3,
17 | .col-md-4,
18 | .col-md-8 {
19 | z-index: 3;
20 | }
21 |
22 | .container {
23 | max-width: 800px;
24 | }
25 |
--------------------------------------------------------------------------------
/src/test.ts:
--------------------------------------------------------------------------------
1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files
2 | import 'zone.js/testing';
3 | import { getTestBed } from '@angular/core/testing';
4 | import {
5 | BrowserDynamicTestingModule,
6 | platformBrowserDynamicTesting,
7 | } from '@angular/platform-browser-dynamic/testing';
8 |
9 | // First, initialize the Angular testing environment.
10 | getTestBed().initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting(), {
11 | teardown: { destroyAfterEach: false },
12 | });
13 |
--------------------------------------------------------------------------------
/src/types.d.ts:
--------------------------------------------------------------------------------
1 | declare module 'material-colors';
2 |
--------------------------------------------------------------------------------
/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "extends": "./tsconfig.json",
4 | "compilerOptions": {
5 | "outDir": "./out-tsc/app",
6 | "types": []
7 | },
8 | "files": ["src/main.ts", "src/polyfills.ts"],
9 | "include": ["src/**/*.d.ts"]
10 | }
11 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "compileOnSave": false,
4 | "compilerOptions": {
5 | "baseUrl": "./",
6 | "outDir": "./dist/out-tsc",
7 | "forceConsistentCasingInFileNames": true,
8 | "esModuleInterop": true,
9 | "strict": true,
10 | "noImplicitAny": false,
11 | "noImplicitReturns": true,
12 | "noFallthroughCasesInSwitch": true,
13 | "sourceMap": true,
14 | "declaration": false,
15 | "experimentalDecorators": true,
16 | "moduleResolution": "bundler",
17 | "importHelpers": true,
18 | "target": "ES2022",
19 | "module": "ES2022",
20 | "lib": ["es2022", "dom"],
21 | "skipLibCheck": true,
22 | "paths": {
23 | "ngx-color": ["./src/lib/public_api"]
24 | },
25 | "useDefineForClassFields": false
26 | },
27 | "angularCompilerOptions": {
28 | "strictInjectionParameters": true,
29 | "strictTemplates": true,
30 | "enableI18nLegacyMessageIdFormat": false,
31 | "strictInputAccessModifiers": true
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "extends": "./tsconfig.json",
4 | "compilerOptions": {
5 | "outDir": "./out-tsc/spec",
6 | "types": ["jasmine"]
7 | },
8 | "files": ["src/test.ts", "src/polyfills.ts"],
9 | "include": ["src/**/*.spec.ts", "src/**/*.d.ts"]
10 | }
11 |
--------------------------------------------------------------------------------
/vercel.json:
--------------------------------------------------------------------------------
1 | {
2 | "github": {
3 | "silent": true
4 | }
5 | }
6 |
--------------------------------------------------------------------------------