PageSourceSearch

https://alfredcurrier.com/wp-content/plugins/cm-alpha/ui-colorpicker/js/jscolor.js?ver=2.4.5

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

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.