├── .jshintrc ├── .editorconfig ├── README.md ├── package.json ├── bower.json ├── LICENSE.txt ├── angular-color-picker.css └── angular-color-picker.js /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | } 3 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | trim_trailing_whitespace = true 6 | insert_final_newline = true 7 | indent_style = space 8 | indent_size = 4 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # angular-color-picker 2 | 3 | angular-color-picker is a lightweight color picker that acts like a custom input element through regular ngModel bindings. It is: 4 | 5 | * Pure CSS – does not use any images 6 | * Pure Angular – does not rely on any other libraries 7 | * Compatible with IE8+ 8 | 9 | [Demo and documentation](http://myplanet.github.io/angular-color-picker/) 10 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-color-picker", 3 | "version": "1.0.11", 4 | "description": "Lightweight color picker for Angular", 5 | "main": "angular-color-picker.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "git://github.com/myplanet/angular-color-picker.git" 9 | }, 10 | "keywords": [ 11 | "angular", 12 | "color", 13 | "hsv" 14 | ], 15 | "author": "Ates Goral ", 16 | "license": "MIT", 17 | "bugs": { 18 | "url": "https://github.com/myplanet/angular-color-picker/issues" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-color-picker", 3 | "main": [ 4 | "angular-color-picker.js", 5 | "angular-color-picker.css" 6 | ], 7 | "homepage": "https://github.com/myplanet/angular-color-picker", 8 | "description": "Lightweight color picker for Angular", 9 | "authors": [ 10 | "Ates Goral " 11 | ], 12 | "moduleType": [ 13 | "amd", 14 | "globals", 15 | "node" 16 | ], 17 | "keywords": [ 18 | "angular", 19 | "color", 20 | "hsv" 21 | ], 22 | "license": "MIT", 23 | "ignore": [ 24 | "**/.*" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014-2015 Myplanet 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /angular-color-picker.css: -------------------------------------------------------------------------------- 1 | .angular-color-picker { 2 | background: #fff; 3 | border: 1px solid #ddd; 4 | padding: 20px; 5 | display: inline-block; 6 | } 7 | .angular-color-picker > ._variations { 8 | border: 1px solid #808080; 9 | width: 200px; 10 | height: 200px; 11 | float: left; 12 | transition: background-color 250ms; 13 | } 14 | .angular-color-picker > ._variations > ._whites { 15 | width: 200px; 16 | height: 200px; 17 | background: -webkit-linear-gradient(left, #fff 0, transparent 100%); 18 | background: -moz-linear-gradient(left, #fff 0, transparent 100%); 19 | background: -ms-linear-gradient(left, #fff 0, transparent 100%); 20 | background: linear-gradient(to right, #fff 0, transparent 100%); 21 | filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#00ffffff', GradientType='1')"; 22 | } 23 | .angular-color-picker > ._variations > ._whites > ._blacks { 24 | width: 200px; 25 | height: 200px; 26 | background: -webkit-linear-gradient(top, transparent 0, #000 100%); 27 | background: -moz-linear-gradient(top, transparent 0, #000 100%); 28 | background: -ms-linear-gradient(top, transparent 0, #000 100%); 29 | background: linear-gradient(to bottom, transparent 0, #000 100%); 30 | filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#ff000000')"; 31 | position: relative; 32 | } 33 | .angular-color-picker > ._variations > ._whites > ._blacks > ._cursor { 34 | position: absolute; 35 | display: inline-block; 36 | width: 8px; 37 | height: 8px; 38 | border: 1px solid #fff; 39 | border-right-color: #000; 40 | border-left-color: #000; 41 | border-radius: 5px; 42 | } 43 | .angular-color-picker > ._variations > ._whites > ._blacks > ._mouse-trap { 44 | position: relative; 45 | z-index: 1; 46 | width: 200px; 47 | height: 200px; 48 | } 49 | .angular-color-picker > ._hues { 50 | border: 1px solid #808080; 51 | position: relative; 52 | margin-left: 210px; 53 | height: 200px; 54 | width: 30px; 55 | background: -webkit-linear-gradient(top, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%); 56 | background: -moz-linear-gradient(top, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%); 57 | background: -ms-linear-gradient(top, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%); 58 | background: linear-gradient(to bottom, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%); 59 | } 60 | .angular-color-picker > ._hues > ._cursor { 61 | position: absolute; 62 | left: -7px; 63 | width: 33px; 64 | height: 0; 65 | border: 5px solid transparent; 66 | border-left-color: #808080; 67 | border-right-color: #808080; 68 | } 69 | .angular-color-picker > ._hues > ._mouse-trap { 70 | position: absolute; 71 | z-index: 1; 72 | top: 0; 73 | left: 0; 74 | width: 30px; 75 | height: 200px; 76 | } 77 | /* Heavily based on: http://jsfiddle.net/bgrins/Whc6Z/ */ 78 | .angular-color-picker > ._hues > ._ie-1 { 79 | height: 17%; 80 | filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0000', endColorstr='#ffff00')"; 81 | } 82 | .angular-color-picker > ._hues > ._ie-2 { 83 | height: 16%; 84 | filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffff00', endColorstr='#00ff00')"; 85 | } 86 | .angular-color-picker > ._hues > ._ie-3 { 87 | height: 17%; 88 | filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr='#00ff00', endColorstr='#00ffff')"; 89 | } 90 | .angular-color-picker > ._hues > ._ie-4 { 91 | height: 17%; 92 | filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr='#00ffff', endColorstr='#0000ff')"; 93 | } 94 | .angular-color-picker > ._hues > ._ie-5 { 95 | height: 16%; 96 | filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr='#0000ff', endColorstr='#ff00ff')"; 97 | } 98 | .angular-color-picker > ._hues > ._ie-6 { 99 | height: 17%; 100 | filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff00ff', endColorstr='#ff0000')"; 101 | } 102 | -------------------------------------------------------------------------------- /angular-color-picker.js: -------------------------------------------------------------------------------- 1 | (function (root, factory) { 2 | if (typeof define === 'function' && define.amd) { 3 | define([ 'module', 'angular' ], function (module, angular) { 4 | module.exports = factory(angular); 5 | }); 6 | } else if (typeof module === 'object') { 7 | module.exports = factory(require('angular')); 8 | } else { 9 | if (!root.mp) { 10 | root.mp = {}; 11 | } 12 | 13 | root.mp.colorPicker = factory(root.angular); 14 | } 15 | }(this, function (angular) { 16 | 'use strict'; 17 | 18 | function hsvToHexRgb(h, s, v) { 19 | if (typeof h === 'object') { 20 | s = h.s; 21 | v = h.v; 22 | h = h.h; 23 | } 24 | 25 | var i = Math.floor(h * 6), 26 | f = h * 6 - i, 27 | p = v * (1 - s), 28 | q = v * (1 - f * s), 29 | t = v * (1 - (1 - f) * s); 30 | 31 | var r, g, b; 32 | 33 | switch (i % 6) { 34 | case 0: 35 | r = v; 36 | g = t; 37 | b = p; 38 | break; 39 | case 1: 40 | r = q; 41 | g = v; 42 | b = p; 43 | break; 44 | case 2: 45 | r = p; 46 | g = v; 47 | b = t; 48 | break; 49 | case 3: 50 | r = p; 51 | g = q; 52 | b = v; 53 | break; 54 | case 4: 55 | r = t; 56 | g = p; 57 | b = v; 58 | break; 59 | case 5: 60 | r = v; 61 | g = p; 62 | b = q; 63 | break; 64 | } 65 | 66 | r = Math.floor(r * 255) + 256; 67 | g = Math.floor(g * 255) + 256; 68 | b = Math.floor(b * 255) + 256; 69 | 70 | return '#' 71 | + r.toString(16).slice(1) 72 | + g.toString(16).slice(1) 73 | + b.toString(16).slice(1); 74 | } 75 | 76 | /** 77 | * Heavily based on: 78 | * http://stackoverflow.com/a/8023734/23501 79 | */ 80 | function hexRgbToHsv(hexRgb) { 81 | var tokens = /^#(..)(..)(..)$/.exec(hexRgb); 82 | 83 | if (tokens) { 84 | var rgb = tokens.slice(1).map(function (hex) { 85 | return parseInt(hex, 16) / 255; // Normalize to 1 86 | }); 87 | 88 | var r = rgb[0], 89 | g = rgb[1], 90 | b = rgb[2], 91 | h, s, 92 | v = Math.max(r, g, b), 93 | diff = v - Math.min(r, g, b), 94 | diffc = function (c) { 95 | return (v - c) / 6 / diff + 1 / 2; 96 | }; 97 | 98 | if (diff === 0) { 99 | h = s = 0; 100 | } else { 101 | s = diff / v; 102 | 103 | var rr = diffc(r), 104 | gg = diffc(g), 105 | bb = diffc(b); 106 | 107 | if (r === v) { 108 | h = bb - gg; 109 | } else if (g === v) { 110 | h = (1 / 3) + rr - bb; 111 | } else if (b === v) { 112 | h = (2 / 3) + gg - rr; 113 | } 114 | 115 | if (h < 0) { 116 | h += 1; 117 | } else if (h > 1) { 118 | h -= 1; 119 | } 120 | } 121 | 122 | return { 123 | h: h, 124 | s: s, 125 | v: v 126 | }; 127 | } 128 | } 129 | 130 | return angular.module('mp.colorPicker', []).directive('colorPicker', [ '$window', function ($window) { 131 | // Introduce custom elements for IE8 132 | $window.document.createElement('color-picker'); 133 | 134 | var tmpl = '' 135 | + '
' 136 | + '
' 137 | + '
' 138 | + '
' 139 | + '
' 140 | + '
' 141 | + '
' 142 | + '
' 143 | + '
' 144 | + '' 145 | + '
' 146 | + '
' 147 | + '
' 148 | + '
' 149 | + '
' 150 | + '
' 151 | + '
' 152 | + '
' 153 | + '
' 154 | + '
' 155 | + '
'; 156 | 157 | return { 158 | restrict: 'AE', 159 | template: tmpl, 160 | replace: true, 161 | require: '?ngModel', 162 | scope: { 163 | }, 164 | 165 | link: function ($scope, $element, $attributes, ngModel) { 166 | $scope.hsv = { h: 0, s: 0, v: 0 }; 167 | 168 | if (ngModel) { 169 | ngModel.$render = function () { 170 | if (/^#[0-9A-Fa-f]{6}$/.test(ngModel.$viewValue)) { 171 | $scope.color = ngModel.$viewValue; 172 | $scope.hsv = hexRgbToHsv($scope.color); 173 | $scope.colorCursor = { 174 | x: $scope.hsv.s * 200, 175 | y: (1 - $scope.hsv.v) * 200 176 | }; 177 | } else { 178 | $scope.color = null; 179 | $scope.hsv = { h: 0.5 }; 180 | $scope.colorCursor = null; 181 | } 182 | 183 | $scope.hueBackgroundColor = hsvToHexRgb($scope.hsv.h, 1, 1); 184 | $scope.hueCursor = $scope.hsv.h * 200; 185 | }; 186 | } 187 | 188 | var dragSubject, 189 | dragRect; 190 | 191 | function doDrag(x, y) { 192 | x = Math.max(Math.min(x, dragRect.width), 0); 193 | y = Math.max(Math.min(y, dragRect.height), 0); 194 | 195 | if (dragSubject === 'hue') { 196 | $scope.hueCursor = y; 197 | 198 | $scope.hsv.h = y / dragRect.height; 199 | 200 | $scope.hueBackgroundColor = hsvToHexRgb($scope.hsv.h, 1, 1); 201 | } else { 202 | $scope.colorCursor = { 203 | x: x, 204 | y: y 205 | }; 206 | 207 | $scope.hsv.s = x / dragRect.width; 208 | $scope.hsv.v = 1 - y / dragRect.height; 209 | } 210 | 211 | if (typeof $scope.hsv.s !== 'undefined') { 212 | $scope.color = hsvToHexRgb($scope.hsv); 213 | 214 | if (ngModel) { 215 | ngModel.$setViewValue($scope.color); 216 | } 217 | } 218 | } 219 | 220 | function onMouseMove(evt) { 221 | evt.preventDefault(); 222 | 223 | $scope.$apply(function () { 224 | doDrag(evt.clientX - dragRect.x, evt.clientY - dragRect.y); 225 | }); 226 | } 227 | function onTouchMove(evt) { 228 | evt.preventDefault(); 229 | 230 | $scope.$apply(function() { 231 | doDrag(evt.targetTouches[0].clientX - dragRect.x, evt.targetTouches[0].clientY - dragRect.y); 232 | }); 233 | } 234 | function onMouseUp() { 235 | angular.element($window) 236 | .off('mousemove', onMouseMove) 237 | .off('touchmove', onTouchMove); 238 | } 239 | 240 | $scope.startDrag = function (evt, subject) { 241 | var rect = evt.target.getBoundingClientRect(); 242 | 243 | dragSubject = subject; 244 | dragRect = { 245 | x: rect.left, 246 | y: rect.top, 247 | width: rect.right - rect.left, 248 | height: rect.bottom - rect.top 249 | }; 250 | 251 | doDrag(evt.offsetX || evt.layerX, evt.offsetY || evt.layerY); 252 | 253 | angular.element($window) 254 | .on('mousemove', onMouseMove) 255 | .on('touchmove', onTouchMove) 256 | .one('mouseup', onMouseUp) 257 | .one('touchend', onMouseUp); 258 | }; 259 | } 260 | }; 261 | }]); 262 | })); 263 | --------------------------------------------------------------------------------