1/** 2 * jscolor - JavaScript Color Picker 3 * 4 * @link http://jscolor.com 5 * @license For open source use: GPLv3 6 * For commercial use: JSColor Commercial License 7 * @author Jan Odvarko - East Desire 8 * @version 2.5.2 9 * 10 * See usage examples at http://jscolor.com/examples/ 11 */ 12 13 14(function (global, factory) { 15 16 'use strict'; 17 18 if (typeof module === 'object' && typeof module.exports === 'object') { 19 // Export jscolor as a module 20 module.exports = global.document ? 21 factory (global) : 22 function (win) { 23 if (!win.document) { 24 throw new Error('jscolor needs a window with document'); 25 } 26 return factory(win); 27 } 28 return; 29 } 30 31 // Default use (no module export) 32 factory(global); 33 34})(typeof window !== 'undefined' ? window : this, function (window) { // BEGIN factory 35 36// BEGIN jscolor code 37 38 39'use strict'; 40 41 42var jscolor = (function () { // BEGIN jscolor 43 44var jsc = { 45 46 47 initialized : false, 48 49 instances : [], // created instances of jscolor 50 51 readyQueue : [], // functions waiting to be called after init 52 53 54 register : function () { 55 if (typeof window !== 'undefined' && window.document) { 56 if (window.document.readyState !== 'loading') { 57 jsc.pub.init(); 58 } else { 59 // console.log('Adding DOMContentLoaded listener for jscolor initialization'); 60 window.document.addEventListener('DOMContentLoaded', jsc.pub.init, false); 61 } 62 } 63 }, 64 65 66 installBySelector : function (selector, rootNode) { 67 rootNode = rootNode ? jsc.node(rootNode) : window.document; 68 if (!rootNode) { 69 throw new Error('Missing root node'); 70 } 71 72 var elms = rootNode.querySelectorAll(selector); 73 74 // for backward compatibility with DEPRECATED installation/configuration using className 75 var matchClass = new RegExp('(^|\\s)(' + jsc.pub.lookupClass + ')(\\s*(\\{[^}]*\\})|\\s|$)', 'i'); 76 77 for (var i = 0; i < elms.length; i += 1) { 78 79 if (elms[i].jscolor && elms[i].jscolor instanceof jsc.pub) { 80 continue; // jscolor already installed on this element 81 } 82 83 if (elms[i].type !== undefined && elms[i].type.toLowerCase() == 'color' && jsc.isColorAttrSupported) { 84 continue; // skips inputs of type 'color' if supported by the browser 85 } 86 87 var dataOpts, m; 88 89 if ( 90 (dataOpts = jsc.getDataAttr(elms[i], 'jscolor')) !== null || 91 (elms[i].className && (m = elms[i].className.match(matchClass))) // installation using className (DEPRECATED) 92 ) { 93 var targetElm = elms[i]; 94 95 var optsStr = ''; 96 if (dataOpts !== null) { 97 optsStr = dataOpts; 98 99 } else if (m) { // installation using className (DEPRECATED) 100 console.warn('Installation using class name is DEPRECATED. Use data-jscolor="" attribute instead.' + jsc.docsRef); 101 if (m[4]) { 102 optsStr = m[4]; 103 } 104 } 105 106 var opts = null; 107 if (optsStr.trim()) { 108 try { 109 opts = jsc.parseOptionsStr(optsStr); 110 } catch (e) { 111 console.warn(e + '\n' + optsStr); 112 } 113 } 114 115 try { 116 new jsc.pub(targetElm, opts); 117 } catch (e) { 118 console.warn(e); 119 } 120 } 121 } 122 }, 123 124 125 parseOptionsStr : function (str) { 126 var opts = null; 127 128 try { 129 opts = JSON.parse(str); 130 131 } catch (eParse) { 132 if (!jsc.pub.looseJSON) { 133 throw new Error('Could not parse jscolor options as JSON: ' + eParse); 134 } else { 135 // loose JSON syntax is enabled -> try to evaluate the options string as JavaScript object 136 try { 137 opts = (new Function ('var opts = (' + str + '); return typeof opts === "object" ? opts : {};'))(); 138 } catch (eEval) { 139 throw new Error('Could not evaluate jscolor options: ' + eEval); 140 } 141 } 142 } 143 return opts; 144 }, 145 146 147 getInstances : function () { 148 var inst = []; 149 for (var i = 0; i < jsc.instances.length; i += 1) { 150 // if the targetElement still exists, the instance is considered "alive" 151 if (jsc.instances[i] && jsc.instances[i].targetElement) { 152 inst.push(jsc.instances[i]); 153 } 154 } 155 return inst; 156 }, 157 158 159 createEl : function (tagName) { 160 var el = window.document.createElement(tagName); 161 jsc.setData(el, 'gui', true); 162 return el; 163 }, 164 165 166 node : function (nodeOrSelector) { 167 if (!nodeOrSelector) { 168 return null; 169 } 170 171 if (typeof nodeOrSelector === 'string') { 172 // query selector 173 var sel = nodeOrSelector; 174 var el = null; 175 try { 176 el = window.document.querySelector(sel); 177 } catch (e) { 178 console.warn(e); 179 return null; 180 } 181 if (!el) { 182 console.warn('No element matches the selector: %s', sel); 183 } 184 return el; 185 } 186 187 if (jsc.isNode(nodeOrSelector)) { 188 // DOM node 189 return nodeOrSelector; 190 } 191 192 console.warn('Invalid node of type %s: %s', typeof nodeOrSelector, nodeOrSelector); 193 return null; 194 }, 195 196 197 // See https://stackoverflow.com/questions/384286/ 198 isNode : function (val) { 199 if (typeof Node === 'object') { 200 return val instanceof Node; 201 } 202 return val && typeof val === 'object' && typeof val.nodeType === 'number' && typeof val.nodeName === 'string'; 203 }, 204 205 206 nodeName : function (node) { 207 if (node && node.nodeName) { 208 return node.nodeName.toLowerCase(); 209 } 210 return false; 211 }, 212 213 214 removeChildren : function (node) { 215 while (node.firstChild) { 216 node.removeChild(node.firstChild); 217 } 218 }, 219 220 221 isTextInput : function (el) {
222 return el && jsc.nodeName(el) === 'input' && el.type.toLowerCase() === 'text'; 223 }, 224 225 226 isButton : function (el) { 227 if (!el) { 228 return false; 229 } 230 var n = jsc.nodeName(el); 231 return ( 232 (n === 'button') || 233 (n === 'input' && ['button', 'submit', 'reset'].indexOf(el.type.toLowerCase()) > -1) 234 ); 235 }, 236 237 238 isButtonEmpty : function (el) { 239 switch (jsc.nodeName(el)) { 240 case 'input': return (!el.value || el.value.trim() === ''); 241 case 'button': return (el.textContent.trim() === ''); 242 } 243 return null; // could not determine element's text 244 }, 245 246 247 // See https://github.com/WICG/EventListenerOptions/blob/gh-pages/explainer.md 248 isPassiveEventSupported : (function () { 249 var supported = false; 250 251 try { 252 var opts = Object.defineProperty({}, 'passive', { 253 get: function () { supported = true; } 254 }); 255 window.addEventListener('testPassive', null, opts); 256 window.removeEventListener('testPassive', null, opts); 257 } catch (e) {} 258 259 return supported; 260 })(), 261 262 263 isColorAttrSupported : (function () { 264 var elm = window.document.createElement('input'); 265 if (elm.setAttribute) { 266 elm.setAttribute('type', 'color'); 267 if (elm.type.toLowerCase() == 'color') { 268 return true; 269 } 270 } 271 return false; 272 })(), 273 274 275 dataProp : '_data_jscolor', 276 277 278 // usage: 279 // setData(obj, prop, value) 280 // setData(obj, {prop:value, ...}) 281 // 282 setData : function () { 283 var obj = arguments[0]; 284 285 if (arguments.length === 3) { 286 // setting a single property 287 var data = obj.hasOwnProperty(jsc.dataProp) ? obj[jsc.dataProp] : (obj[jsc.dataProp] = {}); 288 var prop = arguments[1]; 289 var value = arguments[2]; 290 291 data[prop] = value; 292 return true; 293 294 } else if (arguments.length === 2 && typeof arguments[1] === 'object') { 295 // setting multiple properties 296 var data = obj.hasOwnProperty(jsc.dataProp) ? obj[jsc.dataProp] : (obj[jsc.dataProp] = {}); 297 var map = arguments[1]; 298 299 for (var prop in map) { 300 if (map.hasOwnProperty(prop)) { 301 data[prop] = map[prop]; 302 } 303 } 304 return true; 305 } 306 307 throw new Error('Invalid arguments'); 308 }, 309 310 311 // usage: 312 // removeData(obj, prop, [prop...]) 313 // 314 removeData : function () { 315 var obj = arguments[0]; 316 if (!obj.hasOwnProperty(jsc.dataProp)) { 317 return true; // data object does not exist 318 } 319 for (var i = 1; i < arguments.length; i += 1) { 320 var prop = arguments[i]; 321 delete obj[jsc.dataProp][prop]; 322 } 323 return true; 324 }, 325 326 327 getData : function (obj, prop, setDefault) { 328 if (!obj.hasOwnProperty(jsc.dataProp)) { 329 // data object does not exist 330 if (setDefault !== undefined) { 331 obj[jsc.dataProp] = {}; // create data object 332 } else { 333 return undefined; // no value to return 334 } 335 } 336 var data = obj[jsc.dataProp]; 337 338 if (!data.hasOwnProperty(prop) && setDefault !== undefined) { 339 data[prop] = setDefault; 340 } 341 return data[prop]; 342 }, 343 344 345 getDataAttr : function (el, name) { 346 var attrName = 'data-' + name; 347 var attrValue = el.getAttribute(attrName); 348 return attrValue; 349 }, 350 351 352 setDataAttr : function (el, name, value) { 353 var attrName = 'data-' + name; 354 el.setAttribute(attrName, value); 355 }, 356 357 358 _attachedGroupEvents : {}, 359 360 361 attachGroupEvent : function (groupName, el, evnt, func) { 362 if (!jsc._attachedGroupEvents.hasOwnProperty(groupName)) { 363 jsc._attachedGroupEvents[groupName] = []; 364 } 365 jsc._attachedGroupEvents[groupName].push([el, evnt, func]); 366 el.addEventListener(evnt, func, false); 367 }, 368 369 370 detachGroupEvents : function (groupName) { 371 if (jsc._attachedGroupEvents.hasOwnProperty(groupName)) { 372 for (var i = 0; i < jsc._attachedGroupEvents[groupName].length; i += 1) { 373 var evt = jsc._attachedGroupEvents[groupName][i]; 374 evt[0].removeEventListener(evt[1], evt[2], false); 375 } 376 delete jsc._attachedGroupEvents[groupName]; 377 } 378 }, 379 380 381 preventDefault : function (e) { 382 if (e.preventDefault) { e.preventDefault(); } 383 e.returnValue = false; 384 }, 385 386 387 triggerEvent : function (el, eventName, bubbles, cancelable) { 388 if (!el) { 389 return; 390 } 391 392 var ev = null; 393 394 if (typeof Event === 'function') { 395 ev = new Event(eventName, { 396 bubbles: bubbles, 397 cancelable: cancelable 398 }); 399 } else { 400 // IE 401 ev = window.document.createEvent('Event');
402 ev.initEvent(eventName, bubbles, cancelable); 403 } 404 405 if (!ev) { 406 return false; 407 } 408 409 // so that we know that the event was triggered internally 410 jsc.setData(ev, 'internal', true); 411 412 el.dispatchEvent(ev); 413 return true; 414 }, 415 416 417 triggerInputEvent : function (el, eventName, bubbles, cancelable) { 418 if (!el) { 419 return; 420 } 421 if (jsc.isTextInput(el)) { 422 jsc.triggerEvent(el, eventName, bubbles, cancelable); 423 } 424 }, 425 426 427 eventKey : function (ev) { 428 var keys = { 429 9: 'Tab', 430 13: 'Enter', 431 27: 'Escape', 432 }; 433 if (typeof ev.code === 'string') { 434 return ev.code; 435 } else if (ev.keyCode !== undefined && keys.hasOwnProperty(ev.keyCode)) { 436 return keys[ev.keyCode]; 437 } 438 return null; 439 }, 440 441 442 strList : function (str) { 443 if (!str) { 444 return []; 445 } 446 return str.replace(/^\s+|\s+$/g, '').split(/\s+/); 447 }, 448 449 450 // The className parameter (str) can only contain a single class name 451 hasClass : function (elm, className) { 452 if (!className) { 453 return false; 454 } 455 if (elm.classList !== undefined) { 456 return elm.classList.contains(className); 457 } 458 // polyfill 459 return -1 != (' ' + elm.className.replace(/\s+/g, ' ') + ' ').indexOf(' ' + className + ' '); 460 }, 461 462 463 // The className parameter (str) can contain multiple class names separated by whitespace 464 addClass : function (elm, className) { 465 var classNames = jsc.strList(className); 466 467 if (elm.classList !== undefined) { 468 for (var i = 0; i < classNames.length; i += 1) { 469 elm.classList.add(classNames[i]); 470 } 471 return; 472 } 473 // polyfill 474 for (var i = 0; i < classNames.length; i += 1) { 475 if (!jsc.hasClass(elm, classNames[i])) { 476 elm.className += (elm.className ? ' ' : '') + classNames[i]; 477 } 478 } 479 }, 480 481 482 // The className parameter (str) can contain multiple class names separated by whitespace 483 removeClass : function (elm, className) { 484 var classNames = jsc.strList(className); 485 486 if (elm.classList !== undefined) { 487 for (var i = 0; i < classNames.length; i += 1) { 488 elm.classList.remove(classNames[i]); 489 } 490 return; 491 } 492 // polyfill 493 for (var i = 0; i < classNames.length; i += 1) { 494 var repl = new RegExp( 495 '^\\s*' + classNames[i] + '\\s*|' + 496 '\\s*' + classNames[i] + '\\s*$|' + 497 '\\s+' + classNames[i] + '(\\s+)', 498 'g' 499 ); 500 elm.className = elm.className.replace(repl, '$1'); 501 } 502 }, 503 504 505 getCompStyle : function (elm) { 506 var compStyle = window.getComputedStyle ? window.getComputedStyle(elm) : elm.currentStyle; 507 508 // Note: In Firefox, getComputedStyle returns null in a hidden iframe, 509 // that's why we need to check if the returned value is non-empty 510 if (!compStyle) { 511 return {}; 512 } 513 return compStyle; 514 }, 515 516 517 // Note: 518 // Setting a property to NULL reverts it to the state before it was first set 519 // with the 'reversible' flag enabled 520 // 521 setStyle : function (elm, styles, important, reversible) { 522 // using '' for standard priority (IE10 apparently doesn't like value undefined) 523 var priority = important ? 'important' : ''; 524 var origStyle = null; 525 526 for (var prop in styles) { 527 if (styles.hasOwnProperty(prop)) { 528 var setVal = null; 529 530 if (styles[prop] === null) { 531 // reverting a property value 532 533 if (!origStyle) { 534 // get the original style object, but dont't try to create it if it doesn't exist 535 origStyle = jsc.getData(elm, 'origStyle'); 536 } 537 if (origStyle && origStyle.hasOwnProperty(prop)) { 538 // we have property's original value -> use it 539 setVal = origStyle[prop]; 540 } 541 542 } else { 543 // setting a property value 544 545 if (reversible) { 546 if (!origStyle) { 547 // get the original style object and if it doesn't exist, create it 548 origStyle = jsc.getData(elm, 'origStyle', {}); 549 } 550 if (!origStyle.hasOwnProperty(prop)) { 551 // original property value not yet stored -> store it 552 origStyle[prop] = elm.style[prop]; 553 } 554 } 555 setVal = styles[prop]; 556 } 557 558 if (setVal !== null) { 559 elm.style.setProperty(prop, setVal, priority); 560 } 561 } 562 } 563 }, 564 565 566 appendCss : function (css) { 567 var head = document.querySelector('head'); 568 var style = document.createElement('style'); 569 style.innerText = css; 570 head.appendChild(style); 571 }, 572 573 574 appendDefaultCss : function (css) { 575 jsc.appendCss( 576 [ 577 '.jscolor-wrap, .jscolor-wrap div, .jscolor-wrap canvas { ' + 578 'position:static; display:block; visibility:visible; overflow:visible; margin:0; padding:0; ' + 579 'border:none; border-radius:0; outline:none; z-index:auto; float:none; ' + 580 'width:auto; height:auto; left:auto; right:auto; top:auto; bottom:auto; min-width:0; min-height:0; max-width:none; max-height:none; ' + 581 'background:none; clip:auto;
581 opacity:1; transform:none; box-shadow:none; box-sizing:content-box; ' + 582 '}', 583 '.jscolor-wrap { clear:both; }', 584 '.jscolor-wrap .jscolor-picker { position:relative; }', 585 '.jscolor-wrap .jscolor-shadow { position:absolute; left:0; top:0; width:100%; height:100%; }', 586 '.jscolor-wrap .jscolor-border { position:relative; }', 587 '.jscolor-wrap .jscolor-palette { position:absolute; }', 588 '.jscolor-wrap .jscolor-palette-sw { position:absolute; display:block; cursor:pointer; }', 589 '.jscolor-wrap .jscolor-btn { position:absolute; overflow:hidden; white-space:nowrap; font:13px sans-serif; text-align:center; cursor:pointer; }', 590 ].join('\n') 591 ); 592 }, 593 594 595 hexColor : function (r, g, b) { 596 return '#' + ( 597 ('0' + Math.round(r).toString(16)).slice(-2) + 598 ('0' + Math.round(g).toString(16)).slice(-2) + 599 ('0' + Math.round(b).toString(16)).slice(-2) 600 ).toUpperCase(); 601 }, 602 603 604 hexaColor : function (r, g, b, a) { 605 return '#' + ( 606 ('0' + Math.round(r).toString(16)).slice(-2) + 607 ('0' + Math.round(g).toString(16)).slice(-2) + 608 ('0' + Math.round(b).toString(16)).slice(-2) + 609 ('0' + Math.round(a * 255).toString(16)).slice(-2) 610 ).toUpperCase(); 611 }, 612 613 614 rgbColor : function (r, g, b) { 615 return 'rgb(' + 616 Math.round(r) + ',' + 617 Math.round(g) + ',' + 618 Math.round(b) + 619 ')'; 620 }, 621 622 623 rgbaColor : function (r, g, b, a) { 624 return 'rgba(' + 625 Math.round(r) + ',' + 626 Math.round(g) + ',' + 627 Math.round(b) + ',' + 628 (Math.round((a===undefined || a===null ? 1 : a) * 100) / 100) + 629 ')'; 630 }, 631 632 633 linearGradient : (function () { 634 635 function getFuncName () { 636 var stdName = 'linear-gradient'; 637 var prefixes = ['', '-webkit-', '-moz-', '-o-', '-ms-']; 638 var helper = window.document.createElement('div'); 639 640 for (var i = 0; i < prefixes.length; i += 1) { 641 var tryFunc = prefixes[i] + stdName; 642 var tryVal = tryFunc + '(to right, rgba(0,0,0,0), rgba(0,0,0,0))'; 643 644 helper.style.background = tryVal; 645 if (helper.style.background) { // CSS background successfully set -> function name is supported 646 return tryFunc; 647 } 648 } 649 return stdName; // fallback to standard 'linear-gradient' without vendor prefix 650 } 651 652 var funcName = getFuncName(); 653 654 return function () { 655 return funcName + '(' + Array.prototype.join.call(arguments, ', ') + ')'; 656 }; 657 658 })(), 659 660 661 setBorderRadius : function (elm, value) { 662 jsc.setStyle(elm, {'border-radius' : value || '0'}); 663 }, 664 665 666 setBoxShadow : function (elm, value) { 667 jsc.setStyle(elm, {'box-shadow': value || 'none'}); 668 }, 669 670 671 getElementPos : function (e, relativeToViewport) { 672 var x=0, y=0; 673 var rect = e.getBoundingClientRect(); 674 x = rect.left; 675 y = rect.top; 676 if (!relativeToViewport) { 677 var viewPos = jsc.getViewPos(); 678 x += viewPos[0]; 679 y += viewPos[1]; 680 } 681 return [x, y]; 682 }, 683 684 685 getElementSize : function (e) { 686 return [e.offsetWidth, e.offsetHeight]; 687 }, 688 689 690 // get pointer's X/Y coordinates relative to viewport 691 getAbsPointerPos : function (e) { 692 var x = 0, y = 0; 693 if (typeof e.changedTouches !== 'undefined' && e.changedTouches.length) { 694 // touch devices 695 x = e.changedTouches[0].clientX; 696 y = e.changedTouches[0].clientY; 697 } else if (typeof e.clientX === 'number') { 698 x = e.clientX; 699 y = e.clientY; 700 } 701 return { x: x, y: y }; 702 }, 703 704 705 // get pointer's X/Y coordinates relative to target element 706 getRelPointerPos : function (e) { 707 var target = e.target || e.srcElement; 708 var targetRect = target.getBoundingClientRect(); 709 710 var x = 0, y = 0; 711 712 var clientX = 0, clientY = 0; 713 if (typeof e.changedTouches !== 'undefined' && e.changedTouches.length) { 714 // touch devices 715 clientX = e.changedTouches[0].clientX; 716 clientY = e.changedTouches[0].clientY; 717 } else if (typeof e.clientX === 'number') { 718 clientX = e.clientX; 719 clientY = e.clientY; 720 } 721 722 x = clientX - targetRect.left; 723 y = clientY - targetRect.top; 724 return { x: x, y: y }; 725 }, 726 727 728 getViewPos : function () { 729 var doc = window.document.documentElement; 730 return [ 731 (window.pageXOffset || doc.scrollLeft) - (doc.clientLeft || 0), 732 (window.pageYOffset || doc.scrollTop) - (doc.clientTop || 0) 733 ]; 734 }, 735 736 737 getViewSize : function () { 738 var doc = window.document.documentElement; 739 return [ 740 (window.innerWidth || doc.clientWidth), 741 (window.innerHeight || doc.clientHeight), 742 ]; 743 }, 744 745 746 // r: 0-255 747 // g: 0-255 748 // b: 0-255 749 // 750 // returns: [ 0-360, 0-100, 0-100 ] 751 // 752 RGB_HSV : function (r, g, b) { 753 r /= 255; 754 g /= 255; 755 b /= 255; 756 var n = Math.min(Math.min(r,g),b); 757 var v = Math.max(Math.max(r,g),b); 758 var m = v - n; 759 if (m === 0) { return [ null, 0, 100 * v ]; }
760 var h = r===n ? 3+(b-g)/m : (g===n ? 5+(r-b)/m : 1+(g-r)/m); 761 return [ 762 60 * (h===6?0:h), 763 100 * (m/v), 764 100 * v 765 ]; 766 }, 767 768 769 // h: 0-360 770 // s: 0-100 771 // v: 0-100 772 // 773 // returns: [ 0-255, 0-255, 0-255 ] 774 // 775 HSV_RGB : function (h, s, v) { 776 var u = 255 * (v / 100); 777 778 if (h === null) { 779 return [ u, u, u ]; 780 } 781 782 h /= 60; 783 s /= 100; 784 785 var i = Math.floor(h); 786 var f = i%2 ? h-i : 1-(h-i); 787 var m = u * (1 - s); 788 var n = u * (1 - s * f); 789 switch (i) { 790 case 6: 791 case 0: return [u,n,m]; 792 case 1: return [n,u,m]; 793 case 2: return [m,u,n]; 794 case 3: return [m,n,u]; 795 case 4: return [n,m,u]; 796 case 5: return [u,m,n]; 797 } 798 }, 799 800 // Eyedropper functionality - global state 801 _eyedropperActive : false, 802 _eyedropperInstance : null, 803 _eyedropperOverlay : null, 804 805 // Activate eyedropper mode 806 activateEyedropper : function (instance) { 807 if (jsc._eyedropperActive) return; 808 809 jsc._eyedropperActive = true; 810 jsc._eyedropperInstance = instance; 811 812 // Hide the crosshair cursor instead of the entire picker 813 if (jsc.picker && jsc.picker.cross) { 814 jsc.picker.cross.style.display = 'none'; 815 } 816 817 // Create full-screen overlay 818 jsc._eyedropperOverlay = jsc.createEl('div'); 819 jsc._eyedropperOverlay.style.position = 'fixed'; 820 jsc._eyedropperOverlay.style.left = '0'; 821 jsc._eyedropperOverlay.style.top = '0'; 822 jsc._eyedropperOverlay.style.width = '100%'; 823 jsc._eyedropperOverlay.style.height = '100%'; 824 jsc._eyedropperOverlay.style.zIndex = '999999'; 825 jsc._eyedropperOverlay.style.cursor = instance.eyedropperCursor || 'crosshair'; 826 jsc._eyedropperOverlay.style.backgroundColor = 'transparent'; 827 828 // Add event listeners 829 jsc._eyedropperOverlay.addEventListener('mousemove', jsc.onEyedropperMove, false); 830 jsc._eyedropperOverlay.addEventListener('click', jsc.onEyedropperClick, false); 831 jsc._eyedropperOverlay.addEventListener('keydown', jsc.onEyedropperKeyDown, false); 832 jsc._eyedropperOverlay.tabIndex = -1; // Make it focusable for keyboard events 833 834 // Append to body 835 window.document.body.appendChild(jsc._eyedropperOverlay); 836 jsc._eyedropperOverlay.focus(); 837 }, 838 839 // Deactivate eyedropper mode 840 deactivateEyedropper : function () { 841 if (!jsc._eyedropperActive) return; 842 843 jsc._eyedropperActive = false; 844 jsc._eyedropperInstance = null; 845 846 // Remove overlay 847 if (jsc._eyedropperOverlay) { 848 jsc._eyedropperOverlay.removeEventListener('mousemove', jsc.onEyedropperMove, false); 849 jsc._eyedropperOverlay.removeEventListener('click', jsc.onEyedropperClick, false); 850 jsc._eyedropperOverlay.removeEventListener('keydown', jsc.onEyedropperKeyDown, false); 851 852 if (jsc._eyedropperOverlay.parentNode) { 853 jsc._eyedropperOverlay.parentNode.removeChild(jsc._eyedropperOverlay); 854 } 855 jsc._eyedropperOverlay = null; 856 } 857 858 // Don't restore the crosshair - let the eyedropper crosshair handle both DOM and picker interaction 859 // The original crosshair stays hidden permanently once eyedropper mode is activated 860 }, 861 862 // Eyedropper event handlers 863 onEyedropperMove : function (e) { 864 // Optional: Show preview of color under cursor 865 // This could be implemented to show a small preview window 866 }, 867 868 onEyedropperClick : function (e) { 869 if (!jsc._eyedropperInstance) return; 870 871 var pos = jsc.getAbsPointerPos(e); 872 873 // Check if click is on the color picker pad area 874 var elementAtPoint = window.document.elementFromPoint(pos.x, pos.y); 875 var isPickerPad = false; 876 877 // Check if the clicked element is part of the picker pad 878 if (elementAtPoint && jsc.picker && jsc.picker.padCanvas && jsc.picker.padCanvas.elm) { 879 var padElement = jsc.picker.padCanvas.elm; 880 var current = elementAtPoint; 881 while (current && current !== window.document.body) { 882 if (current === padElement || current === jsc.picker.pad || current === jsc.picker.padB) { 883 isPickerPad = true; 884 break; 885 } 886 current = current.parentElement; 887 } 888 } 889 890 if (isPickerPad) { 891 // Handle picker pad interaction - simulate normal pad click 892 var padRect = jsc.picker.padCanvas.elm.getBoundingClientRect(); 893 var x = pos.x - padRect.left; 894 var y = pos.y - padRect.top; 895 896 var xVal = x * (360 / (jsc._eyedropperInstance.width - 1)); 897 var yVal = 100 - (y * (100 / (jsc._eyedropperInstance.height - 1))); 898 899 switch (jsc.getPadYChannel(jsc._eyedropperInstance)) { 900 case 's': jsc._eyedropperInstance.fromHSVA(xVal, yVal, null, null); break; 901 case 'v': jsc._eyedropperInstance.fromHSVA(xVal, null, yVal, null); break; 902 } 903 jsc._eyedropperInstance.trigger('input'); 904 } else { 905 // Handle DOM color picking 906 var color = jsc.getColorFromDOM(pos.x, pos.y); 907 if (color) { 908 jsc._eyedropperInstance.fromRGBA(color.r, color.g, color.b, color.a); 909 jsc._eyedropperInstance.trigger('input'); 910 jsc._eyedropperInstance.trigger('change'); 911 } 912 jsc.deactivateEyedropper(); 913 } 914 915 jsc.preventDefault(e); 916 }, 917 918 onEyedropperKeyDown : function (e) { 919 var key = jsc.eventKey(e); 920 if (key === 'Escape') { 921 jsc.deactivateEyedropper(); 922 jsc.preventDefault(e); 923 } 924 }, 925 926 // Get color from DOM at specific coordinates 927 getColorFromDOM : function (x, y) { 928 // Temporarily hide the eyedropper overlay to get the actual element
929 var overlay = jsc._eyedropperOverlay; 930 if (overlay) { 931 overlay.style.display = 'none'; 932 } 933 934 var elementAtPoint = window.document.elementFromPoint(x, y); 935 936 // Restore the overlay 937 if (overlay) { 938 overlay.style.display = ''; 939 } 940 941 if (!elementAtPoint) { 942 return { r: 255, g: 255, b: 255, a: 1.0 }; 943 } 944 945 // Function to extract color from various CSS properties 946 function extractColorFromElement(element) { 947 var style = jsc.getCompStyle(element); 948 var colorSources = [ 949 style.backgroundColor, 950 style.color, 951 style.borderColor, 952 style.borderTopColor, 953 style.borderRightColor, 954 style.borderBottomColor, 955 style.borderLeftColor 956 ]; 957 958 // Check background image for solid colors 959 if (style.backgroundImage && style.backgroundImage !== 'none') { 960 var bgImg = style.backgroundImage; 961 // Extract colors from linear-gradient, radial-gradient, etc. 962 var gradientMatch = bgImg.match(/rgba?\([^)]+\)|#[0-9a-fA-F]{3,8}/g); 963 if (gradientMatch && gradientMatch.length > 0) { 964 colorSources.unshift(gradientMatch[0]); 965 } 966 } 967 968 for (var i = 0; i < colorSources.length; i++) { 969 var colorValue = colorSources[i]; 970 if (colorValue && colorValue !== 'transparent' && colorValue !== 'rgba(0, 0, 0, 0)' && colorValue !== 'initial' && colorValue !== 'inherit') { 971 var color = jsc.parseColorString(colorValue); 972 if (color && color.rgba) { 973 return { 974 r: color.rgba[0], 975 g: color.rgba[1], 976 b: color.rgba[2], 977 a: color.rgba[3] !== null ? color.rgba[3] : 1.0 978 }; 979 } 980 } 981 } 982 return null; 983 } 984 985 // Try the clicked element first 986 var color = extractColorFromElement(elementAtPoint); 987 if (color) { 988 return color; 989 } 990 991 // Traverse up the DOM tree to find a colored element 992 var element = elementAtPoint.parentElement; 993 while (element && element !== window.document.documentElement) { 994 color = extractColorFromElement(element); 995 if (color) { 996 return color; 997 } 998 element = element.parentElement; 999 } 1000 1001 // Check if element has any text content and try to get text color 1002 if (elementAtPoint.textContent && elementAtPoint.textContent.trim()) { 1003 var style = jsc.getCompStyle(elementAtPoint); 1004 if (style.color && style.color !== 'transparent') { 1005 var textColor = jsc.parseColorString(style.color); 1006 if (textColor && textColor.rgba) { 1007 return { 1008 r: textColor.rgba[0], 1009 g: textColor.rgba[1], 1010 b: textColor.rgba[2], 1011 a: textColor.rgba[3] !== null ? textColor.rgba[3] : 1.0 1012 }; 1013 } 1014 } 1015 } 1016 1017 // Try to get color from body background as last resort 1018 var bodyStyle = jsc.getCompStyle(window.document.body); 1019 if (bodyStyle.backgroundColor && bodyStyle.backgroundColor !== 'transparent') { 1020 var bodyColor = jsc.parseColorString(bodyStyle.backgroundColor); 1021 if (bodyColor && bodyColor.rgba) { 1022 return { 1023 r: bodyColor.rgba[0], 1024 g: bodyColor.rgba[1], 1025 b: bodyColor.rgba[2], 1026 a: bodyColor.rgba[3] !== null ? bodyColor.rgba[3] : 1.0 1027 }; 1028 } 1029 } 1030 1031 // Final fallback: return a neutral gray instead of white 1032 return { r: 128, g: 128, b: 128, a: 1.0 }; 1033 }, 1034 1035 1036 parseColorString : function (str) { 1037 var ret = { 1038 rgba: null, 1039 format: null // 'hex' | 'hexa' | 'rgb' | 'rgba' 1040 }; 1041 1042 var m; 1043 1044 if (m = str.match(/^\W*([0-9A-F]{3,8})\W*$/i)) { 1045 // HEX notation 1046 1047 if (m[1].length === 8) { 1048 // 8-char notation (= with alpha) 1049 ret.format = 'hexa'; 1050 ret.rgba = [ 1051 parseInt(m[1].slice(0,2),16), 1052 parseInt(m[1].slice(2,4),16), 1053 parseInt(m[1].slice(4,6),16), 1054 parseInt(m[1].slice(6,8),16) / 255 1055 ]; 1056 1057 } else if (m[1].length === 6) { 1058 // 6-char notation 1059 ret.format = 'hex'; 1060 ret.rgba = [ 1061 parseInt(m[1].slice(0,2),16), 1062 parseInt(m[1].slice(2,4),16), 1063 parseInt(m[1].slice(4,6),16), 1064 null 1065 ]; 1066 1067 } else if (m[1].length === 3) { 1068 // 3-char notation 1069 ret.format = 'hex'; 1070 ret.rgba = [ 1071 parseInt(m[1].charAt(0) + m[1].charAt(0),16), 1072 parseInt(m[1].charAt(1) + m[1].charAt(1),16), 1073 parseInt(m[1].charAt(2) + m[1].charAt(2),16), 1074 null 1075 ]; 1076 1077 } else { 1078 return false; 1079 } 1080 1081 return ret; 1082 } 1083 1084 if (m = str.match(/^\W*rgba?\(([^)]*)\)\W*$/i)) { 1085 // rgb(...) or rgba(...) notation 1086 1087 var par = m[1].split(','); 1088 var re = /^\s*(\d+|\d*\.\d+|\d+\.\d*)\s*$/; 1089 var mR, mG, mB, mA; 1090 if ( 1091 par.length >= 3 && 1092 (mR = par[0].match(re)) && 1093 (mG = par[1].match(re)) && 1094 (mB = par[2].match(re)) 1095 ) { 1096 ret.format = 'rgb'; 1097 ret.rgba = [ 1098 parseFloat(mR[1]) || 0, 1099 parseFloat(mG[1]) || 0, 1100 parseFloat(mB[1]) || 0, 1101 null 1102 ]; 1103 1104 if ( 1105 par.length >= 4 && 1106 (mA = par[3].match(re)) 1107 ) { 1108 ret.format = 'rgba'; 1109 ret.rgba[3] = parseFloat(mA[1]) || 0; 1110 } 1111 return ret; 1112 } 1113 } 1114 1115 return false; 1116 }, 1117 1118 1119 parsePaletteValue : function (mixed) { 1120 var vals = []; 1121 1122 if (typeof mixed === 'string') { // input is a string of space separated color values 1123 // rgb() and rgba() may contain spaces too, so let's find all color values by regex 1124 mixed.replace(/#[0-9A-F]{3}\b|#[0-9A-F]{6}([0-9A-F]{2})?\b|rgba?\(([^)]*)\)/ig, function (val) { 1125 vals.push(val); 1126 }); 1127 } else if (Array.isArray(mixed)) { // input is an array of color values 1128 vals = mixed; 1129 } 1130 1131 // convert all values into uniform color format 1132 1133 var colors = []; 1134 1135 for (var i = 0; i < vals.length; i++) { 1136 var color = jsc.parseColorString(vals[i]); 1137 if (color) { 1138 colors.push(color); 1139 } 1140 } 1141 1142 return colors; 1143 }, 1144 1145 1146 containsTranparentColor : function (colors) { 1147 for (var i = 0; i < colors.length; i++) { 1148 var a = colors[i].rgba[3]; 1149 if (a !== null && a < 1.0) { 1150 return true; 1151 } 1152 } 1153 return false; 1154 }, 1155 1156 1157 isAlphaFormat : function (format) { 1158 switch (format.toLowerCase()) { 1159 case 'hexa': 1160 case 'rgba': 1161 return true; 1162 } 1163 return false; 1164 }, 1165 1166 1167 // Canvas scaling for retina displays 1168 // 1169 // adapted from https://www.html5rocks.com/en/tutorials/canvas/hidpi/ 1170 // 1171 scaleCanvasForHighDPR : function (canvas) { 1172 var dpr = window.devicePixelRatio || 1; 1173 canvas.width *= dpr; 1174 canvas.height *= dpr; 1175 var ctx = canvas.getContext('2d'); 1176 ctx.scale(dpr, dpr); 1177 }, 1178 1179 1180 genColorPreviewCanvas : function (color, separatorPos, specWidth, scaleForHighDPR) { 1181 1182 var sepW = Math.round(jsc.pub.previewSeparator.length); 1183 var sqSize = jsc.pub.chessboardSize; 1184 var sqColor1 = jsc.pub.chessboardColor1; 1185 var sqColor2 = jsc.pub.chessboardColor2; 1186 1187 var cWidth = specWidth ? specWidth : sqSize * 2; 1188 var cHeight = sqSize * 2; 1189 1190 var canvas = jsc.createEl('canvas'); 1191 var ctx = canvas.getContext('2d'); 1192 1193 canvas.width = cWidth; 1194 canvas.height = cHeight; 1195 if (scaleForHighDPR) { 1196 jsc.scaleCanvasForHighDPR(canvas); 1197 } 1198 1199 // transparency chessboard - background 1200 ctx.fillStyle = sqColor1; 1201 ctx.fillRect(0, 0, cWidth, cHeight); 1202 1203 // transparency chessboard - squares 1204 ctx.fillStyle = sqColor2; 1205 for (var x = 0; x < cWidth; x += sqSize * 2) { 1206 ctx.fillRect(x, 0, sqSize, sqSize); 1207 ctx.fillRect(x + sqSize, sqSize, sqSize, sqSize); 1208 } 1209 1210 if (color) { 1211 // actual color in foreground 1212 ctx.fillStyle = color; 1213 ctx.fillRect(0, 0, cWidth, cHeight); 1214 } 1215 1216 var start = null; 1217 switch (separatorPos) { 1218 case 'left': 1219 start = 0; 1220 ctx.clearRect(0, 0, sepW/2, cHeight); 1221 break; 1222 case 'right': 1223 start = cWidth - sepW; 1224 ctx.clearRect(cWidth - (sepW/2), 0, sepW/2, cHeight); 1225 break; 1226 } 1227 if (start !== null) { 1228 ctx.lineWidth = 1; 1229 for (var i = 0; i < jsc.pub.previewSeparator.length; i += 1) { 1230 ctx.beginPath(); 1231 ctx.strokeStyle = jsc.pub.previewSeparator[i]; 1232 ctx.moveTo(0.5 + start + i, 0); 1233 ctx.lineTo(0.5 + start + i, cHeight); 1234 ctx.stroke(); 1235 } 1236 } 1237 1238 return { 1239 canvas: canvas, 1240 width: cWidth, 1241 height: cHeight, 1242 }; 1243 }, 1244 1245 1246 // if position or width is not set => fill the entire element (0%-100%) 1247 genColorPreviewGradient : function (color, position, width) { 1248 var params = []; 1249 1250 if (position && width) { 1251 params = [ 1252 'to ' + {'left':'right', 'right':'left'}[position], 1253 color + ' 0%', 1254 color + ' ' + width + 'px', 1255 'rgba(0,0,0,0) ' + (width + 1) + 'px', 1256 'rgba(0,0,0,0) 100%', 1257 ]; 1258 } else { 1259 params = [ 1260 'to right', 1261 color + ' 0%', 1262 color + ' 100%', 1263 ]; 1264 } 1265 1266 return jsc.linearGradient.apply(this, params); 1267 }, 1268 1269 1270 redrawPosition : function () { 1271 1272 if (!jsc.picker || !jsc.picker.owner) { 1273 return; // picker is not shown 1274 } 1275 1276 var thisObj = jsc.picker.owner; 1277 1278 if (thisObj.container !== window.document.body) { 1279 1280 jsc._drawPosition(thisObj, 0, 0, 'relative', false); 1281 1282 } else { 1283 1284 var tp, vp; 1285 1286 if (thisObj.fixed) { 1287 // Fixed elements are positioned relative to viewport, 1288 // therefore we can ignore the scroll offset 1289 tp = jsc.getElementPos(thisObj.targetElement, true); // target pos 1290 vp = [0, 0]; // view pos 1291 } else { 1292 tp = jsc.getElementPos(thisObj.targetElement); // target pos 1293 vp = jsc.getViewPos(); // view pos 1294 } 1295 1296 var ts = jsc.getElementSize(thisObj.targetElement); // target size 1297 var vs = jsc.getViewSize(); // view size 1298 var pd = jsc.getPickerDims(thisObj); 1299 var ps = [pd.borderW, pd.borderH]; // picker outer size 1300 var a, b, c; 1301 switch (thisObj.position.toLowerCase()) { 1302 case 'left': a=1; b=0; c=-1; break; 1303 case 'right':a=1; b=0; c=1; break; 1304 case 'top': a=0; b=1; c=-1; break; 1305 default: a=0; b=1; c=1; break; 1306 } 1307 var l = (ts[b]+ps[b])/2; 1308 1309 // compute picker position 1310 if (!thisObj.smartPosition) { 1311 var pp = [ 1312 tp[a], 1313 tp[b]+ts[b]-l+l*c 1314 ]; 1315 } else { 1316 var pp = [ 1317 -vp[a]+tp[a]+ps[a] > vs[a] ? 1318 (-vp[a]+tp[a]+ts[a]/2 > vs[a]/2 && tp[a]+ts[a]-ps[a] >
1318= 0 ? tp[a]+ts[a]-ps[a] : tp[a]) : 1319 tp[a], 1320 -vp[b]+tp[b]+ts[b]+ps[b]-l+l*c > vs[b] ? 1321 (-vp[b]+tp[b]+ts[b]/2 > vs[b]/2 && tp[b]+ts[b]-l-l*c >= 0 ? tp[b]+ts[b]-l-l*c : tp[b]+ts[b]-l+l*c) : 1322 (tp[b]+ts[b]-l+l*c >= 0 ? tp[b]+ts[b]-l+l*c : tp[b]+ts[b]-l-l*c) 1323 ]; 1324 } 1325 1326 var x = pp[a]; 1327 var y = pp[b]; 1328 var positionValue = thisObj.fixed ? 'fixed' : 'absolute'; 1329 var contractShadow = 1330 (pp[0] + ps[0] > tp[0] || pp[0] < tp[0] + ts[0]) && 1331 (pp[1] + ps[1] < tp[1] + ts[1]); 1332 1333 jsc._drawPosition(thisObj, x, y, positionValue, contractShadow); 1334 1335 } 1336 1337 }, 1338 1339 1340 _drawPosition : function (thisObj, x, y, positionValue, contractShadow) { 1341 var vShadow = contractShadow ? 0 : thisObj.shadowBlur; // px 1342 1343 jsc.picker.wrap.style.position = positionValue; 1344 1345 if ( // To avoid unnecessary repositioning during scroll 1346 Math.round(parseFloat(jsc.picker.wrap.style.left)) !== Math.round(x) || 1347 Math.round(parseFloat(jsc.picker.wrap.style.top)) !== Math.round(y) 1348 ) { 1349 jsc.picker.wrap.style.left = x + 'px'; 1350 jsc.picker.wrap.style.top = y + 'px'; 1351 } 1352 1353 jsc.setBoxShadow( 1354 jsc.picker.boxS, 1355 thisObj.shadow ? 1356 new jsc.BoxShadow(0, vShadow, thisObj.shadowBlur, 0, thisObj.shadowColor) : 1357 null); 1358 }, 1359 1360 1361 getPickerDims : function (thisObj) { 1362 var w = 2 * thisObj.controlBorderWidth + thisObj.width; 1363 var h = 2 * thisObj.controlBorderWidth + thisObj.height; 1364 1365 var sliderSpace = 2 * thisObj.controlBorderWidth + 2 * jsc.getControlPadding(thisObj) + thisObj.sliderSize; 1366 1367 if (jsc.getSliderChannel(thisObj)) { 1368 w += sliderSpace; 1369 } 1370 if (thisObj.hasAlphaChannel()) { 1371 w += sliderSpace; 1372 } 1373 1374 // Add space for eyedropper button if enabled 1375 if (thisObj.extendedPicking && thisObj.eyedropperButton) { 1376 w += 32; // 24px button + 8px margin 1377 } 1378 1379 var pal = jsc.getPaletteDims(thisObj, w); 1380 1381 if (pal.height) { 1382 h += pal.height + thisObj.padding; 1383 } 1384 if (thisObj.closeButton) { 1385 h += 2 * thisObj.controlBorderWidth + thisObj.padding + thisObj.buttonHeight; 1386 } 1387 1388 var pW = w + (2 * thisObj.padding); 1389 var pH = h + (2 * thisObj.padding); 1390 1391 return { 1392 contentW: w, 1393 contentH: h, 1394 paddedW: pW, 1395 paddedH: pH, 1396 borderW: pW + (2 * thisObj.borderWidth), 1397 borderH: pH + (2 * thisObj.borderWidth), 1398 palette: pal, 1399 }; 1400 }, 1401 1402 1403 getPaletteDims : function (thisObj, width) { 1404 var cols = 0, rows = 0, cellW = 0, cellH = 0, height = 0; 1405 var sampleCount = thisObj._palette ? thisObj._palette.length : 0; 1406 1407 if (sampleCount) { 1408 cols = thisObj.paletteCols; 1409 rows = cols > 0 ? Math.ceil(sampleCount / cols) : 0; 1410 1411 // color sample's dimensions (includes border) 1412 cellW = Math.max(1, Math.floor((width - ((cols - 1) * thisObj.paletteSpacing)) / cols)); 1413 cellH = thisObj.paletteHeight ? Math.min(thisObj.paletteHeight, cellW) : cellW; 1414 } 1415 1416 if (rows) { 1417 height = 1418 rows * cellH + 1419 (rows - 1) * thisObj.paletteSpacing; 1420 } 1421 1422 return { 1423 cols: cols, 1424 rows: rows, 1425 cellW: cellW, 1426 cellH: cellH, 1427 width: width, 1428 height: height, 1429 }; 1430 }, 1431 1432 1433 getControlPadding : function (thisObj) { 1434 return Math.max( 1435 thisObj.padding / 2, 1436 (2 * thisObj.pointerBorderWidth + thisObj.pointerThickness) - thisObj.controlBorderWidth 1437 ); 1438 }, 1439 1440 1441 getPadYChannel : function (thisObj) { 1442 switch (thisObj.mode.charAt(1).toLowerCase()) { 1443 case 'v': return 'v'; break; 1444 } 1445 return 's'; 1446 }, 1447 1448 1449 getSliderChannel : function (thisObj) { 1450 if (thisObj.mode.length > 2) { 1451 switch (thisObj.mode.charAt(2).toLowerCase()) { 1452 case 's': return 's'; break; 1453 case 'v': return 'v'; break; 1454 } 1455 } 1456 return null; 1457 }, 1458 1459 1460 // calls function specified in picker's property 1461 triggerCallback : function (thisObj, prop) { 1462 if (!thisObj[prop]) { 1463 return; // callback func not specified 1464 } 1465 var callback = null; 1466 1467 if (typeof thisObj[prop] === 'string') { 1468 // string with code 1469 try { 1470 callback = new Function (thisObj[prop]); 1471 } catch (e) { 1472 console.error(e); 1473 } 1474 } else { 1475 // function 1476 callback = thisObj[prop]; 1477 } 1478 1479 if (callback) { 1480 callback.call(thisObj); 1481 } 1482 }, 1483 1484 1485 // Triggers a color change related event(s) on all picker instances. 1486 // It is possible to specify multiple events separated with a space. 1487 triggerGlobal : function (eventNames) { 1488 var inst = jsc.getInstances(); 1489 for (var i = 0; i < inst.length; i += 1) { 1490 inst[i].trigger(eventNames); 1491 } 1492 }, 1493 1494 1495 _pointerMoveEvent : { 1496 mouse: 'mousemove', 1497 touch: 'touchmove' 1498 }, 1499 _pointerEndEvent : { 1500 mouse: 'mouseup', 1501 touch: 'touchend' 1502 }, 1503 1504 1505 _pointerOrigin : null, 1506 1507 1508 onDocumentKeyUp : function (e) { 1509 var key = jsc.eventKey(e); 1510 if (['Tab', 'Escape'].indexOf(key) !== -1) { 1511 if (jsc.picker && jsc.picker.owner) { 1512 jsc.picker.owner.tryHide(); 1513 } 1514 } 1515 1516 // Check for eyedropper activation 1517 if (jsc.picker && jsc.picker.owner && jsc.picker.owner.extendedPicking) { 1518 var eyedropperKey = jsc.picker.owner.eyedropperKey || 'e'; 1519 if (key.toLowerCase() === eyedropperKey.toLowerCase() || e.key === eyedropperKey) { 1520 jsc.activateEyedropper(jsc.picker.owner); 1521 jsc.preventDefault(e); 1522 } 1523 } 1524 }, 1525 1526 1527 onWindowResize : function (e) { 1528 jsc.redrawPosition(); 1529 }, 1530 1531 1532 onWindowScroll : function (e) { 1533 jsc.redrawPosition(); 1534 }, 1535 1536 1537 onParentScroll : function (e) { 1538 // hide the picker when one of the parent elements is scrolled 1539 if (jsc.picker && jsc.picker.owner) { 1540 jsc.picker.owner.tryHide(); 1541 } 1542 }, 1543 1544 1545 onDocumentMouseDown : function (e) { 1546 var target = e.target || e.srcElement; 1547 1548 // console.log('Document mousedown event - target:', target); 1549 if (target.jscolor && target.jscolor instanceof jsc.pub) { // clicked targetElement -> show picker 1550 console.log('Target has jscolor instance:', target.jscolor); 1551 if (target.jscolor.showOnClick && !target.disabled) { 1552 console.log('Showing picker for:', target); 1553 target.jscolor.show(); 1554 } 1555 } else if (jsc.getData(target, 'gui')) { // clicked jscolor's GUI element 1556 var control = jsc.getData(target, 'control'); 1557 if (control) { 1558 // jscolor's control 1559 jsc.onControlPointerStart(e, target, jsc.getData(target, 'control'), 'mouse'); 1560 } 1561 } else { 1562 // mouse is outside the picker's controls -> hide the color picker! 1563 if (jsc.picker && jsc.picker.owner) { 1564 jsc.picker.owner.tryHide(); 1565 } 1566 } 1567 }, 1568 1569 1570 onPickerTouchStart : function (e) { 1571 var target = e.target || e.srcElement; 1572 1573 if (jsc.getData(target, 'control')) { 1574 jsc.onControlPointerStart(e, target, jsc.getData(target, 'control'), 'touch'); 1575 } 1576 }, 1577 1578 1579 onControlPointerStart : function (e, target, controlName, pointerType) { 1580 var thisObj = jsc.getData(target, 'instance'); 1581 1582 jsc.preventDefault(e); 1583 1584 var registerDragEvents = function (doc, offset) { 1585 jsc.attachGroupEvent('drag', doc, jsc._pointerMoveEvent[pointerType], 1586 jsc.onDocumentPointerMove(e, target, controlName, pointerType, offset)); 1587 jsc.attachGroupEvent('drag', doc, jsc._pointerEndEvent[pointerType], 1588 jsc.onDocumentPointerEnd(e, target, controlName, pointerType)); 1589 }; 1590 1591 registerDragEvents(window.document, [0, 0]); 1592 1593 if (window.parent && window.frameElement) { 1594 var rect = window.frameElement.getBoundingClientRect(); 1595 var ofs = [-rect.left, -rect.top]; 1596 registerDragEvents(window.parent.window.document, ofs); 1597 } 1598 1599 var abs = jsc.getAbsPointerPos(e); 1600 var rel = jsc.getRelPointerPos(e); 1601 jsc._pointerOrigin = { 1602 x: abs.x - rel.x, 1603 y: abs.y - rel.y 1604 }; 1605 1606 switch (controlName) { 1607 case 'pad': 1608 // if the value slider is at the bottom, move it up 1609 if (jsc.getSliderChannel(thisObj) === 'v' && thisObj.channels.v === 0) { 1610 thisObj.fromHSVA(null, null, 100, null); 1611 }
1612 jsc.setPad(thisObj, e, 0, 0); 1613 break; 1614 1615 case 'sld': 1616 jsc.setSld(thisObj, e, 0); 1617 break; 1618 1619 case 'asld': 1620 jsc.setASld(thisObj, e, 0); 1621 break; 1622 } 1623 thisObj.trigger('input'); 1624 }, 1625 1626 1627 onDocumentPointerMove : function (e, target, controlName, pointerType, offset) { 1628 return function (e) { 1629 var thisObj = jsc.getData(target, 'instance'); 1630 switch (controlName) { 1631 case 'pad': 1632 jsc.setPad(thisObj, e, offset[0], offset[1]); 1633 break; 1634 1635 case 'sld': 1636 jsc.setSld(thisObj, e, offset[1]); 1637 break; 1638 1639 case 'asld': 1640 jsc.setASld(thisObj, e, offset[1]); 1641 break; 1642 } 1643 thisObj.trigger('input'); 1644 } 1645 }, 1646 1647 1648 onDocumentPointerEnd : function (e, target, controlName, pointerType) { 1649 return function (e) { 1650 var thisObj = jsc.getData(target, 'instance'); 1651 jsc.detachGroupEvents('drag'); 1652 1653 // Always trigger changes AFTER detaching outstanding mouse handlers, 1654 // in case some color change that occured in user-defined onChange/onInput handler 1655 // intruded into current mouse events 1656 thisObj.trigger('input'); 1657 thisObj.trigger('change'); 1658 }; 1659 }, 1660 1661 1662 onPaletteSampleClick : function (e) { 1663 var target = e.currentTarget; 1664 var thisObj = jsc.getData(target, 'instance'); 1665 var color = jsc.getData(target, 'color'); 1666 1667 // when format is flexible, use the original format of this color sample 1668 if (thisObj.format.toLowerCase() === 'any') { 1669 thisObj._setFormat(color.format); // adapt format 1670 if (!jsc.isAlphaFormat(thisObj.getFormat())) { 1671 color.rgba[3] = 1.0; // when switching to a format that doesn't support alpha, set full opacity 1672 } 1673 } 1674 1675 // if this color doesn't specify alpha, use alpha of 1.0 (if applicable) 1676 if (color.rgba[3] === null) { 1677 if (thisObj.paletteSetsAlpha === true || (thisObj.paletteSetsAlpha === 'auto' && thisObj._paletteHasTransparency)) { 1678 color.rgba[3] = 1.0; 1679 } 1680 } 1681 1682 thisObj.fromRGBA.apply(thisObj, color.rgba); 1683 1684 thisObj.trigger('input'); 1685 thisObj.trigger('change'); 1686 1687 if (thisObj.hideOnPaletteClick) { 1688 thisObj.hide(); 1689 } 1690 }, 1691 1692 1693 setPad : function (thisObj, e, ofsX, ofsY) { 1694 var pointerAbs = jsc.getAbsPointerPos(e); 1695 var x = ofsX + pointerAbs.x - jsc._pointerOrigin.x - thisObj.padding - thisObj.controlBorderWidth; 1696 var y = ofsY + pointerAbs.y - jsc._pointerOrigin.y - thisObj.padding - thisObj.controlBorderWidth; 1697 1698 var xVal = x * (360 / (thisObj.width - 1)); 1699 var yVal = 100 - (y * (100 / (thisObj.height - 1))); 1700 1701 switch (jsc.getPadYChannel(thisObj)) { 1702 case 's': thisObj.fromHSVA(xVal, yVal, null, null); break; 1703 case 'v': thisObj.fromHSVA(xVal, null, yVal, null); break; 1704 } 1705 }, 1706 1707 1708 setSld : function (thisObj, e, ofsY) { 1709 var pointerAbs = jsc.getAbsPointerPos(e); 1710 var y = ofsY + pointerAbs.y - jsc._pointerOrigin.y - thisObj.padding - thisObj.controlBorderWidth; 1711 var yVal = 100 - (y * (100 / (thisObj.height - 1))); 1712 1713 switch (jsc.getSliderChannel(thisObj)) { 1714 case 's': thisObj.fromHSVA(null, yVal, null, null); break; 1715 case 'v': thisObj.fromHSVA(null, null, yVal, null); break; 1716 } 1717 }, 1718 1719 1720 setASld : function (thisObj, e, ofsY) { 1721 var pointerAbs = jsc.getAbsPointerPos(e); 1722 var y = ofsY + pointerAbs.y - jsc._pointerOrigin.y - thisObj.padding - thisObj.controlBorderWidth; 1723 var yVal = 1.0 - (y * (1.0 / (thisObj.height - 1))); 1724 1725 if (yVal < 1.0) { 1726 // if format is flexible and the current format doesn't support alpha, switch to a suitable one 1727 var fmt = thisObj.getFormat(); 1728 if (thisObj.format.toLowerCase() === 'any' && !jsc.isAlphaFormat(fmt)) { 1729 thisObj._setFormat(fmt === 'hex' ? 'hexa' : 'rgba'); 1730 } 1731 } 1732 1733 thisObj.fromHSVA(null, null, null, yVal); 1734 }, 1735 1736 1737 createPadCanvas : function () { 1738 1739 var ret = { 1740 elm: null, 1741 draw: null 1742 }; 1743 1744 var canvas = jsc.createEl('canvas'); 1745 var ctx = canvas.getContext('2d'); 1746 1747 var drawFunc = function (width, height, type) { 1748 canvas.width = width; 1749 canvas.height = height; 1750 1751 ctx.clearRect(0, 0, canvas.width, canvas.height); 1752 1753 var hGrad = ctx.createLinearGradient(0, 0, canvas.width, 0); 1754 hGrad.addColorStop(0 / 6, '#F00'); 1755 hGrad.addColorStop(1 / 6, '#FF0'); 1756 hGrad.addColorStop(2 / 6, '#0F0'); 1757 hGrad.addColorStop(3 / 6, '#0FF'); 1758 hGrad.addColorStop(4 / 6, '#00F'); 1759 hGrad.addColorStop(5 / 6, '#F0F'); 1760 hGrad.addColorStop(6 / 6, '#F00'); 1761 1762 ctx.fillStyle = hGrad; 1763 ctx.fillRect(0, 0, canvas.width, canvas.height); 1764 1765 var vGrad = ctx.createLinearGradient(0, 0, 0, canvas.height); 1766 switch (type.toLowerCase()) { 1767 case 's': 1768 vGrad.addColorStop(0, 'rgba(255,255,255,0)'); 1769 vGrad.addColorStop(1, 'rgba(255,255,255,1)'); 1770 break; 1771 case 'v': 1772 vGrad.addColorStop(0, 'rgba(0,0,0,0)'); 1773 vGrad.addColorStop(1, 'rgba(0,0,0,1)'); 1774 break; 1775 } 1776 ctx.fillStyle = vGrad; 1777 ctx.fillRect(0, 0, canvas.width, canvas.height); 1778 }; 1779 1780 ret.elm = canvas; 1781 ret.draw = drawFunc; 1782 1783 return ret; 1784 }, 1785 1786 1787 createSliderGradient : function () { 1788 1789 var ret = { 1790 elm: null, 1791 draw: null 1792 }; 1793 1794 var canvas = jsc.createEl('canvas'); 1795 var ctx = canvas.getContext('2d'); 1796 1797 var drawFunc = function (width, height, color1, color2) { 1798 canvas.width = width; 1799 canvas.height = height; 1800 1801 ctx.clearRect(0, 0, canvas.width, canvas.height); 1802 1803 var grad = ctx.createLinearGradient(0, 0, 0, canvas.height); 1804 grad.addColorStop(0, color1); 1805 grad.addColorStop(1, color2); 1806 1807 ctx.fillStyle = grad; 1808 ctx.fillRect(0, 0, canvas.width, canvas.height); 1809 }; 1810 1811 ret.elm = canvas; 1812 ret.draw = drawFunc; 1813 1814 return ret; 1815 }, 1816 1817 1818 createASliderGradient : function () { 1819 1820 var ret = { 1821 elm: null, 1822 draw: null 1823 }; 1824 1825 var canvas = jsc.createEl('canvas'); 1826 var ctx = canvas.getContext('2d'); 1827 1828 var drawFunc = function (width, height, color) { 1829 canvas.width = width; 1830 canvas.height = height; 1831 1832 ctx.clearRect(0, 0, canvas.width, canvas.height); 1833 1834 var sqSize = canvas.width / 2; 1835 var sqColor1 = jsc.pub.chessboardColor1; 1836 var sqColor2 = jsc.pub.chessboardColor2; 1837 1838 // dark gray background 1839 ctx.fillStyle = sqColor1; 1840 ctx.fillRect(0, 0, canvas.width, canvas.height); 1841 1842 if (sqSize > 0) { // to avoid infinite loop 1843 for (var y = 0; y < canvas.height; y += sqSize * 2) { 1844 // light gray squares 1845 ctx.fillStyle = sqColor2; 1846 ctx.fillRect(0, y, sqSize, sqSize); 1847 ctx.fillRect(sqSize, y + sqSize, sqSize, sqSize); 1848 } 1849 } 1850 1851 var grad = ctx.createLinearGradient(0, 0, 0, canvas.height); 1852 grad.addColorStop(0, color); 1853 grad.addColorStop(1, 'rgba(0,0,0,0)'); 1854 1855 ctx.fillStyle = grad; 1856 ctx.fillRect(0, 0, canvas.width, canvas.height); 1857 }; 1858 1859 ret.elm = canvas; 1860 ret.draw = drawFunc; 1861 1862 return ret; 1863 }, 1864 1865 1866 BoxShadow : (function () { 1867 var BoxShadow = function (hShadow, vShadow, blur, spread, color, inset) {
1868 this.hShadow = hShadow; 1869 this.vShadow = vShadow; 1870 this.blur = blur; 1871 this.spread = spread; 1872 this.color = color; 1873 this.inset = !!inset; 1874 }; 1875 1876 BoxShadow.prototype.toString = function () { 1877 var vals = [ 1878 Math.round(this.hShadow) + 'px', 1879 Math.round(this.vShadow) + 'px', 1880 Math.round(this.blur) + 'px', 1881 Math.round(this.spread) + 'px', 1882 this.color 1883 ]; 1884 if (this.inset) { 1885 vals.push('inset'); 1886 } 1887 return vals.join(' '); 1888 }; 1889 1890 return BoxShadow; 1891 })(), 1892 1893 1894 flags : { 1895 leaveValue : 1 << 0, 1896 leaveAlpha : 1 << 1, 1897 leavePreview : 1 << 2, 1898 }, 1899 1900 1901 enumOpts : { 1902 format: ['auto', 'any', 'hex', 'hexa', 'rgb', 'rgba'], 1903 previewPosition: ['left', 'right'], 1904 mode: ['hsv', 'hvs', 'hs', 'hv'], 1905 position: ['left', 'right', 'top', 'bottom'], 1906 alphaChannel: ['auto', true, false], 1907 paletteSetsAlpha: ['auto', true, false], 1908 }, 1909 1910 1911 deprecatedOpts : { 1912 // <old_option>: <new_option> (<new_option> can be null) 1913 'styleElement': 'previewElement', 1914 'onFineChange': 'onInput', 1915 'overwriteImportant': 'forceStyle', 1916 'closable': 'closeButton', 1917 'insetWidth': 'controlBorderWidth', 1918 'insetColor': 'controlBorderColor', 1919 'refine': null, 1920 }, 1921 1922 1923 docsRef : ' ' + 'See https://jscolor.com/docs/', 1924 1925 1926 // 1927 // Usage: 1928 // var myPicker = new JSColor(<targetElement> [, <options>]) 1929 // 1930 // (constructor is accessible via both 'jscolor' and 'JSColor' name) 1931 // 1932 1933 pub : function (targetElement, opts) { 1934 1935 var THIS = this; 1936 1937 if (!opts) { 1938 opts = {}; 1939 } 1940 1941 this.channels = { 1942 r: 255, // red [0-255] 1943 g: 255, // green [0-255] 1944 b: 255, // blue [0-255] 1945 h: 0, // hue [0-360] 1946 s: 0, // saturation [0-100] 1947 v: 100, // value (brightness) [0-100] 1948 a: 1.0, // alpha (opacity) [0.0 - 1.0] 1949 }; 1950 1951 // General options 1952 // 1953 this.format = 'auto'; // 'auto' | 'any' | 'hex' | 'hexa' | 'rgb' | 'rgba' - Format of the input/output value 1954 this.value = undefined; // INITIAL color value in any supported format. To change it later, use method fromString(), fromHSVA(), fromRGBA() or channel() 1955 this.alpha = undefined; // INITIAL alpha value. To change it later, call method channel('A', <value>) 1956 this.random = false; // whether to randomize the initial color. Either true | false, or an array of ranges: [minV, maxV, minS, maxS, minH, maxH, minA, maxA] 1957 this.onChange = undefined; // called when color changes. Value can be either a function or a string with JS code. 1958 this.onInput = undefined; // called repeatedly as the color is being changed, e.g. while dragging a slider. Value can be either a function or a string with JS code. 1959 this.valueElement = undefined; // element that will be used to display and input the color value 1960 this.alphaElement = undefined; // element that will be used to display and input the alpha (opacity) value 1961 this.previewElement = undefined; // element that will preview the picked color using CSS background 1962 this.previewPosition = 'left'; // 'left' | 'right' - position of the color preview in previewElement 1963 this.previewSize = 32; // (px) width of the color preview displayed in previewElement 1964 this.previewPadding = 8; // (px) space between color preview and content of the previewElement 1965 this.required = true; // whether the associated text input must always contain a color value. If false, the input can be left empty. 1966 this.hash = true; // whether to prefix the HEX color code with # symbol (only applicable for HEX format) 1967 this.uppercase = true; // whether to show the HEX color code in upper case (only applicable for HEX format) 1968 this.forceStyle = true; // whether to overwrite CSS style of the previewElement using !important flag 1969 1970 // Color Picker options 1971 // 1972 this.width = 181; // width of the color spectrum (in px) 1973 this.height = 101; // height of the color spectrum (in px) 1974 this.mode = 'HSV'; // 'HSV' | 'HVS' | 'HS' | 'HV' - layout of the color picker controls 1975 this.alphaChannel = 'auto'; // 'auto' | true | false - if alpha channel is enabled, the alpha slider will be visible. If 'auto', it will be determined according to color format 1976 this.position = 'bottom'; // 'left' | 'right' | 'top' | 'bottom' - position relative to the target element 1977 this.smartPosition = true; // automatically change picker position when there is not enough space for it 1978 this.showOnClick = true; // whether to show the picker when user clicks its target element 1979 this.hideOnLeave = true; // whether to automatically hide the picker when user leaves its target element (e.g. upon clicking the document) 1980 this.palette = []; // colors to be displayed in the palette, specified as an array or a string of space separated color values (in any supp
1980orted format) 1981 this.paletteCols = 10; // number of columns in the palette 1982 this.paletteSetsAlpha = 'auto'; // 'auto' | true | false - if true, palette colors that don't specify alpha will set alpha to 1.0 1983 this.paletteHeight = 16; // maximum height (px) of a row in the palette 1984 this.paletteSpacing = 4; // distance (px) between color samples in the palette 1985 this.hideOnPaletteClick = false; // when set to true, clicking the palette will also hide the color picker 1986 this.sliderSize = 16; // px 1987 this.crossSize = 8; // px 1988 this.closeButton = false; // whether to display the Close button 1989 this.closeText = 'Close'; 1990 this.buttonColor = 'rgba(0,0,0,1)'; // CSS color 1991 this.buttonHeight = 18; // px 1992 this.padding = 12; // px 1993 this.backgroundColor = 'rgba(255,255,255,1)'; // CSS color 1994 this.borderWidth = 1; // px 1995 this.borderColor = 'rgba(187,187,187,1)'; // CSS color 1996 this.borderRadius = 8; // px 1997 this.controlBorderWidth = 1; // px 1998 this.controlBorderColor = 'rgba(187,187,187,1)'; // CSS color 1999 this.shadow = true; // whether to display a shadow 2000 this.shadowBlur = 15; // px 2001 this.shadowColor = 'rgba(0,0,0,0.2)'; // CSS color 2002 this.pointerColor = 'rgba(76,76,76,1)'; // CSS color 2003 this.pointerBorderWidth = 1; // px 2004 this.pointerBorderColor = 'rgba(255,255,255,1)'; // CSS color 2005 this.pointerThickness = 2; // px 2006 this.zIndex = 5000; 2007 this.container = undefined; // where to append the color picker (BODY element by default) 2008 2009 // Extended Color Picking options 2010 // 2011 this.extendedPicking = true; // whether to enable extended color picking from entire DOM 2012 this.eyedropperKey = 'e'; // key to activate eyedropper mode (default: 'e') 2013 this.eyedropperCursor = 'crosshair'; // cursor style during eyedropper mode 2014 this.eyedropperButton = true; // whether to show the eyedropper button in the picker 2015 2016 // Experimental 2017 // 2018 this.minS = 0; // min allowed saturation (0 - 100) 2019 this.maxS = 100; // max allowed saturation (0 - 100) 2020 this.minV = 0; // min allowed value (brightness) (0 - 100) 2021 this.maxV = 100; // max allowed value (brightness) (0 - 100) 2022 this.minA = 0.0; // min allowed alpha (opacity) (0.0 - 1.0) 2023 this.maxA = 1.0; // max allowed alpha (opacity) (0.0 - 1.0) 2024 2025 2026 // Getter: option(name) 2027 // Setter: option(name, value) 2028 // option({name:value, ...}) 2029 // 2030 this.option = function () { 2031 if (!arguments.length) { 2032 throw new Error('No option specified'); 2033 } 2034 2035 if (arguments.length === 1 && typeof arguments[0] === 'string') { 2036 // getting a single option 2037 try { 2038 return getOption(arguments[0]); 2039 } catch (e) { 2040 console.warn(e); 2041 } 2042 return false; 2043 2044 } else if (arguments.length >= 2 && typeof arguments[0] === 'string') { 2045 // setting a single option 2046 try { 2047 if (!setOption(arguments[0], arguments[1])) { 2048 return false; 2049 } 2050 } catch (e) { 2051 console.warn(e); 2052 return false; 2053 } 2054 this.redraw(); // immediately redraws the picker, if it's displayed 2055 this.exposeColor(); // in case some preview-related or format-related option was changed 2056 return true; 2057 2058 } else if (arguments.length === 1 && typeof arguments[0] === 'object') { 2059 // setting multiple options 2060 var opts = arguments[0]; 2061 var success = true; 2062 for (var opt in opts) { 2063 if (opts.hasOwnProperty(opt)) { 2064 try { 2065 if (!setOption(opt, opts[opt])) { 2066 success = false; 2067 } 2068 } catch (e) { 2069 console.warn(e); 2070 success = false; 2071 } 2072 } 2073 } 2074 this.redraw(); // immediately redraws the picker, if it's displayed 2075 this.exposeColor(); // in case some preview-related or format-related option was changed 2076 return success; 2077 } 2078 2079 throw new Error('Invalid arguments'); 2080 } 2081 2082 2083 // Getter: channel(name) 2084 // Setter: channel(name, value) 2085 // 2086 this.channel = function (name, value) { 2087 if (typeof name !== 'string') { 2088 throw new Error('Invalid value for channel name: ' + name); 2089 } 2090 2091 if (value === undefined) { 2092 // getting channel value 2093 if (!this.channels.hasOwnProperty(name.toLowerCase())) { 2094 console.warn('Getting unknown channel: ' + name); 2095 return false; 2096 } 2097 return this.channels[name.toLowerCase()]; 2098 2099 } else { 2100 // setting channel value 2101 var res = false; 2102 switch (name.toLowerCase()) { 2103 case 'r': res = this.fromRGBA(value, null, null, null); break; 2104 case 'g': res = this.fromRGBA(null, value, null, null); break; 2105 case 'b': res = this.fromRGBA(null, null, value, null); break; 2106 case 'h': res = this.fromHSVA(value, null, null, null); break; 2107 case 's': res = this.fromHSVA(null, value, null, null); break; 2108 case 'v': res = this.fromHSVA(null, null, value, null); break; 2109 case 'a': res = this.fromHSVA(null, null, null, value); break; 2110 default: 2111 console.warn('Setting unknown channel: ' + name); 2112 return false; 2113 } 2114 if (res) { 2115 this.redraw(); // immediately redraws the picker, if it's displayed 2116 return true; 2117 } 2118 } 2119 2120 return false; 2121 } 2122 2123
2124 // Triggers given input event(s) by: 2125 // - executing on<Event> callback specified as picker's option 2126 // - triggering standard DOM event listeners attached to the value element 2127 // 2128 // It is possible to specify multiple events separated with a space. 2129 // 2130 this.trigger = function (eventNames) { 2131 var evs = jsc.strList(eventNames); 2132 for (var i = 0; i < evs.length; i += 1) { 2133 var ev = evs[i].toLowerCase(); 2134 2135 // trigger a callback 2136 var callbackProp = null; 2137 switch (ev) { 2138 case 'input': callbackProp = 'onInput'; break; 2139 case 'change': callbackProp = 'onChange'; break; 2140 } 2141 if (callbackProp) { 2142 jsc.triggerCallback(this, callbackProp); 2143 } 2144 2145 // trigger standard DOM event listeners on the value element 2146 jsc.triggerInputEvent(this.valueElement, ev, true, true); 2147 } 2148 }; 2149 2150 2151 // h: 0-360 2152 // s: 0-100 2153 // v: 0-100 2154 // a: 0.0-1.0 2155 // 2156 this.fromHSVA = function (h, s, v, a, flags) { // null = don't change 2157 if (h === undefined) { h = null; } 2158 if (s === undefined) { s = null; } 2159 if (v === undefined) { v = null; } 2160 if (a === undefined) { a = null; } 2161 2162 if (h !== null) { 2163 if (isNaN(h)) { return false; } 2164 this.channels.h = Math.max(0, Math.min(360, h)); 2165 } 2166 if (s !== null) { 2167 if (isNaN(s)) { return false; } 2168 this.channels.s = Math.max(0, Math.min(100, this.maxS, s), this.minS); 2169 } 2170 if (v !== null) { 2171 if (isNaN(v)) { return false; } 2172 this.channels.v = Math.max(0, Math.min(100, this.maxV, v), this.minV); 2173 } 2174 if (a !== null) { 2175 if (isNaN(a)) { return false; } 2176 this.channels.a = this.hasAlphaChannel() ? 2177 Math.max(0, Math.min(1, this.maxA, a), this.minA) : 2178 1.0; // if alpha channel is disabled, the color should stay 100% opaque 2179 } 2180 2181 var rgb = jsc.HSV_RGB( 2182 this.channels.h, 2183 this.channels.s, 2184 this.channels.v 2185 ); 2186 this.channels.r = rgb[0]; 2187 this.channels.g = rgb[1]; 2188 this.channels.b = rgb[2]; 2189 2190 this.exposeColor(flags); 2191 return true; 2192 }; 2193 2194 2195 // r: 0-255 2196 // g: 0-255 2197 // b: 0-255 2198 // a: 0.0-1.0 2199 // 2200 this.fromRGBA = function (r, g, b, a, flags) { // null = don't change 2201 if (r === undefined) { r = null; } 2202 if (g === undefined) { g = null; } 2203 if (b === undefined) { b = null; } 2204 if (a === undefined) { a = null; } 2205 2206 if (r !== null) { 2207 if (isNaN(r)) { return false; } 2208 r = Math.max(0, Math.min(255, r)); 2209 } 2210 if (g !== null) { 2211 if (isNaN(g)) { return false; } 2212 g = Math.max(0, Math.min(255, g)); 2213 } 2214 if (b !== null) { 2215 if (isNaN(b)) { return false; } 2216 b = Math.max(0, Math.min(255, b)); 2217 } 2218 if (a !== null) { 2219 if (isNaN(a)) { return false; } 2220 this.channels.a = this.hasAlphaChannel() ? 2221 Math.max(0, Math.min(1, this.maxA, a), this.minA) : 2222 1.0; // if alpha channel is disabled, the color should stay 100% opaque 2223 } 2224 2225 var hsv = jsc.RGB_HSV( 2226 r===null ? this.channels.r : r, 2227 g===null ? this.channels.g : g, 2228 b===null ? this.channels.b : b 2229 ); 2230 if (hsv[0] !== null) { 2231 this.channels.h = Math.max(0, Math.min(360, hsv[0])); 2232 } 2233 if (hsv[2] !== 0) { // fully black color stays black through entire saturation range, so let's not change saturation 2234 this.channels.s = Math.max(0, this.minS, Math.min(100, this.maxS, hsv[1])); 2235 } 2236 this.channels.v = Math.max(0, this.minV, Math.min(100, this.maxV, hsv[2])); 2237 2238 // update RGB according to final HSV, as some values might be trimmed 2239 var rgb = jsc.HSV_RGB(this.channels.h, this.channels.s, this.channels.v); 2240 this.channels.r = rgb[0]; 2241 this.channels.g = rgb[1]; 2242 this.channels.b = rgb[2]; 2243 2244 this.exposeColor(flags); 2245 return true; 2246 }; 2247 2248 2249 // DEPRECATED. Use .fromHSVA() instead 2250 // 2251 this.fromHSV = function (h, s, v, flags) { 2252 console.warn('fromHSV() method is DEPRECATED. Using fromHSVA() instead.' + jsc.docsRef); 2253 return this.fromHSVA(h, s, v, null, flags); 2254 }; 2255 2256 2257 // DEPRECATED. Use .fromRGBA() instead 2258 // 2259 this.fromRGB = function (r, g, b, flags) { 2260 console.warn('fromRGB() method is DEPRECATED. Using fromRGBA() instead.' + jsc.docsRef); 2261 return this.fromRGBA(r, g, b, null, flags); 2262 }; 2263 2264 2265 this.fromString = function (str, flags) { 2266 if (!this.required && str.trim() === '') { 2267 // setting empty string to an optional color input 2268 this.setPreviewElementBg(null); 2269 this.setValueElementValue(''); 2270 return true; 2271 } 2272 2273 var color = jsc.parseColorString(str); 2274 if (!color) { 2275 return false; // could not parse 2276 } 2277 if (this.format.toLowerCase() === 'any') { 2278 this._setFormat(color.format); // adapt format 2279 if (!jsc.isAlphaFormat(this.getFormat())) { 2280 color.rgba[3] = 1.0; // when switching to a format that doesn't support alpha, set full opacity 2281 } 2282 } 2283 this.fromRGBA( 2284 color.rgba[0], 2285 color.rgba[1], 2286 color.rgba[2], 2287 color.rgba[3], 2288 flags 2289 ); 2290 return true; 2291 }; 2292 2293 2294 this.randomize = function (minV, maxV, minS, maxS, minH, maxH, minA, maxA) { 2295 if (minV === undefined) { minV = 0; } 2296 if (maxV === undefined) { maxV = 100; } 2297 if (minS === undefined) { minS = 0; } 2298 if (maxS === undefined) { maxS = 100; } 2299 if (minH === undefined) { minH = 0; } 2300 if (maxH === undefined) { maxH = 359; } 2301 if (minA === undefined) { minA = 1; } 2302 if (maxA === undefined) { maxA = 1; } 2303 2304 this.fromHSVA( 2305 minH + Math.floor(Math.random() * (maxH - minH + 1)), 2306 minS + Math.floor(Math.random() * (maxS - minS + 1)), 2307 minV + Math.floor(Math.random() * (maxV - minV + 1)), 2308 ((100 * minA) + Math.floor(Math.random() * (100 * (maxA - minA) + 1))) / 100 2309 ); 2310 }; 2311 2312 2313 this.toString = function (format) { 2314 if (format === undefined) { 2315 format = this.getFormat(); // format not specified -> use the current format 2316 } 2317 switch (format.toLowerCase()) { 2318 case 'hex': return this.toHEXString(); break; 2319 case 'hexa': return this.toHEXAString(); break; 2320 case 'rgb': return this.toRGBString(); break; 2321 case 'rgba': return this.toRGBAString(); break; 2322 } 2323 return false; 2324 }; 2325 2326 2327 this.toHEXString = function () { 2328 return jsc.hexColor( 2329 this.channels.r, 2330 this.channels.g, 2331 this.channels.b 2332 ); 2333 }; 2334 2335 2336 this.toHEXAString = function () { 2337 return jsc.hexaColor( 2338 this.channels.r, 2339 this.channels.g, 2340 this.channels.b, 2341 this.channels.a 2342 ); 2343 }; 2344 2345 2346 this.toRGBString = function () { 2347 return jsc.rgbColor( 2348 this.channels.r, 2349 this.channels.g, 2350 this.channels.b 2351 ); 2352 }; 2353 2354 2355 this.toRGBAString = function () { 2356 return jsc.rgbaColor( 2357 this.channels.r, 2358 this.channels.g, 2359 this.channels.b, 2360 this.channels.a 2361 ); 2362 }; 2363 2364 2365 this.toGrayscale = function () { 2366 return ( 2367 0.213 * this.channels.r + 2368 0.715 * this.channels.g + 2369 0.072 * this.channels.b 2370 ); 2371 }; 2372 2373 2374 this.toCanvas = function () { 2375 return jsc.genColorPreviewCanvas(this.toRGBAString()).canvas; 2376 }; 2377 2378 2379 this.toDataURL = function () { 2380 return this.toCanvas().toDataURL(); 2381 }; 2382 2383
2384 this.toBackground = function () { 2385 return jsc.pub.background(this.toRGBAString()); 2386 }; 2387 2388 2389 this.isLight = function () { 2390 return this.toGrayscale() > 255 / 2; 2391 }; 2392 2393 2394 this.hide = function () { 2395 if (isPickerOwner()) { 2396 detachPicker(); 2397 } 2398 }; 2399 2400 2401 this.show = function () { 2402 drawPicker(); 2403 }; 2404 2405 2406 this.redraw = function () { 2407 if (isPickerOwner()) { 2408 drawPicker(); 2409 } 2410 }; 2411 2412 2413 this.getFormat = function () { 2414 return this._currentFormat; 2415 }; 2416 2417 2418 this._setFormat = function (format) { 2419 this._currentFormat = format.toLowerCase(); 2420 }; 2421 2422 2423 this.hasAlphaChannel = function () { 2424 if (this.alphaChannel === 'auto') { 2425 return ( 2426 this.format.toLowerCase() === 'any' || // format can change on the fly (e.g. from hex to rgba), so let's consider the alpha channel enabled 2427 jsc.isAlphaFormat(this.getFormat()) || // the current format supports alpha channel 2428 this.alpha !== undefined || // initial alpha value is set, so we're working with alpha channel 2429 this.alphaElement !== undefined // the alpha value is redirected, so we're working with alpha channel 2430 ); 2431 } 2432 2433 return this.alphaChannel; // the alpha channel is explicitly set 2434 }; 2435 2436 2437 this.processValueInput = function (str) { 2438 if (!this.fromString(str)) { 2439 // could not parse the color value - let's just expose the current color 2440 this.exposeColor(); 2441 } 2442 }; 2443 2444 2445 this.processAlphaInput = function (str) { 2446 if (!this.fromHSVA(null, null, null, parseFloat(str))) { 2447 // could not parse the alpha value - let's just expose the current color 2448 this.exposeColor(); 2449 } 2450 }; 2451 2452 2453 this.exposeColor = function (flags) { 2454 var colorStr = this.toString(); 2455 var fmt = this.getFormat(); 2456 2457 // reflect current color in data- attribute 2458 jsc.setDataAttr(this.targetElement, 'current-color', colorStr); 2459 2460 if (!(flags & jsc.flags.leaveValue) && this.valueElement) { 2461 if (fmt === 'hex' || fmt === 'hexa') { 2462 if (!this.uppercase) { colorStr = colorStr.toLowerCase(); } 2463 if (!this.hash) { colorStr = colorStr.replace(/^#/, ''); } 2464 } 2465 this.setValueElementValue(colorStr); 2466 } 2467 2468 if (!(flags & jsc.flags.leaveAlpha) && this.alphaElement) { 2469 var alphaVal = Math.round(this.channels.a * 100) / 100; 2470 this.setAlphaElementValue(alphaVal); 2471 } 2472 2473 if (!(flags & jsc.flags.leavePreview) && this.previewElement) { 2474 var previewPos = null; // 'left' | 'right' (null -> fill the entire element) 2475 2476 if ( 2477 jsc.isTextInput(this.previewElement) || // text input 2478 (jsc.isButton(this.previewElement) && !jsc.isButtonEmpty(this.previewElement)) // button with text 2479 ) { 2480 previewPos = this.previewPosition; 2481 } 2482 2483 this.setPreviewElementBg(this.toRGBAString()); 2484 } 2485 2486 if (isPickerOwner()) { 2487 redrawPad(); 2488 redrawSld(); 2489 redrawASld(); 2490 } 2491 }; 2492 2493 2494 this.setPreviewElementBg = function (color) { 2495 if (!this.previewElement) { 2496 return; 2497 } 2498 2499 var position = null; // color preview position: null | 'left' | 'right' 2500 var width = null; // color preview width: px | null = fill the entire element 2501 if ( 2502 jsc.isTextInput(this.previewElement) || // text input 2503 (jsc.isButton(this.previewElement) && !jsc.isButtonEmpty(this.previewElement)) // button with text 2504 ) { 2505 position = this.previewPosition; 2506 width = this.previewSize; 2507 } 2508 2509 var backgrounds = []; 2510 2511 if (!color) { 2512 // there is no color preview to display -> let's remove any previous background image 2513 backgrounds.push({ 2514 image: 'none', 2515 position: 'left top', 2516 size: 'auto', 2517 repeat: 'no-repeat', 2518 origin: 'padding-box', 2519 }); 2520 } else { 2521 // CSS gradient for background color preview 2522 backgrounds.push({ 2523 image: jsc.genColorPreviewGradient( 2524 color, 2525 position, 2526 width ? width - jsc.pub.previewSeparator.length : null 2527 ), 2528 position: 'left top', 2529 size: 'auto', 2530 repeat: position ? 'repeat-y' : 'repeat', 2531 origin: 'padding-box', 2532 }); 2533 2534 // data URL of generated PNG image with a gray transparency chessboard 2535 var preview = jsc.genColorPreviewCanvas( 2536 'rgba(0,0,0,0)', 2537 position ? {'left':'right', 'right':'left'}[position] : null, 2538 width, 2539 true 2540 ); 2541 backgrounds.push({ 2542 image: 'url(\'' + preview.canvas.toDataURL() + '\')', 2543 position: (position || 'left') + ' top', 2544 size: preview.width + 'px ' + preview.height + 'px', 2545 repeat: position ? 'repeat-y' : 'repeat', 2546 origin: 'padding-box', 2547 }); 2548 } 2549 2550 var bg = { 2551 image: [], 2552 position: [], 2553 size: [], 2554 repeat: [], 2555 origin: [], 2556 }; 2557 for (var i = 0; i < backgrounds.length; i += 1) { 2558 bg.image.push(backgrounds[i].image); 2559 bg.position.push(backgrounds[i].position); 2560 bg.size.push(backgrounds[i].size); 2561 bg.repeat.push(backgrounds[i].repeat); 2562 bg.origin.push(backgrounds[i].origin); 2563 } 2564 2565 // set previewElement's background-images 2566 var sty = { 2567 'background-image': bg.image.join(', '), 2568 'background-position': bg.position.join(', '), 2569 'background-size': bg.size.join(', '), 2570 'background-repeat': bg.repeat.join(', '), 2571 'background-origin': bg.origin.join(', '), 2572 }; 2573 jsc.setStyle(this.previewElement, sty, this.forceStyle); 2574 2575 2576 // set/restore previewElement's padding 2577 var padding = { 2578 left: null, 2579 right: null, 2580 }; 2581 if (position) { 2582 padding[position] = (this.previewSize + this.previewPadding) + 'px'; 2583 } 2584 2585 var sty = { 2586 'padding-left': padding.left, 2587 'padding-right': padding.right, 2588 }; 2589 jsc.setStyle(this.previewElement, sty, this.forceStyle, true); 2590 }; 2591 2592 2593 this.setValueElementValue = function (str) { 2594 if (this.valueElement) {
2595 if (jsc.nodeName(this.valueElement) === 'input') { 2596 this.valueElement.value = str; 2597 } else { 2598 this.valueElement.innerHTML = str; 2599 } 2600 } 2601 }; 2602 2603 2604 this.setAlphaElementValue = function (str) { 2605 if (this.alphaElement) { 2606 if (jsc.nodeName(this.alphaElement) === 'input') { 2607 this.alphaElement.value = str; 2608 } else { 2609 this.alphaElement.innerHTML = str; 2610 } 2611 } 2612 }; 2613 2614 2615 this._processParentElementsInDOM = function () { 2616 if (this._parentElementsProcessed) { return; } 2617 this._parentElementsProcessed = true; 2618 2619 var elm = this.targetElement; 2620 do { 2621 // If the target element or one of its parent nodes has fixed position, 2622 // then use fixed positioning instead 2623 var compStyle = jsc.getCompStyle(elm); 2624 if (compStyle.position && compStyle.position.toLowerCase() === 'fixed') { 2625 this.fixed = true; 2626 } 2627 2628 if (elm !== this.targetElement) { 2629 // Ensure to attach onParentScroll only once to each parent element 2630 // (multiple targetElements can share the same parent nodes) 2631 // 2632 // Note: It's not just offsetParents that can be scrollable, 2633 // that's why we loop through all parent nodes 2634 if (!jsc.getData(elm, 'hasScrollListener')) { 2635 elm.addEventListener('scroll', jsc.onParentScroll, false); 2636 jsc.setData(elm, 'hasScrollListener', true); 2637 } 2638 } 2639 } while ((elm = elm.parentNode) && jsc.nodeName(elm) !== 'body'); 2640 }; 2641 2642 2643 this.tryHide = function () { 2644 if (this.hideOnLeave) { 2645 this.hide(); 2646 } 2647 }; 2648 2649 2650 this.set__palette = function (val) { 2651 this.palette = val; 2652 this._palette = jsc.parsePaletteValue(val); 2653 this._paletteHasTransparency = jsc.containsTranparentColor(this._palette); 2654 }; 2655 2656 2657 function setOption (option, value) { 2658 if (typeof option !== 'string') { 2659 throw new Error('Invalid value for option name: ' + option); 2660 } 2661 2662 // enum option 2663 if (jsc.enumOpts.hasOwnProperty(option)) { 2664 if (typeof value === 'string') { // enum string values are case insensitive 2665 value = value.toLowerCase(); 2666 } 2667 if (jsc.enumOpts[option].indexOf(value) === -1) { 2668 throw new Error('Option \'' + option + '\' has invalid value: ' + value); 2669 } 2670 } 2671 2672 // deprecated option 2673 if (jsc.deprecatedOpts.hasOwnProperty(option)) { 2674 var oldOpt = option; 2675 var newOpt = jsc.deprecatedOpts[option]; 2676 if (newOpt) { 2677 // if we have a new name for this option, let's log a warning and use the new name 2678 console.warn('Option \'%s\' is DEPRECATED, using \'%s\' instead.' + jsc.docsRef, oldOpt, newOpt); 2679 option = newOpt; 2680 } else { 2681 // new name not available for the option 2682 throw new Error('Option \'' + option + '\' is DEPRECATED'); 2683 } 2684 } 2685 2686 var setter = 'set__' + option; 2687 2688 if (typeof THIS[setter] === 'function') { // a setter exists for this option 2689 THIS[setter](value); 2690 return true; 2691 2692 } else if (option in THIS) { // option exists as a property 2693 THIS[option] = value; 2694 return true; 2695 } 2696 2697 throw new Error('Unrecognized configuration option: ' + option); 2698 } 2699 2700 2701 function getOption (option) { 2702 if (typeof option !== 'string') { 2703 throw new Error('Invalid value for option name: ' + option); 2704 } 2705 2706 // deprecated option 2707 if (jsc.deprecatedOpts.hasOwnProperty(option)) { 2708 var oldOpt = option; 2709 var newOpt = jsc.deprecatedOpts[option]; 2710 if (newOpt) { 2711 // if we have a new name for this option, let's log a warning and use the new name 2712 console.warn('Option \'%s\' is DEPRECATED, using \'%s\' instead.' + jsc.docsRef, oldOpt, newOpt); 2713 option = newOpt; 2714 } else { 2715 // new name not available for the option 2716 throw new Error('Option \'' + option + '\' is DEPRECATED'); 2717 } 2718 } 2719 2720 var getter = 'get__' + option; 2721 2722 if (typeof THIS[getter] === 'function') { // a getter exists for this option 2723 return THIS[getter](value); 2724 2725 } else if (option in THIS) { // option exists as a property 2726 return THIS[option]; 2727 } 2728 2729 throw new Error('Unrecognized configuration option: ' + option); 2730 } 2731 2732 2733 function detachPicker () { 2734 jsc.removeClass(THIS.targetElement, jsc.pub.activeClassName); 2735 jsc.picker.wrap.parentNode.removeChild(jsc.picker.wrap); 2736 delete jsc.picker.owner; 2737 } 2738 2739 2740 function drawPicker () { 2741
2742 // At this point, when drawing the picker, we know what the parent elements are 2743 // and we can do all related DOM operations, such as registering events on them 2744 // or checking their positioning 2745 THIS._processParentElementsInDOM(); 2746 2747 if (!jsc.picker) { 2748 jsc.picker = { 2749 owner: null, // owner picker instance 2750 wrap : jsc.createEl('div'), 2751 box : jsc.createEl('div'), 2752 boxS : jsc.createEl('div'), // shadow area 2753 boxB : jsc.createEl('div'), // border 2754 pad : jsc.createEl('div'), 2755 padB : jsc.createEl('div'), // border 2756 padM : jsc.createEl('div'), // mouse/touch area 2757 padCanvas : jsc.createPadCanvas(), 2758 cross : jsc.createEl('div'), 2759 crossBY : jsc.createEl('div'), // border Y 2760 crossBX : jsc.createEl('div'), // border X 2761 crossLY : jsc.createEl('div'), // line Y 2762 crossLX : jsc.createEl('div'), // line X 2763 sld : jsc.createEl('div'), // slider 2764 sldB : jsc.createEl('div'), // border 2765 sldM : jsc.createEl('div'), // mouse/touch area 2766 sldGrad : jsc.createSliderGradient(), 2767 sldPtrS : jsc.createEl('div'), // slider pointer spacer 2768 sldPtrIB : jsc.createEl('div'), // slider pointer inner border 2769 sldPtrMB : jsc.createEl('div'), // slider pointer middle border 2770 sldPtrOB : jsc.createEl('div'), // slider pointer outer border 2771 asld : jsc.createEl('div'), // alpha slider 2772 asldB : jsc.createEl('div'), // border 2773 asldM : jsc.createEl('div'), // mouse/touch area 2774 asldGrad : jsc.createASliderGradient(), 2775 asldPtrS : jsc.createEl('div'), // slider pointer spacer 2776 asldPtrIB : jsc.createEl('div'), // slider pointer inner border 2777 asldPtrMB : jsc.createEl('div'), // slider pointer middle border 2778 asldPtrOB : jsc.createEl('div'), // slider pointer outer border 2779 pal : jsc.createEl('div'), // palette 2780 btn : jsc.createEl('div'), 2781 btnT : jsc.createEl('div'), // text 2782 eyedropperBtn : jsc.createEl('div'), // eyedropper button 2783 eyedropperBtnT : jsc.createEl('div'), // eyedropper button text/icon 2784 }; 2785 2786 jsc.picker.pad.appendChild(jsc.picker.padCanvas.elm); 2787 jsc.picker.padB.appendChild(jsc.picker.pad); 2788 jsc.picker.cross.appendChild(jsc.picker.crossBY); 2789 jsc.picker.cross.appendChild(jsc.picker.crossBX); 2790 jsc.picker.cross.appendChild(jsc.picker.crossLY); 2791 jsc.picker.cross.appendChild(jsc.picker.crossLX); 2792 jsc.picker.padB.appendChild(jsc.picker.cross); 2793 jsc.picker.box.appendChild(jsc.picker.padB); 2794 jsc.picker.box.appendChild(jsc.picker.padM); 2795 2796 jsc.picker.sld.appendChild(jsc.picker.sldGrad.elm); 2797 jsc.picker.sldB.appendChild(jsc.picker.sld); 2798 jsc.picker.sldB.appendChild(jsc.picker.sldPtrOB); 2799 jsc.picker.sldPtrOB.appendChild(jsc.picker.sldPtrMB); 2800 jsc.picker.sldPtrMB.appendChild(jsc.picker.sldPtrIB); 2801 jsc.picker.sldPtrIB.appendChild(jsc.picker.sldPtrS); 2802 jsc.picker.box.appendChild(jsc.picker.sldB); 2803 jsc.picker.box.appendChild(jsc.picker.sldM); 2804 2805 jsc.picker.asld.appendChild(jsc.picker.asldGrad.elm); 2806 jsc.picker.asldB.appendChild(jsc.picker.asld); 2807 jsc.picker.asldB.appendChild(jsc.picker.asldPtrOB); 2808 jsc.picker.asldPtrOB.appendChild(jsc.picker.asldPtrMB); 2809 jsc.picker.asldPtrMB.appendChild(jsc.picker.asldPtrIB); 2810 jsc.picker.asldPtrIB.appendChild(jsc.picker.asldPtrS); 2811 jsc.picker.box.appendChild(jsc.picker.asldB); 2812 jsc.picker.box.appendChild(jsc.picker.asldM); 2813 2814 jsc.picker.box.appendChild(jsc.picker.pal); 2815 2816 jsc.picker.btn.appendChild(jsc.picker.btnT); 2817 jsc.picker.box.appendChild(jsc.picker.btn); 2818 2819 jsc.picker.eyedropperBtn.appendChild(jsc.picker.eyedropperBtnT); 2820 jsc.picker.box.appendChild(jsc.picker.eyedropperBtn); 2821 2822 jsc.picker.boxB.appendChild(jsc.picker.box); 2823 jsc.picker.wrap.appendChild(jsc.picker.boxS); 2824 jsc.picker.wrap.appendChild(jsc.picker.boxB); 2825 2826 jsc.picker.wrap.addEventListener('touchstart', jsc.onPickerTouchStart, 2827 jsc.isPassiveEventSupported ? {passive: false} : false); 2828 } 2829 2830 var p = jsc.picker; 2831 2832 var displaySlider = !!jsc.getSliderChannel(THIS); 2833 var displayAlphaSlider = THIS.hasAlphaChannel(); 2834 var pickerDims = jsc.getPickerDims(THIS); 2835 var crossOuterSize = (2 * THIS.pointerBorderWidth + THIS.pointerThickness + 2 * THIS.crossSize); 2836 var controlPadding = jsc.getControlPadding(THIS); 2837 var borderRadius = Math.min( 2838 THIS.borderRadius, 2839 Math.round(THIS.padding * Math.PI)); // px 2840 var padCursor = 'crosshair'; 2841 2842 // wrap 2843 p.wrap.className = 'jscolor-wrap'; 2844 p.wrap.style.width = pickerDims.borderW + 'px'; 2845 p.wrap.style.height = pickerDims.borderH + 'px'; 2846 p.wrap.style.zIndex = THIS.zIndex; 2847 2848 // picker 2849 p.box.className = 'jscolor-picker'; 2850 p.box.style.width = pickerDims.paddedW + 'px'; 2851 p.box.style.height = pickerDims.paddedH + 'px'; 2852 2853 // picker shadow 2854 p.boxS.className = 'jscolor-shadow'; 2855 jsc.setBorderRadius(p.boxS, borderRadius + 'px'); 2856 2857 // picker border 2858 p.boxB.className = 'jscolor-border'; 2859 p.boxB.style.border = THIS.borderWidth + 'px solid'; 2860 p.boxB.style.borderColor = THIS.borderColor; 2861 p.boxB.style.background = THIS.backgroundColor; 2862 jsc.setBorderRadius(p.boxB, borderRadius + 'px'); 2863 2864 // IE hack: 2865 // If the element is transparent, IE will trigger the event on the elements under it, 2866 // e.g. on Canvas or on elements with border 2867 p.padM.style.background = 'rgba(255,0,0,.2)'; 2868 p.sldM.style.background = 'rgba(0,255,0,.2)'; 2869 p.asldM.style.background = 'rgba(0,0,255,.2)'; 2870 2871 p.padM.style.opacity = 2872 p.sldM.style.opacity = 2873 p.asldM.style.opacity = 2874 '0'; 2875 2876 // pad 2877 p.pad.style.position = 'relative'; 2878 p.pad.style.width = THIS.width + 'px'; 2879 p.pad.style.height = THIS.height + 'px'; 2880 2881 // pad - color spectrum (HSV and HVS) 2882 p.padCanvas.draw(THIS.width, THIS.height, jsc.getPadYChannel(THIS)); 2883 2884 // pad border 2885 p.padB.style.position = 'absolute'; 2886 p.padB.style.left = (THIS.padding + (THIS.extendedPicking && THIS.eyedropperButton ? 32 : 0)) + 'px'; 2887 p.padB.style.top = THIS.padding + 'px'; 2888 p.padB.style.border = THIS.controlBorderWidth + 'px solid'; 2889 p.padB.style.borderColor = THIS.controlBorderColor; 2890 2891 // pad mouse area 2892 p.padM.style.position = 'absolute'; 2893 p.padM.style.left = (THIS.extendedPicking && THIS.eyedropperButton ? 32 : 0) + 'px'; 2894 p.padM.style.top = 0 + 'px'; 2895 p.padM.style.width = (THIS.padding + 2 * THIS.controlBorderWidth + THIS.width + controlPadding) + 'px'; 2896 p.padM.style.height = (2 * THIS.controlBorderWidth + 2 * THIS.padding + THIS.height) + 'px'; 2897 p.padM.style.cursor = padCursor; 2898 jsc.setData(p.padM, { 2899 instance: THIS, 2900 control: 'pad', 2901 }) 2902 2903 // pad cross 2904 p.cross.style.position = 'absolute'; 2905 p.cross.style.left = 2906 p.cross.style.top = 2907 '0'; 2908 p.cross.style.width = 2909 p.cross.style.height = 2910 crossOuterSize + 'px'; 2911 2912 // pad cross border Y and X 2913 p.crossBY.style.position = 2914 p.crossBX.style.position = 2915 'absolute'; 2916 p.crossBY.style.background = 2917 p.crossBX.style.background = 2918 THIS.pointerBorderColor; 2919 p.crossBY.style.width = 2920 p.crossBX.style.height = 2921 (2 * THIS.pointerBorderWidth + THIS.pointerThickness) + 'px'; 2922 p.crossBY.style.height = 2923 p.crossBX.style.width = 2924 crossOuterSize + 'px'; 2925 p.crossBY.style.left = 2926 p.crossBX.style.top = 2927 (Math.floor(crossOuterSize / 2) - Math.floor(THIS.pointerThickness / 2) - THIS.pointerBorderWidth) + 'px'; 2928 p.crossBY.style.top = 2929 p.crossBX.style.left = 2930 '0'; 2931 2932 // pad cross line Y and X 2933 p.crossLY.style.position = 2934 p.crossLX.style.position = 2935 'absolute'; 2936 p.crossLY.style.background = 2937 p.crossLX.style.background = 2938 THIS.pointerColor; 2939 p.crossLY.style.height = 2940 p.crossLX.style.width = 2941 (crossOuterSize - 2 * THIS.pointerBorderWidth) + 'px'; 2942 p.crossLY.style.width = 2943 p.crossLX.style.height = 2944 THIS.pointerThickness + 'px'; 2945 p.crossLY.style.left = 2946 p.crossLX.style.top = 2947 (Math.floor(crossOuterSize / 2) - Math.floor(THIS.pointerThickness / 2)) + 'px'; 2948 p.crossLY.style.top = 2949 p.crossLX.style.left = 2950 THIS.pointerBorderWidth + 'px'; 2951 2952 2953 // slider 2954 p.sld.style.overflow = 'hidden'; 2955 p.sld.style.width = THIS.sliderSize + 'px'; 2956 p.sld.style.height = THIS.height + 'px'; 2957 2958 // slider gradient 2959 p.sldGrad.draw(THIS.sliderSize, THIS.height, '#000', '#000'); 2960 2961 // slider border 2962 p.sldB.style.display = displaySlider ? 'block' : 'none'; 2963 p.sldB.style.position = 'absolute'; 2964 p.sldB.style.left = (THIS.padding + THIS.width + 2 * THIS.controlBorderWidth + 2 * controlPadding + (THIS.extendedPicking && THIS.eyedropperButton ? 32 : 0)) + 'px'; 2965 p.sldB.style.top = THIS.padding + 'px'; 2966 p.sldB.style.border = THIS.controlBorderWidth + 'px solid'; 2967 p.sldB.style.borderColor = THIS.controlBorderColor; 2968 2969 // slider mouse area 2970 p.sldM.style.display = displaySlider ? 'block' : 'none'; 2971 p.sldM.style.position = 'absolute'; 2972 p.sldM.style.left = (THIS.padding + THIS.width + 2 * THIS.controlBorderWidth + controlPadding + (THIS.extendedPicking && THIS.eyedropperButton ? 32 : 0)) + 'px'; 2973 p.sldM.style.top = 0 + 'px'; 2974 p.sldM.style.width = ( 2975 (THIS.sliderSize + 2 * controlPadding + 2 * THIS.controlBorderWidth) + 2976 (displayAlphaSlider ? 0 : Math.max(0, THIS.padding - controlPadding)) // remaining padding to the right edge 2977 ) + 'px'; 2978 p.sldM.style.height = (2 * THIS.controlBorderWidth + 2 * THIS.padding + THIS.height) + 'px'; 2979 p.sldM.style.cursor = 'default'; 2980 jsc.setData(p.sldM, { 2981 instance: THIS, 2982 control: 'sld', 2983 }); 2984 2985 // slider pointer inner and outer border 2986 p.sldPtrIB.style.border = 2987 p.sldPtrOB.style.border = 2988 THIS.pointerBorderWidth + 'px solid ' + THIS.pointerBorderColor; 2989 2990 // slider pointer outer border 2991 p.sldPtrOB.style.position = 'absolute'; 2992 p.sldPtrOB.style.left = -(2 * THIS.pointerBorderWidth + THIS.pointerThickness) + 'px'; 2993 p.sldPtrOB.style.top = '0'; 2994 2995 // slider pointer middle border 2996 p.sldPtrMB.style.border = THIS.pointerThickness + 'px solid ' + THIS.pointerColor; 2997 2998 // slider pointer spacer
2999 p.sldPtrS.style.width = THIS.sliderSize + 'px'; 3000 p.sldPtrS.style.height = jsc.pub.sliderInnerSpace + 'px'; 3001 3002 3003 // alpha slider 3004 p.asld.style.overflow = 'hidden'; 3005 p.asld.style.width = THIS.sliderSize + 'px'; 3006 p.asld.style.height = THIS.height + 'px'; 3007 3008 // alpha slider gradient 3009 p.asldGrad.draw(THIS.sliderSize, THIS.height, '#000'); 3010 3011 // alpha slider border 3012 p.asldB.style.display = displayAlphaSlider ? 'block' : 'none'; 3013 p.asldB.style.position = 'absolute'; 3014 p.asldB.style.left = ( 3015 (THIS.padding + THIS.width + 2 * THIS.controlBorderWidth + controlPadding) + 3016 (displaySlider ? (THIS.sliderSize + 3 * controlPadding + 2 * THIS.controlBorderWidth) : 0) + 3017 (THIS.extendedPicking && THIS.eyedropperButton ? 32 : 0) 3018 ) + 'px'; 3019 p.asldB.style.top = THIS.padding + 'px'; 3020 p.asldB.style.border = THIS.controlBorderWidth + 'px solid'; 3021 p.asldB.style.borderColor = THIS.controlBorderColor; 3022 3023 // alpha slider mouse area 3024 p.asldM.style.display = displayAlphaSlider ? 'block' : 'none'; 3025 p.asldM.style.position = 'absolute'; 3026 p.asldM.style.left = ( 3027 (THIS.padding + THIS.width + 2 * THIS.controlBorderWidth + controlPadding) + 3028 (displaySlider ? (THIS.sliderSize + 2 * controlPadding + 2 * THIS.controlBorderWidth) : 0) + 3029 (THIS.extendedPicking && THIS.eyedropperButton ? 32 : 0) 3030 ) + 'px'; 3031 p.asldM.style.top = 0 + 'px'; 3032 p.asldM.style.width = ( 3033 (THIS.sliderSize + 2 * controlPadding + 2 * THIS.controlBorderWidth) + 3034 Math.max(0, THIS.padding - controlPadding) // remaining padding to the right edge 3035 ) + 'px'; 3036 p.asldM.style.height = (2 * THIS.controlBorderWidth + 2 * THIS.padding + THIS.height) + 'px'; 3037 p.asldM.style.cursor = 'default'; 3038 jsc.setData(p.asldM, { 3039 instance: THIS, 3040 control: 'asld', 3041 }) 3042 3043 // alpha slider pointer inner and outer border 3044 p.asldPtrIB.style.border = 3045 p.asldPtrOB.style.border = 3046 THIS.pointerBorderWidth + 'px solid ' + THIS.pointerBorderColor; 3047 3048 // alpha slider pointer outer border 3049 p.asldPtrOB.style.position = 'absolute'; 3050 p.asldPtrOB.style.left = -(2 * THIS.pointerBorderWidth + THIS.pointerThickness) + 'px'; 3051 p.asldPtrOB.style.top = '0'; 3052 3053 // alpha slider pointer middle border 3054 p.asldPtrMB.style.border = THIS.pointerThickness + 'px solid ' + THIS.pointerColor; 3055 3056 // alpha slider pointer spacer 3057 p.asldPtrS.style.width = THIS.sliderSize + 'px'; 3058 p.asldPtrS.style.height = jsc.pub.sliderInnerSpace + 'px'; 3059 3060 3061 // palette 3062 p.pal.className = 'jscolor-palette'; 3063 p.pal.style.display = pickerDims.palette.rows ? 'block' : 'none'; 3064 p.pal.style.left = (THIS.padding + (THIS.extendedPicking && THIS.eyedropperButton ? 32 : 0)) + 'px'; 3065 p.pal.style.top = (2 * THIS.controlBorderWidth + 2 * THIS.padding + THIS.height) + 'px'; 3066 3067 // palette's color samples 3068 3069 p.pal.innerHTML = ''; 3070 3071 var chessboard = jsc.genColorPreviewCanvas('rgba(0,0,0,0)'); 3072 3073 var si = 0; // color sample's index 3074 for (var r = 0; r < pickerDims.palette.rows; r++) { 3075 for (var c = 0; c < pickerDims.palette.cols && si < THIS._palette.length; c++, si++) { 3076 var sampleColor = THIS._palette[si]; 3077 var sampleCssColor = jsc.rgbaColor.apply(null, sampleColor.rgba); 3078 3079 var sc = jsc.createEl('div'); // color sample's color 3080 sc.style.width = (pickerDims.palette.cellW - 2 * THIS.controlBorderWidth) + 'px'; 3081 sc.style.height = (pickerDims.palette.cellH - 2 * THIS.controlBorderWidth) + 'px'; 3082 sc.style.backgroundColor = sampleCssColor; 3083 3084 var sw = jsc.createEl('div'); // color sample's wrap 3085 sw.className = 'jscolor-palette-sw'; 3086 sw.style.left = 3087 ( 3088 pickerDims.palette.cols <= 1 ? 0 : 3089 Math.round(10 * (c * ((pickerDims.contentW - pickerDims.palette.cellW) / (pickerDims.palette.cols - 1)))) / 10 3090 ) + 'px'; 3091 sw.style.top = (r * (pickerDims.palette.cellH + THIS.paletteSpacing)) + 'px'; 3092 sw.style.border = THIS.controlBorderWidth + 'px solid'; 3093 sw.style.borderColor = THIS.controlBorderColor; 3094 if (sampleColor.rgba[3] !== null && sampleColor.rgba[3] < 1.0) { // only create chessboard background if the sample has transparency 3095 sw.style.backgroundImage = 'url(\'' + chessboard.canvas.toDataURL() + '\')'; 3096 sw.style.backgroundRepeat = 'repeat'; 3097 sw.style.backgroundPosition = 'center center'; 3098 } 3099 jsc.setData(sw, { 3100 instance: THIS, 3101 control: 'palette-sw',
3102 color: sampleColor, 3103 }); 3104 sw.addEventListener('click', jsc.onPaletteSampleClick, false); 3105 sw.appendChild(sc); 3106 p.pal.appendChild(sw); 3107 } 3108 } 3109 3110 3111 // the Close button 3112 function setBtnBorder () { 3113 var insetColors = THIS.controlBorderColor.split(/\s+/); 3114 var outsetColor = insetColors.length < 2 ? insetColors[0] : insetColors[1] + ' ' + insetColors[0] + ' ' + insetColors[0] + ' ' + insetColors[1]; 3115 p.btn.style.borderColor = outsetColor; 3116 } 3117 var btnPadding = 15; // px 3118 p.btn.className = 'jscolor-btn jscolor-btn-close'; 3119 p.btn.style.display = THIS.closeButton ? 'block' : 'none'; 3120 p.btn.style.left = THIS.padding + 'px'; 3121 p.btn.style.bottom = THIS.padding + 'px'; 3122 p.btn.style.padding = '0 ' + btnPadding + 'px'; 3123 p.btn.style.maxWidth = (pickerDims.contentW - 2 * THIS.controlBorderWidth - 2 * btnPadding) + 'px'; 3124 p.btn.style.height = THIS.buttonHeight + 'px'; 3125 p.btn.style.border = THIS.controlBorderWidth + 'px solid'; 3126 setBtnBorder(); 3127 p.btn.style.color = THIS.buttonColor; 3128 p.btn.onmousedown = function () { 3129 THIS.hide(); 3130 }; 3131 p.btnT.style.display = 'inline'; 3132 p.btnT.style.lineHeight = THIS.buttonHeight + 'px'; 3133 p.btnT.innerText = THIS.closeText; 3134 3135 // the Eyedropper button 3136 p.eyedropperBtn.className = 'jscolor-btn jscolor-btn-eyedropper'; 3137 p.eyedropperBtn.style.display = (THIS.extendedPicking && THIS.eyedropperButton) ? 'block' : 'none'; 3138 p.eyedropperBtn.style.position = 'absolute'; 3139 p.eyedropperBtn.style.left = THIS.padding + 'px'; 3140 p.eyedropperBtn.style.bottom = THIS.padding + 'px'; 3141 p.eyedropperBtn.style.width = '24px'; 3142 p.eyedropperBtn.style.height = '24px'; 3143 p.eyedropperBtn.style.border = THIS.controlBorderWidth + 'px solid'; 3144 p.eyedropperBtn.style.borderColor = THIS.controlBorderColor; 3145 p.eyedropperBtn.style.backgroundColor = THIS.backgroundColor; 3146 p.eyedropperBtn.style.borderRadius = '3px'; 3147 p.eyedropperBtn.style.cursor = 'pointer'; 3148 p.eyedropperBtn.style.fontSize = '14px'; 3149 p.eyedropperBtn.style.lineHeight = '22px'; 3150 p.eyedropperBtn.style.textAlign = 'center'; 3151 p.eyedropperBtn.style.color = THIS.buttonColor; 3152 p.eyedropperBtn.title = 'Pick color from page (Press ' + THIS.eyedropperKey.toUpperCase() + ')'; 3153 p.eyedropperBtn.onmousedown = function (e) { 3154 jsc.activateEyedropper(THIS); 3155 jsc.preventDefault(e); 3156 }; 3157 p.eyedropperBtnT.style.display = 'inline'; 3158 p.eyedropperBtnT.style.lineHeight = '22px'; 3159 p.eyedropperBtnT.innerHTML = '💧'; // droplet emoji as eyedropper icon 3160 3161 // reposition the pointers 3162 redrawPad(); 3163 redrawSld(); 3164 redrawASld(); 3165 3166 // If we are changing the owner without first closing the picker, 3167 // make sure to first deal with the old owner 3168 if (jsc.picker.owner && jsc.picker.owner !== THIS) { 3169 jsc.removeClass(jsc.picker.owner.targetElement, jsc.pub.activeClassName); 3170 } 3171 3172 // Set a new picker owner 3173 jsc.picker.owner = THIS; 3174 3175 // The redrawPosition() method needs picker.owner to be set, that's why we call it here, 3176 // after setting the owner 3177 jsc.redrawPosition(); 3178 3179 if (p.wrap.parentNode !== THIS.container) { 3180 THIS.container.appendChild(p.wrap); 3181 } 3182 3183 jsc.addClass(THIS.targetElement, jsc.pub.activeClassName); 3184 } 3185 3186 3187 function redrawPad () { 3188 // redraw the pad pointer 3189 var yChannel = jsc.getPadYChannel(THIS); 3190 var x = Math.round((THIS.channels.h / 360) * (THIS.width - 1)); 3191 var y = Math.round((1 - THIS.channels[yChannel] / 100) * (THIS.height - 1)); 3192 var crossOuterSize = (2 * THIS.pointerBorderWidth + THIS.pointerThickness + 2 * THIS.crossSize); 3193 var ofs = -Math.floor(crossOuterSize / 2); 3194 jsc.picker.cross.style.left = (x + ofs) + 'px'; 3195 jsc.picker.cross.style.top = (y + ofs) + 'px'; 3196 3197 // redraw the slider 3198 switch (jsc.getSliderChannel(THIS)) { 3199 case 's': 3200 var rgb1 = jsc.HSV_RGB(THIS.channels.h, 100, THIS.channels.v); 3201 var rgb2 = jsc.HSV_RGB(THIS.channels.h, 0, THIS.channels.v); 3202 var color1 = 'rgb(' + 3203 Math.round(rgb1[0]) + ',' + 3204 Math.round(rgb1[1]) + ',' + 3205 Math.round(rgb1[2]) + ')'; 3206 var color2 = 'rgb(' + 3207 Math.round(rgb2[0]) + ',' + 3208 Math.round(rgb2[1]) + ',' + 3209 Math.round(rgb2[2]) + ')'; 3210 jsc.picker.sldGrad.draw(THIS.sliderSize, THIS.height, color1, color2); 3211 break; 3212 case 'v': 3213 var rgb = jsc.HSV_RGB(THIS.channels.h, THIS.channels.s, 100); 3214 var color1 = 'rgb(' + 3215 Math.round(rgb[0]) + ',' + 3216 Math.round(rgb[1]) + ',' + 3217 Math.round(rgb[2]) + ')'; 3218 var color2 = '#000'; 3219 jsc.picker.sldGrad.draw(THIS.sliderSize, THIS.height, color1, color2); 3220 break; 3221 } 3222 3223 // redraw the alpha slider 3224 jsc.picker.asldGrad.draw(THIS.sliderSize, THIS.height, THIS.toHEXString()); 3225 } 3226 3227 3228 function redrawSld () { 3229 var sldChannel = jsc.getSliderChannel(THIS); 3230 if (sldChannel) { 3231 // redraw the slider pointer 3232 var y = Math.round((1 - THIS.channels[sldChannel] / 100) * (THIS.height - 1)); 3233 jsc.picker.sldPtrOB.style.top = (y - (2 * THIS.pointerBorderWidth + THIS.pointerThickness) - Math.floor(jsc.pub.sliderInnerSpace / 2)) + 'px'; 3234 } 3235 3236 // redraw the alpha slider 3237 jsc.picker.asldGrad.draw(THIS.sliderSize, THIS.height, THIS.toHEXString()); 3238 } 3239 3240 3241 function redrawASld () { 3242 var y = Math.round((1 - THIS.channels.a) * (THIS.height - 1)); 3243 jsc.picker.asldPtrOB.style.top = (y - (2 * THIS.pointerBorderWidth + THIS.pointerThickness) - Math.floor(jsc.pub.sliderInnerSpace / 2)) + 'px'; 3244 } 3245 3246 3247 function isPickerOwner () { 3248 return jsc.picker && jsc.picker.owner === THIS; 3249 } 3250 3251 3252 function onValueKeyDown (ev) { 3253 if (jsc.eventKey(ev) === 'Enter') { 3254 if (THIS.valueElement) { 3255 THIS.processValueInput(THIS.valueElement.value); 3256 } 3257 THIS.tryHide(); 3258 } 3259 } 3260 3261 3262 function onAlphaKeyDown (ev) { 3263 if (jsc.eventKey(ev) === 'Enter') { 3264 if (THIS.alphaElement) { 3265 THIS.processAlphaInput(THIS.alphaElement.value); 3266 } 3267 THIS.tryHide(); 3268 } 3269 } 3270 3271 3272 function onValueChange (ev) { 3273 if (jsc.getData(ev, 'internal')) { 3274 return;
3274 // skip if the event was internally triggered by jscolor 3275 } 3276 3277 var oldVal = THIS.valueElement.value; 3278 3279 THIS.processValueInput(THIS.valueElement.value); // this might change the value 3280 3281 jsc.triggerCallback(THIS, 'onChange'); 3282 3283 if (THIS.valueElement.value !== oldVal) { 3284 // value was additionally changed -> let's trigger the change event again, even though it was natively dispatched 3285 jsc.triggerInputEvent(THIS.valueElement, 'change', true, true); 3286 } 3287 } 3288 3289 3290 function onAlphaChange (ev) { 3291 if (jsc.getData(ev, 'internal')) { 3292 return; // skip if the event was internally triggered by jscolor 3293 } 3294 3295 var oldVal = THIS.alphaElement.value; 3296 3297 THIS.processAlphaInput(THIS.alphaElement.value); // this might change the value 3298 3299 jsc.triggerCallback(THIS, 'onChange'); 3300 3301 // triggering valueElement's onChange (because changing alpha changes the entire color, e.g. with rgba format) 3302 jsc.triggerInputEvent(THIS.valueElement, 'change', true, true); 3303 3304 if (THIS.alphaElement.value !== oldVal) { 3305 // value was additionally changed -> let's trigger the change event again, even though it was natively dispatched 3306 jsc.triggerInputEvent(THIS.alphaElement, 'change', true, true); 3307 } 3308 } 3309 3310 3311 function onValueInput (ev) { 3312 if (jsc.getData(ev, 'internal')) { 3313 return; // skip if the event was internally triggered by jscolor 3314 } 3315 3316 if (THIS.valueElement) { 3317 THIS.fromString(THIS.valueElement.value, jsc.flags.leaveValue); 3318 } 3319 3320 jsc.triggerCallback(THIS, 'onInput'); 3321 3322 // triggering valueElement's onInput 3323 // (not needed, it was dispatched normally by the browser) 3324 } 3325 3326 3327 function onAlphaInput (ev) { 3328 if (jsc.getData(ev, 'internal')) { 3329 return; // skip if the event was internally triggered by jscolor 3330 } 3331 3332 if (THIS.alphaElement) { 3333 THIS.fromHSVA(null, null, null, parseFloat(THIS.alphaElement.value), jsc.flags.leaveAlpha); 3334 } 3335 3336 jsc.triggerCallback(THIS, 'onInput'); 3337 3338 // triggering valueElement's onInput (because changing alpha changes the entire color, e.g. with rgba format) 3339 jsc.triggerInputEvent(THIS.valueElement, 'input', true, true); 3340 } 3341 3342 3343 // let's process the DEPRECATED 'options' property (this will be later removed) 3344 if (jsc.pub.options) { 3345 // let's set custom default options, if specified 3346 for (var opt in jsc.pub.options) { 3347 if (jsc.pub.options.hasOwnProperty(opt)) { 3348 try { 3349 setOption(opt, jsc.pub.options[opt]); 3350 } catch (e) { 3351 console.warn(e); 3352 } 3353 } 3354 } 3355 } 3356 3357 3358 // let's apply configuration presets 3359 // 3360 var presetsArr = []; 3361 3362 if (opts.preset) { 3363 if (typeof opts.preset === 'string') { 3364 presetsArr = opts.preset.split(/\s+/); 3365 } else if (Array.isArray(opts.preset)) { 3366 presetsArr = opts.preset.slice(); // slice() to clone 3367 } else { 3368 console.warn('Unrecognized preset value'); 3369 } 3370 } 3371 3372 // always use the 'default' preset. If it's not listed, append it to the end. 3373 if (presetsArr.indexOf('default') === -1) { 3374 presetsArr.push('default'); 3375 } 3376 3377 // let's apply the presets in reverse order, so that should there be any overlapping options, 3378 // the formerly listed preset will override the latter 3379 for (var i = presetsArr.length - 1; i >= 0; i -= 1) { 3380 var pres = presetsArr[i]; 3381 if (!pres) { 3382 continue; // preset is empty string 3383 } 3384 if (!jsc.pub.presets.hasOwnProperty(pres)) { 3385 console.warn('Unknown preset: %s', pres); 3386 continue; 3387 } 3388 for (var opt in jsc.pub.presets[pres]) { 3389 if (jsc.pub.presets[pres].hasOwnProperty(opt)) { 3390 try { 3391 setOption(opt, jsc.pub.presets[pres][opt]); 3392 } catch (e) { 3393 console.warn(e); 3394 } 3395 } 3396 } 3397 } 3398 3399 3400 // let's set specific options for this color picker 3401 var nonProperties = [ 3402 // these options won't be set as instance properties 3403 'preset', 3404 ]; 3405 for (var opt in opts) { 3406 if (opts.hasOwnProperty(opt)) { 3407 if (nonProperties.indexOf(opt) === -1) { 3408 try { 3409 setOption(opt, opts[opt]); 3410 } catch (e) { 3411 console.warn(e); 3412 } 3413 } 3414 } 3415 } 3416 3417 3418 // 3419 // Install the color picker on chosen element(s) 3420 // 3421 3422 3423 // Determine picker's container element 3424 if (this.container === undefined) { 3425 this.container = window.document.body; // default container is BODY element 3426 3427 } else { // explicitly set to custom element 3428 this.container = jsc.node(this.container); 3429 } 3430 3431 if (!this.container) { 3432 throw new Error('Cannot instantiate color picker without a container element'); 3433 } 3434 3435 3436 // Fetch the target element 3437 this.targetElement = jsc.node(targetElement); 3438 3439 if (!this.targetElement) { 3440 // temporarily customized error message to help with migrating from versions prior to 2.2
3441 if (typeof targetElement === 'string' && /^[a-zA-Z][\w:.-]*$/.test(targetElement)) { 3442 // targetElement looks like valid ID 3443 var possiblyId = targetElement; 3444 throw new Error('If \'' + possiblyId + '\' is supposed to be an ID, please use \'#' + possiblyId + '\' or any valid CSS selector.'); 3445 } 3446 3447 throw new Error('Cannot instantiate color picker without a target element'); 3448 } 3449 3450 if (this.targetElement.jscolor && this.targetElement.jscolor instanceof jsc.pub) { 3451 throw new Error('Color picker already installed on this element'); 3452 } 3453 3454 3455 // link this instance with the target element 3456 this.targetElement.jscolor = this; 3457 jsc.addClass(this.targetElement, jsc.pub.className); 3458 3459 // register this instance 3460 jsc.instances.push(this); 3461 3462 3463 // if target is BUTTON 3464 if (jsc.isButton(this.targetElement)) { 3465 3466 if (this.targetElement.type.toLowerCase() !== 'button') { 3467 // on buttons, always force type to be 'button', e.g. in situations the target <button> has no type 3468 // and thus defaults to 'submit' and would submit the form when clicked 3469 this.targetElement.type = 'button'; 3470 console.log('Initialized jscolor on button element:', this.targetElement); 3471 } 3472 3473 if (jsc.isButtonEmpty(this.targetElement)) { // empty button 3474 // it is important to clear element's contents first. 3475 // if we're re-instantiating color pickers on DOM that has been modified by changing page's innerHTML, 3476 // we would keep adding more non-breaking spaces to element's content (because element's contents survive 3477 // innerHTML changes, but picker instances don't) 3478 jsc.removeChildren(this.targetElement); 3479 3480 // let's insert a non-breaking space 3481 this.targetElement.appendChild(window.document.createTextNode('\xa0')); 3482 3483 // set min-width = previewSize, if not already greater 3484 var compStyle = jsc.getCompStyle(this.targetElement); 3485 var currMinWidth = parseFloat(compStyle['min-width']) || 0; 3486 if (currMinWidth < this.previewSize) { 3487 jsc.setStyle(this.targetElement, { 3488 'min-width': this.previewSize + 'px', 3489 }, this.forceStyle); 3490 } 3491 } 3492 } 3493 3494 // Determine the value element 3495 if (this.valueElement === undefined) { 3496 if (jsc.isTextInput(this.targetElement)) { 3497 // for text inputs, default valueElement is targetElement 3498 this.valueElement = this.targetElement; 3499 } else { 3500 // leave it undefined 3501 } 3502 3503 } else if (this.valueElement === null) { // explicitly set to null 3504 // leave it null 3505 3506 } else { // explicitly set to custom element 3507 this.valueElement = jsc.node(this.valueElement); 3508 } 3509 3510 // Determine the alpha element 3511 if (this.alphaElement) { 3512 this.alphaElement = jsc.node(this.alphaElement); 3513 } 3514 3515 // Determine the preview element 3516 if (this.previewElement === undefined) { 3517 this.previewElement = this.targetElement; // default previewElement is targetElement 3518 3519 } else if (this.previewElement === null) { // explicitly set to null 3520 // leave it null 3521 3522 } else { // explicitly set to custom element 3523 this.previewElement = jsc.node(this.previewElement); 3524 } 3525 3526 // valueElement 3527 if (this.valueElement && jsc.isTextInput(this.valueElement)) { 3528 3529 // If the value element has onInput event already set, we need to detach it and attach AFTER our listener. 3530 // otherwise the picker instance would still contain the old color when accessed from the onInput handler. 3531 var valueElementOrigEvents = { 3532 onInput: this.valueElement.oninput 3533 }; 3534 this.valueElement.oninput = null; 3535 3536 console.log('Adding event listeners to value element:', this.valueElement); 3537 this.valueElement.addEventListener('keydown', onValueKeyDown, false); 3538 this.valueElement.addEventListener('change', onValueChange, false); 3539 this.valueElement.addEventListener('input', onValueInput, false); 3540 // the original event listener must be attached AFTER our handler (to let it first set picker's color) 3541 if (valueElementOrigEvents.onInput) { 3542 this.valueElement.addEventListener('input', valueElementOrigEvents.onInput, false); 3543 } 3544 3545 this.valueElement.setAttribute('autocomplete', 'off'); 3546 this.valueElement.setAttribute('autocorrect', 'off'); 3547 this.valueElement.setAttribute('autocapitalize', 'off'); 3548 this.valueElement.setAttribute('spellcheck', false); 3549 } 3550 3551 // alphaElement 3552 if (this.alphaElement && jsc.isTextInput(this.alphaElement)) { 3553 this.alphaElement.addEventListener('keydown', onAlphaKeyDown, false); 3554 this.alphaElement.addEventListener('change', onAlphaChange, false); 3555 this.alphaElement.addEventListener('input', onAlphaInput, false); 3556 3557 this.alphaElement.setAttribute('autocomplete', 'off'); 3558 this.alphaElement.setAttribute('autocorrect', 'off'); 3559 this.alphaElement.setAttribute('autocapitalize', 'off'); 3560 this.alphaElement.setAttribute('spellcheck', false); 3561 } 3562 3563 // determine initial color value 3564 // 3565 var initValue = 'FFFFFF'; 3566 3567 if (this.value !== undefined) { 3568 initValue = this.value; // get initial color from the 'value' property 3569 } else if (this.valueElement && this.valueElement.value !== undefined) { 3570 initValue = this.valueElement.value; // get initial color from valueElement's value 3571 } 3572 3573 // determine initial alpha value 3574 // 3575 var initAlpha = undefined; 3576 3577 if (this.alpha !== undefined) { 3578 initAlpha = (''+this.alpha); // get initial alpha value from the 'alpha' property 3579 } else if (this.alphaElement && this.alphaElement.value !== undefined) { 3580 initAlpha = this.alphaElement.value; // get initial color from alphaElement's value 3581 } 3582 3583 // determine current format based on the initial color value 3584 // 3585 this._currentFormat = null; 3586 3587 if (['auto', 'any'].indexOf(this.format.toLowerCase()) > -1) { 3588 // format is 'auto' or 'any' -> let's auto-detect current format 3589 var color = jsc.parseColorString(initValue); 3590 this._currentFormat = color ? color.format : 'hex'; 3591 } else { 3592 // format is specified 3593 this._currentFormat = this.format.toLowerCase(); 3594 } 3595 3596 3597 // let's parse the initial color value and expose color's preview 3598 this.processValueInput(initValue); 3599 3600 // let's also parse and expose the initial alpha value, if any 3601 // 3602 // Note: If the initial color value contains alpha value in it (e.g. in rgba format),
3603 // this will overwrite it. So we should only process alpha input if there was initial 3604 // alpha explicitly set, otherwise we could needlessly lose initial value's alpha 3605 if (initAlpha !== undefined) { 3606 this.processAlphaInput(initAlpha); 3607 } 3608 3609 if (this.random) { 3610 // randomize the initial color value 3611 this.randomize.apply(this, Array.isArray(this.random) ? this.random : []); 3612 } 3613 3614 } 3615 3616}; 3617 3618 3619//================================ 3620// Public properties and methods 3621//================================ 3622 3623// 3624// These will be publicly available via jscolor.<name> and JSColor.<name> 3625// 3626 3627 3628// class that will be set to elements having jscolor installed on them 3629jsc.pub.className = 'jscolor'; 3630 3631 3632// class that will be set to elements having jscolor active on them 3633jsc.pub.activeClassName = 'jscolor-active'; 3634 3635 3636// whether to try to parse the options string by evaluating it using 'new Function()' 3637// in case it could not be parsed with JSON.parse() 3638jsc.pub.looseJSON = true; 3639 3640 3641// presets 3642jsc.pub.presets = {}; 3643 3644// built-in presets 3645jsc.pub.presets['default'] = {}; // baseline for customization 3646 3647jsc.pub.presets['light'] = { // default color scheme 3648 backgroundColor: 'rgba(255,255,255,1)', 3649 controlBorderColor: 'rgba(187,187,187,1)', 3650 buttonColor: 'rgba(0,0,0,1)', 3651}; 3652jsc.pub.presets['dark'] = { 3653 backgroundColor: 'rgba(51,51,51,1)', 3654 controlBorderColor: 'rgba(153,153,153,1)', 3655 buttonColor: 'rgba(240,240,240,1)', 3656}; 3657 3658jsc.pub.presets['small'] = { width:101, height:101, padding:10, sliderSize:14, paletteCols:8 }; 3659jsc.pub.presets['medium'] = { width:181, height:101, padding:12, sliderSize:16, paletteCols:10 }; // default size 3660jsc.pub.presets['large'] = { width:271, height:151, padding:12, sliderSize:24, paletteCols:15 }; 3661 3662jsc.pub.presets['thin'] = { borderWidth:1, controlBorderWidth:1, pointerBorderWidth:1 }; // default thickness 3663jsc.pub.presets['thick'] = { borderWidth:2, controlBorderWidth:2, pointerBorderWidth:2 }; 3664 3665 3666// size of space in the sliders 3667jsc.pub.sliderInnerSpace = 3; // px 3668 3669// transparency chessboard 3670jsc.pub.chessboardSize = 8; // px 3671jsc.pub.chessboardColor1 = '#666666'; 3672jsc.pub.chessboardColor2 = '#999999'; 3673 3674// preview separator 3675jsc.pub.previewSeparator = ['rgba(255,255,255,.65)', 'rgba(128,128,128,.65)']; 3676 3677 3678// Initializes jscolor 3679jsc.pub.init = function () { 3680 if (jsc.initialized) { 3681 return; 3682 } 3683 3684 // attach some necessary handlers 3685 // console.log('Attaching global event handlers for jscolor'); 3686 window.document.addEventListener('mousedown', jsc.onDocumentMouseDown, false); 3687 window.document.addEventListener('keyup', jsc.onDocumentKeyUp, false); 3688 window.addEventListener('resize', jsc.onWindowResize, false); 3689 window.addEventListener('scroll', jsc.onWindowScroll, false); 3690 3691 // append default CSS to HEAD 3692 jsc.appendDefaultCss(); 3693 3694 // install jscolor on current DOM 3695 jsc.pub.install(); 3696 3697 jsc.initialized = true; 3698 3699 // call functions waiting in the queue 3700 while (jsc.readyQueue.length) { 3701 var func = jsc.readyQueue.shift(); 3702 func(); 3703 } 3704}; 3705 3706 3707// Installs jscolor on current DOM tree 3708jsc.pub.install = function (rootNode) { 3709 var success = true; 3710 3711 try { 3712 jsc.installBySelector('[data-jscolor]', rootNode); 3713 } catch (e) { 3714 success = false; 3715 console.warn(e); 3716 } 3717 3718 // for backward compatibility with DEPRECATED installation using class name 3719 if (jsc.pub.lookupClass) { 3720 try { 3721 jsc.installBySelector( 3722 ( 3723 'input.' + jsc.pub.lookupClass + ', ' + 3724 'button.' + jsc.pub.lookupClass 3725 ), 3726 rootNode 3727 ); 3728 } catch (e) {} 3729 } 3730 3731 return success; 3732}; 3733 3734 3735// Registers function to be called as soon as jscolor is initialized (or immediately, if it already is). 3736// 3737jsc.pub.ready = function (func) { 3738 if (typeof func !== 'function') { 3739 console.warn('Passed value is not a function'); 3740 return false; 3741 } 3742 3743 if (jsc.initialized) { 3744 func(); 3745 } else { 3746 jsc.readyQueue.push(func); 3747 } 3748 return true; 3749}; 3750 3751 3752// Triggers given input event(s) (e.g. 'input' or 'change') on all color pickers. 3753// 3754// It is possible to specify multiple events separated with a space. 3755// If called before jscolor is initialized, then the events will be triggered after initialization. 3756// 3757jsc.pub.trigger = function (eventNames) { 3758 var triggerNow = function () { 3759 jsc.triggerGlobal(eventNames); 3760 }; 3761 3762 if (jsc.initialized) { 3763 triggerNow(); 3764 } else { 3765 jsc.pub.ready(triggerNow); 3766 } 3767}; 3768 3769 3770// Hides current color picker box 3771jsc.pub.hide = function () { 3772 if (jsc.picker && jsc.picker.owner) { 3773 jsc.picker.owner.hide(); 3774 } 3775}; 3776 3777 3778// Returns a data URL of a gray chessboard image that indicates transparency 3779jsc.pub.chessboard = function (color) { 3780 if (!color) { 3781 color = 'rgba(0,0,0,0)'; 3782 } 3783 var preview = jsc.genColorPreviewCanvas(color); 3784 return preview.canvas.toDataURL(); 3785}; 3786 3787 3788// Returns a data URL of a gray chessboard image that indicates transparency 3789jsc.pub.background = function (color) { 3790 var backgrounds = []; 3791 3792 // CSS gradient for background color preview 3793 backgrounds.push(jsc.genColorPreviewGradient(color)); 3794 3795 // data URL of generated PNG image with a gray transparency chessboard 3796 var preview = jsc.genColorPreviewCanvas(); 3797 backgrounds.push([ 3798 'url(\'' + preview.canvas.toDataURL() + '\')', 3799 'left top', 3800 'repeat', 3801 ].join(' '));
3802 3803 return backgrounds.join(', '); 3804}; 3805 3806 3807// 3808// DEPRECATED properties and methods 3809// 3810 3811 3812// DEPRECATED. Use jscolor.presets.default instead. 3813// 3814// Custom default options for all color pickers, e.g. { hash: true, width: 300 } 3815jsc.pub.options = {}; 3816 3817 3818// DEPRECATED. Use data-jscolor attribute instead, which installs jscolor on given element. 3819// 3820// By default, we'll search for all elements with class="jscolor" and install a color picker on them. 3821// 3822// You can change what class name will be looked for by setting the property jscolor.lookupClass 3823// anywhere in your HTML document. To completely disable the automatic lookup, set it to null. 3824// 3825jsc.pub.lookupClass = 'jscolor'; 3826 3827 3828// DEPRECATED. Use data-jscolor attribute instead, which installs jscolor on given element. 3829// 3830// Install jscolor on all elements that have the specified class name 3831jsc.pub.installByClassName = function () { 3832 console.error('jscolor.installByClassName() is DEPRECATED. Use data-jscolor="" attribute instead of a class name.' + jsc.docsRef); 3833 return false; 3834}; 3835 3836 3837jsc.register(); 3838 3839 3840return jsc.pub; 3841 3842 3843})(); // END jscolor 3844 3845 3846if (typeof window.jscolor === 'undefined') { 3847 window.jscolor = window.JSColor = jscolor; 3848} 3849 3850 3851// END jscolor code 3852 3853return jscolor; 3854 3855}); // END factory
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.