PageSourceSearch

https://www.blmj.org/wp-content/plugins/sitelinx-accessibility-lite/assets/js/jscolor.js?ver=1790252589

js blmj.org collected 2026-09-24 19:36:27 UTC 103,837 bytes, 3,558 lines download raw bytes

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