File indexing completed on 2024-05-19 06:00:15
0001 'use strict'; 0002 0003 angular.module('colorpicker.module', []) 0004 .factory('Helper', function () { 0005 return { 0006 closestSlider: function (elem) { 0007 var matchesSelector = elem.matches || elem.webkitMatchesSelector || elem.mozMatchesSelector || elem.msMatchesSelector; 0008 if (matchesSelector.bind(elem)('I')) { 0009 return elem.parentNode; 0010 } 0011 return elem; 0012 }, 0013 getOffset: function (elem, fixedPosition) { 0014 var 0015 x = 0, 0016 y = 0, 0017 scrollX = 0, 0018 scrollY = 0; 0019 while (elem && !isNaN(elem.offsetLeft) && !isNaN(elem.offsetTop)) { 0020 x += elem.offsetLeft; 0021 y += elem.offsetTop; 0022 if (!fixedPosition && elem.tagName === 'BODY') { 0023 scrollX += document.documentElement.scrollLeft || elem.scrollLeft; 0024 scrollY += document.documentElement.scrollTop || elem.scrollTop; 0025 } else { 0026 scrollX += elem.scrollLeft; 0027 scrollY += elem.scrollTop; 0028 } 0029 elem = elem.offsetParent; 0030 } 0031 return { 0032 top: y, 0033 left: x, 0034 scrollX: scrollX, 0035 scrollY: scrollY 0036 }; 0037 }, 0038 // a set of RE's that can match strings and generate color tuples. https://github.com/jquery/jquery-color/ 0039 stringParsers: [ 0040 { 0041 re: /rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(?:,\s*(\d+(?:\.\d+)?)\s*)?\)/, 0042 parse: function (execResult) { 0043 return [ 0044 execResult[1], 0045 execResult[2], 0046 execResult[3], 0047 execResult[4] 0048 ]; 0049 } 0050 }, 0051 { 0052 re: /rgba?\(\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d+(?:\.\d+)?)\s*)?\)/, 0053 parse: function (execResult) { 0054 return [ 0055 2.55 * execResult[1], 0056 2.55 * execResult[2], 0057 2.55 * execResult[3], 0058 execResult[4] 0059 ]; 0060 } 0061 }, 0062 { 0063 re: /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/, 0064 parse: function (execResult) { 0065 return [ 0066 parseInt(execResult[1], 16), 0067 parseInt(execResult[2], 16), 0068 parseInt(execResult[3], 16) 0069 ]; 0070 } 0071 }, 0072 { 0073 re: /#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/, 0074 parse: function (execResult) { 0075 return [ 0076 parseInt(execResult[1] + execResult[1], 16), 0077 parseInt(execResult[2] + execResult[2], 16), 0078 parseInt(execResult[3] + execResult[3], 16) 0079 ]; 0080 } 0081 } 0082 ] 0083 }; 0084 }) 0085 .factory('Color', ['Helper', function (Helper) { 0086 return { 0087 value: { 0088 h: 1, 0089 s: 1, 0090 b: 1, 0091 a: 1 0092 }, 0093 // translate a format from Color object to a string 0094 'rgb': function () { 0095 var rgb = this.toRGB(); 0096 return 'rgb(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ')'; 0097 }, 0098 'rgba': function () { 0099 var rgb = this.toRGB(); 0100 return 'rgba(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ',' + rgb.a + ')'; 0101 }, 0102 'hex': function () { 0103 return this.toHex(); 0104 }, 0105 0106 // HSBtoRGB from RaphaelJS 0107 RGBtoHSB: function (r, g, b, a) { 0108 r /= 255; 0109 g /= 255; 0110 b /= 255; 0111 0112 var H, S, V, C; 0113 V = Math.max(r, g, b); 0114 C = V - Math.min(r, g, b); 0115 H = (C === 0 ? null : 0116 V === r ? (g - b) / C : 0117 V === g ? (b - r) / C + 2 : 0118 (r - g) / C + 4 0119 ); 0120 H = ((H + 360) % 6) * 60 / 360; 0121 S = C === 0 ? 0 : C / V; 0122 return {h: H || 1, s: S, b: V, a: a || 1}; 0123 }, 0124 0125 //parse a string to HSB 0126 setColor: function (val) { 0127 val = val.toLowerCase(); 0128 for (var key in Helper.stringParsers) { 0129 if (Helper.stringParsers.hasOwnProperty(key)) { 0130 var parser = Helper.stringParsers[key]; 0131 var match = parser.re.exec(val), 0132 values = match && parser.parse(match); 0133 if (values) { 0134 this.value = this.RGBtoHSB.apply(null, values); 0135 return false; 0136 } 0137 } 0138 } 0139 }, 0140 0141 setHue: function (h) { 0142 this.value.h = 1 - h; 0143 }, 0144 0145 setSaturation: function (s) { 0146 this.value.s = s; 0147 }, 0148 0149 setLightness: function (b) { 0150 this.value.b = 1 - b; 0151 }, 0152 0153 setAlpha: function (a) { 0154 this.value.a = parseInt((1 - a) * 100, 10) / 100; 0155 }, 0156 0157 // HSBtoRGB from RaphaelJS 0158 // https://github.com/DmitryBaranovskiy/raphael/ 0159 toRGB: function (h, s, b, a) { 0160 if (!h) { 0161 h = this.value.h; 0162 s = this.value.s; 0163 b = this.value.b; 0164 } 0165 h *= 360; 0166 var R, G, B, X, C; 0167 h = (h % 360) / 60; 0168 C = b * s; 0169 X = C * (1 - Math.abs(h % 2 - 1)); 0170 R = G = B = b - C; 0171 0172 h = ~~h; 0173 R += [C, X, 0, 0, X, C][h]; 0174 G += [X, C, C, X, 0, 0][h]; 0175 B += [0, 0, X, C, C, X][h]; 0176 return { 0177 r: Math.round(R * 255), 0178 g: Math.round(G * 255), 0179 b: Math.round(B * 255), 0180 a: a || this.value.a 0181 }; 0182 }, 0183 0184 toHex: function (h, s, b, a) { 0185 var rgb = this.toRGB(h, s, b, a); 0186 return '#' + ((1 << 24) | (parseInt(rgb.r, 10) << 16) | (parseInt(rgb.g, 10) << 8) | parseInt(rgb.b, 10)).toString(16).substr(1); 0187 } 0188 }; 0189 }]) 0190 .factory('Slider', ['Helper', function (Helper) { 0191 var 0192 slider = { 0193 maxLeft: 0, 0194 maxTop: 0, 0195 callLeft: null, 0196 callTop: null, 0197 knob: { 0198 top: 0, 0199 left: 0 0200 } 0201 }, 0202 pointer = {}; 0203 0204 return { 0205 getSlider: function() { 0206 return slider; 0207 }, 0208 getLeftPosition: function(event) { 0209 return Math.max(0, Math.min(slider.maxLeft, slider.left + ((event.pageX || pointer.left) - pointer.left))); 0210 }, 0211 getTopPosition: function(event) { 0212 return Math.max(0, Math.min(slider.maxTop, slider.top + ((event.pageY || pointer.top) - pointer.top))); 0213 }, 0214 setSlider: function (event, fixedPosition) { 0215 var 0216 target = Helper.closestSlider(event.target), 0217 targetOffset = Helper.getOffset(target, fixedPosition); 0218 slider.knob = target.children[0].style; 0219 slider.left = event.pageX - targetOffset.left - window.pageXOffset + targetOffset.scrollX; 0220 slider.top = event.pageY - targetOffset.top - window.pageYOffset + targetOffset.scrollY; 0221 0222 pointer = { 0223 left: event.pageX, 0224 top: event.pageY 0225 }; 0226 }, 0227 setSaturation: function(event, fixedPosition) { 0228 slider = { 0229 maxLeft: 100, 0230 maxTop: 100, 0231 callLeft: 'setSaturation', 0232 callTop: 'setLightness' 0233 }; 0234 this.setSlider(event, fixedPosition); 0235 }, 0236 setHue: function(event, fixedPosition) { 0237 slider = { 0238 maxLeft: 0, 0239 maxTop: 100, 0240 callLeft: false, 0241 callTop: 'setHue' 0242 }; 0243 this.setSlider(event, fixedPosition); 0244 }, 0245 setAlpha: function(event, fixedPosition) { 0246 slider = { 0247 maxLeft: 0, 0248 maxTop: 100, 0249 callLeft: false, 0250 callTop: 'setAlpha' 0251 }; 0252 this.setSlider(event, fixedPosition); 0253 }, 0254 setKnob: function(top, left) { 0255 slider.knob.top = top + 'px'; 0256 slider.knob.left = left + 'px'; 0257 } 0258 }; 0259 }]) 0260 .directive('colorpicker', ['$document', '$compile', 'Color', 'Slider', 'Helper', function ($document, $compile, Color, Slider, Helper) { 0261 return { 0262 require: '?ngModel', 0263 restrict: 'A', 0264 link: function ($scope, elem, attrs, ngModel) { 0265 var 0266 thisFormat = attrs.colorpicker ? attrs.colorpicker : 'hex', 0267 position = angular.isDefined(attrs.colorpickerPosition) ? attrs.colorpickerPosition : 'bottom', 0268 fixedPosition = angular.isDefined(attrs.colorpickerFixedPosition) ? attrs.colorpickerFixedPosition : false, 0269 target = angular.isDefined(attrs.colorpickerParent) ? elem.parent() : angular.element(document.body), 0270 withInput = angular.isDefined(attrs.colorpickerWithInput) ? attrs.colorpickerWithInput : false, 0271 inputTemplate = withInput ? '<input type="text" name="colorpicker-input">' : '', 0272 template = 0273 '<div class="colorpicker dropdown">' + 0274 '<div class="dropdown-menu">' + 0275 '<colorpicker-saturation><i></i></colorpicker-saturation>' + 0276 '<colorpicker-hue><i></i></colorpicker-hue>' + 0277 '<colorpicker-alpha><i></i></colorpicker-alpha>' + 0278 '<colorpicker-preview></colorpicker-preview>' + 0279 inputTemplate + 0280 '<button class="close close-colorpicker">×</button>' + 0281 '</div>' + 0282 '</div>', 0283 colorpickerTemplate = angular.element(template), 0284 pickerColor = Color, 0285 sliderAlpha, 0286 sliderHue = colorpickerTemplate.find('colorpicker-hue'), 0287 sliderSaturation = colorpickerTemplate.find('colorpicker-saturation'), 0288 colorpickerPreview = colorpickerTemplate.find('colorpicker-preview'), 0289 pickerColorPointers = colorpickerTemplate.find('i'); 0290 0291 $compile(colorpickerTemplate)($scope); 0292 0293 if (withInput) { 0294 var pickerColorInput = colorpickerTemplate.find('input'); 0295 pickerColorInput 0296 .on('mousedown', function(event) { 0297 event.stopPropagation(); 0298 }) 0299 .on('keyup', function(event) { 0300 var newColor = this.value; 0301 elem.val(newColor); 0302 if(ngModel) { 0303 $scope.$apply(ngModel.$setViewValue(newColor)); 0304 } 0305 event.stopPropagation(); 0306 event.preventDefault(); 0307 }); 0308 elem.on('keyup', function() { 0309 pickerColorInput.val(elem.val()); 0310 }); 0311 } 0312 0313 var bindMouseEvents = function() { 0314 $document.on('mousemove', mousemove); 0315 $document.on('mouseup', mouseup); 0316 }; 0317 0318 if (thisFormat === 'rgba') { 0319 colorpickerTemplate.addClass('alpha'); 0320 sliderAlpha = colorpickerTemplate.find('colorpicker-alpha'); 0321 sliderAlpha 0322 .on('click', function(event) { 0323 Slider.setAlpha(event, fixedPosition); 0324 mousemove(event); 0325 }) 0326 .on('mousedown', function(event) { 0327 Slider.setAlpha(event, fixedPosition); 0328 bindMouseEvents(); 0329 }); 0330 } 0331 0332 sliderHue 0333 .on('click', function(event) { 0334 Slider.setHue(event, fixedPosition); 0335 mousemove(event); 0336 }) 0337 .on('mousedown', function(event) { 0338 Slider.setHue(event, fixedPosition); 0339 bindMouseEvents(); 0340 }); 0341 0342 sliderSaturation 0343 .on('click', function(event) { 0344 Slider.setSaturation(event, fixedPosition); 0345 mousemove(event); 0346 }) 0347 .on('mousedown', function(event) { 0348 Slider.setSaturation(event, fixedPosition); 0349 bindMouseEvents(); 0350 }); 0351 0352 if (fixedPosition) { 0353 colorpickerTemplate.addClass('colorpicker-fixed-position'); 0354 } 0355 0356 colorpickerTemplate.addClass('colorpicker-position-' + position); 0357 0358 target.append(colorpickerTemplate); 0359 0360 if(ngModel) { 0361 ngModel.$render = function () { 0362 elem.val(ngModel.$viewValue); 0363 }; 0364 $scope.$watch(attrs.ngModel, function() { 0365 update(); 0366 }); 0367 } 0368 0369 elem.on('$destroy', function() { 0370 colorpickerTemplate.remove(); 0371 }); 0372 0373 var previewColor = function () { 0374 try { 0375 colorpickerPreview.css('backgroundColor', pickerColor[thisFormat]()); 0376 } catch (e) { 0377 colorpickerPreview.css('backgroundColor', pickerColor.toHex()); 0378 } 0379 sliderSaturation.css('backgroundColor', pickerColor.toHex(pickerColor.value.h, 1, 1, 1)); 0380 if (thisFormat === 'rgba') { 0381 sliderAlpha.css.backgroundColor = pickerColor.toHex(); 0382 } 0383 }; 0384 0385 var mousemove = function (event) { 0386 var 0387 left = Slider.getLeftPosition(event), 0388 top = Slider.getTopPosition(event), 0389 slider = Slider.getSlider(); 0390 0391 Slider.setKnob(top, left); 0392 0393 if (slider.callLeft) { 0394 pickerColor[slider.callLeft].call(pickerColor, left / 100); 0395 } 0396 if (slider.callTop) { 0397 pickerColor[slider.callTop].call(pickerColor, top / 100); 0398 } 0399 previewColor(); 0400 var newColor = pickerColor[thisFormat](); 0401 elem.val(newColor); 0402 if(ngModel) { 0403 $scope.$apply(ngModel.$setViewValue(newColor)); 0404 } 0405 if (withInput) { 0406 pickerColorInput.val(newColor); 0407 } 0408 return false; 0409 }; 0410 0411 var mouseup = function () { 0412 $document.off('mousemove', mousemove); 0413 $document.off('mouseup', mouseup); 0414 }; 0415 0416 var update = function () { 0417 pickerColor.setColor(elem.val()); 0418 pickerColorPointers.eq(0).css({ 0419 left: pickerColor.value.s * 100 + 'px', 0420 top: 100 - pickerColor.value.b * 100 + 'px' 0421 }); 0422 pickerColorPointers.eq(1).css('top', 100 * (1 - pickerColor.value.h) + 'px'); 0423 pickerColorPointers.eq(2).css('top', 100 * (1 - pickerColor.value.a) + 'px'); 0424 previewColor(); 0425 }; 0426 0427 var getColorpickerTemplatePosition = function() { 0428 var 0429 positionValue, 0430 positionOffset = Helper.getOffset(elem[0]); 0431 0432 if(angular.isDefined(attrs.colorpickerParent)) { 0433 positionOffset.left = 0; 0434 positionOffset.top = 0; 0435 } 0436 0437 if (position === 'top') { 0438 positionValue = { 0439 'top': positionOffset.top - 147, 0440 'left': positionOffset.left 0441 }; 0442 } else if (position === 'right') { 0443 positionValue = { 0444 'top': positionOffset.top, 0445 'left': positionOffset.left + 126 0446 }; 0447 } else if (position === 'bottom') { 0448 positionValue = { 0449 'top': positionOffset.top + elem[0].offsetHeight + 2, 0450 'left': positionOffset.left 0451 }; 0452 } else if (position === 'left') { 0453 positionValue = { 0454 'top': positionOffset.top, 0455 'left': positionOffset.left - 150 0456 }; 0457 } 0458 return { 0459 'top': positionValue.top + 'px', 0460 'left': positionValue.left + 'px' 0461 }; 0462 }; 0463 0464 var documentMousedownHandler = function() { 0465 hideColorpickerTemplate(); 0466 }; 0467 0468 elem.on('click', function () { 0469 update(); 0470 colorpickerTemplate 0471 .addClass('colorpicker-visible') 0472 .css(getColorpickerTemplatePosition()); 0473 0474 // register global mousedown event to hide the colorpicker 0475 $document.on('mousedown', documentMousedownHandler); 0476 }); 0477 0478 colorpickerTemplate.on('mousedown', function (event) { 0479 event.stopPropagation(); 0480 event.preventDefault(); 0481 }); 0482 0483 var hideColorpickerTemplate = function() { 0484 if (colorpickerTemplate.hasClass('colorpicker-visible')) { 0485 colorpickerTemplate.removeClass('colorpicker-visible'); 0486 0487 // unregister the global mousedown event 0488 $document.off('mousedown', documentMousedownHandler); 0489 } 0490 }; 0491 0492 colorpickerTemplate.find('button').on('click', function () { 0493 hideColorpickerTemplate(); 0494 }); 0495 } 0496 }; 0497 }]);