Skip to content

Instantly share code, notes, and snippets.

@Oscar0159
Last active March 24, 2024 19:40
Show Gist options
  • Save Oscar0159/262dd340a113c1c8ca43e0a01f92649f to your computer and use it in GitHub Desktop.
Save Oscar0159/262dd340a113c1c8ca43e0a01f92649f to your computer and use it in GitHub Desktop.
df2profiler map drawer
// ==UserScript==
// @name df2profiler map drawer - Dead Frontier 2
// @name:zh-TW df2profiler 地圖繪製器 - 死亡邊境 2
// @namespace http://tampermonkey.net/
// @version 0.5.0
// @description a simple map drawer for df2profiler.com by clicking on the map to draw color on area
// @description:zh-TW 透過點擊地圖來繪製顏色的簡易地圖繪製器
// @author Archer_Wn
// @match https://df2profiler.com/gamemap/
// @icon https://www.google.com/s2/favicons?sz=64&domain=df2profiler.com
// @grant none
// @license GPL-3.0
// ==/UserScript==
// Options
let color = "rgba(255, 255, 0, 0.3)"; // default color for drawing
const pvpColor = "rgba(255, 0, 0, 0.4)"; // color for pvp area
const outpostColor = "rgba(0, 255, 0, 0.4)"; // color for outpost area
const chunkSize = 6; // echo chunk have (chunkSize * chunkSize) cells
// Main
let mouseDown = false;
let drawingMode = false;
let drewCells = [];
function onPickerInput(value) {
color = value;
}
function onSavedMap() {
const tbody = document.querySelector("#map tbody");
const backgroundImgUrl = window
.getComputedStyle(document.querySelector("#map"))
.getPropertyValue("background-image");
const canvas = document.createElement("canvas");
canvas.width = tbody.clientWidth;
canvas.height = tbody.clientHeight;
const ctx = canvas.getContext("2d");
const img = new Image();
img.src = backgroundImgUrl.slice(5, -2);
img.onload = () => {
// draw background image
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
// draw pvp area
const pvpCells = tbody.querySelectorAll(".pvpZone");
pvpCells.forEach((cell) => {
const rect = cell.getBoundingClientRect();
const x = rect.left - tbody.getBoundingClientRect().left;
const y = rect.top - tbody.getBoundingClientRect().top;
ctx.fillStyle = pvpColor;
ctx.fillRect(x, y, rect.width, rect.height);
});
// draw outpost area
const outpostCells = tbody.querySelectorAll(".outpost");
outpostCells.forEach((cell) => {
const rect = cell.getBoundingClientRect();
const x = rect.left - tbody.getBoundingClientRect().left;
const y = rect.top - tbody.getBoundingClientRect().top;
ctx.fillStyle = outpostColor;
ctx.fillRect(x, y, rect.width, rect.height);
});
// draw area
const cells = tbody.querySelectorAll("td");
cells.forEach((cell) => {
if (cell.style.backgroundColor === "") return;
const rect = cell.getBoundingClientRect();
const x = rect.left - tbody.getBoundingClientRect().left;
const y = rect.top - tbody.getBoundingClientRect().top;
ctx.fillStyle = cell.style.backgroundColor;
ctx.fillRect(x, y, rect.width, rect.height);
});
// draw grid (6x6 cells)
const td = tbody.querySelector("td");
const chunkWidth = td.clientWidth * chunkSize;
const chunkHeight = td.clientHeight * chunkSize;
ctx.strokeStyle = "white";
ctx.lineWidth = 1;
ctx.beginPath();
for (let x = chunkWidth; x < canvas.width; x += chunkWidth) {
ctx.moveTo(x, 0);
ctx.lineTo(x, canvas.height);
}
for (let y = chunkHeight; y < canvas.height; y += chunkHeight) {
ctx.moveTo(0, y);
ctx.lineTo(canvas.width, y);
}
ctx.stroke();
// download image
const a = document.createElement("a");
a.href = canvas.toDataURL("image/png");
a.download = "map.png";
a.click();
};
}
(function () {
"use strict";
const tbody = document.querySelector("#map tbody");
tbody.addEventListener("click", (e) => {
const cell = e.target.closest("td");
if (!cell) return;
if (drawingMode) {
cell.style.backgroundColor = color;
} else {
cell.style.backgroundColor = "";
}
});
tbody.addEventListener("mousedown", (e) => {
mouseDown = true;
const cell = e.target.closest("td");
if (!cell) return;
if (cell.style.backgroundColor === "") {
drawingMode = true;
} else {
drawingMode = false;
}
});
tbody.addEventListener("mousemove", (e) => {
if (!mouseDown) return;
const cell = e.target.closest("td");
if (!cell) return;
if (drewCells.includes(cell)) return;
if (drawingMode) {
cell.style.backgroundColor = color;
} else {
cell.style.backgroundColor = "";
}
drewCells.push(cell);
});
tbody.addEventListener("mouseup", (e) => {
mouseDown = false;
drewCells = [];
});
const navbarLinks = document.querySelector("#navbar-links");
// color picker (rgba)
const colorPicker = document.createElement("input");
colorPicker.value = color;
colorPicker.style.padding = "0 0.5rem";
colorPicker.style.margin = "0.3rem 1rem";
colorPicker.style.borderRadius =
navbarLinks.getBoundingClientRect().height / 2 + "px";
colorPicker.style.marginLeft = "auto";
colorPicker.style.cursor = "pointer";
colorPicker.setAttribute("data-jscolor", {});
colorPicker.addEventListener("input", function () {
onPickerInput(this.jscolor.toRGBAString());
});
navbarLinks.appendChild(colorPicker);
// screenshot button
const screenshotBtn = document.createElement("button");
screenshotBtn.textContent = "Screenshot Map";
screenshotBtn.style.padding = "0 1rem";
screenshotBtn.style.margin = "0.3rem 0rem";
screenshotBtn.style.borderRadius =
navbarLinks.getBoundingClientRect().height / 2 + "px";
screenshotBtn.style.cursor = "pointer";
navbarLinks.appendChild(screenshotBtn);
screenshotBtn.addEventListener("click", onSavedMap);
})();
/**
* jscolor - JavaScript Color Picker
*
* @link http://jscolor.com
* @license For open source use: GPLv3
* For commercial use: JSColor Commercial License
* @author Jan Odvarko - East Desire
* @version 2.5.2
*
* See usage examples at http://jscolor.com/examples/
*/
(function (global, factory) {
"use strict";
if (typeof module === "object" && typeof module.exports === "object") {
// Export jscolor as a module
module.exports = global.document
? factory(global)
: function (win) {
if (!win.document) {
throw new Error("jscolor needs a window with document");
}
return factory(win);
};
return;
}
// Default use (no module export)
factory(global);
})(typeof window !== "undefined" ? window : this, function (window) {
// BEGIN factory
// BEGIN jscolor code
"use strict";
var jscolor = (function () {
// BEGIN jscolor
var jsc = {
initialized: false,
instances: [], // created instances of jscolor
readyQueue: [], // functions waiting to be called after init
register: function () {
if (typeof window !== "undefined" && window.document) {
if (window.document.readyState !== "loading") {
jsc.pub.init();
} else {
window.document.addEventListener(
"DOMContentLoaded",
jsc.pub.init,
false
);
}
}
},
installBySelector: function (selector, rootNode) {
rootNode = rootNode ? jsc.node(rootNode) : window.document;
if (!rootNode) {
throw new Error("Missing root node");
}
var elms = rootNode.querySelectorAll(selector);
// for backward compatibility with DEPRECATED installation/configuration using className
var matchClass = new RegExp(
"(^|\\s)(" + jsc.pub.lookupClass + ")(\\s*(\\{[^}]*\\})|\\s|$)",
"i"
);
for (var i = 0; i < elms.length; i += 1) {
if (elms[i].jscolor && elms[i].jscolor instanceof jsc.pub) {
continue; // jscolor already installed on this element
}
if (
elms[i].type !== undefined &&
elms[i].type.toLowerCase() == "color" &&
jsc.isColorAttrSupported
) {
continue; // skips inputs of type 'color' if supported by the browser
}
var dataOpts, m;
if (
(dataOpts = jsc.getDataAttr(elms[i], "jscolor")) !== null ||
(elms[i].className && (m = elms[i].className.match(matchClass))) // installation using className (DEPRECATED)
) {
var targetElm = elms[i];
var optsStr = "";
if (dataOpts !== null) {
optsStr = dataOpts;
} else if (m) {
// installation using className (DEPRECATED)
console.warn(
'Installation using class name is DEPRECATED. Use data-jscolor="" attribute instead.' +
jsc.docsRef
);
if (m[4]) {
optsStr = m[4];
}
}
var opts = null;
if (optsStr.trim()) {
try {
opts = jsc.parseOptionsStr(optsStr);
} catch (e) {
console.warn(e + "\n" + optsStr);
}
}
try {
new jsc.pub(targetElm, opts);
} catch (e) {
console.warn(e);
}
}
}
},
parseOptionsStr: function (str) {
var opts = null;
try {
opts = JSON.parse(str);
} catch (eParse) {
if (!jsc.pub.looseJSON) {
throw new Error(
"Could not parse jscolor options as JSON: " + eParse
);
} else {
// loose JSON syntax is enabled -> try to evaluate the options string as JavaScript object
try {
opts = new Function(
"var opts = (" +
str +
'); return typeof opts === "object" ? opts : {};'
)();
} catch (eEval) {
throw new Error("Could not evaluate jscolor options: " + eEval);
}
}
}
return opts;
},
getInstances: function () {
var inst = [];
for (var i = 0; i < jsc.instances.length; i += 1) {
// if the targetElement still exists, the instance is considered "alive"
if (jsc.instances[i] && jsc.instances[i].targetElement) {
inst.push(jsc.instances[i]);
}
}
return inst;
},
createEl: function (tagName) {
var el = window.document.createElement(tagName);
jsc.setData(el, "gui", true);
return el;
},
node: function (nodeOrSelector) {
if (!nodeOrSelector) {
return null;
}
if (typeof nodeOrSelector === "string") {
// query selector
var sel = nodeOrSelector;
var el = null;
try {
el = window.document.querySelector(sel);
} catch (e) {
console.warn(e);
return null;
}
if (!el) {
console.warn("No element matches the selector: %s", sel);
}
return el;
}
if (jsc.isNode(nodeOrSelector)) {
// DOM node
return nodeOrSelector;
}
console.warn(
"Invalid node of type %s: %s",
typeof nodeOrSelector,
nodeOrSelector
);
return null;
},
// See https://stackoverflow.com/questions/384286/
isNode: function (val) {
if (typeof Node === "object") {
return val instanceof Node;
}
return (
val &&
typeof val === "object" &&
typeof val.nodeType === "number" &&
typeof val.nodeName === "string"
);
},
nodeName: function (node) {
if (node && node.nodeName) {
return node.nodeName.toLowerCase();
}
return false;
},
removeChildren: function (node) {
while (node.firstChild) {
node.removeChild(node.firstChild);
}
},
isTextInput: function (el) {
return (
el && jsc.nodeName(el) === "input" && el.type.toLowerCase() === "text"
);
},
isButton: function (el) {
if (!el) {
return false;
}
var n = jsc.nodeName(el);
return (
n === "button" ||
(n === "input" &&
["button", "submit", "reset"].indexOf(el.type.toLowerCase()) > -1)
);
},
isButtonEmpty: function (el) {
switch (jsc.nodeName(el)) {
case "input":
return !el.value || el.value.trim() === "";
case "button":
return el.textContent.trim() === "";
}
return null; // could not determine element's text
},
// See https://github.com/WICG/EventListenerOptions/blob/gh-pages/explainer.md
isPassiveEventSupported: (function () {
var supported = false;
try {
var opts = Object.defineProperty({}, "passive", {
get: function () {
supported = true;
},
});
window.addEventListener("testPassive", null, opts);
window.removeEventListener("testPassive", null, opts);
} catch (e) {}
return supported;
})(),
isColorAttrSupported: (function () {
var elm = window.document.createElement("input");
if (elm.setAttribute) {
elm.setAttribute("type", "color");
if (elm.type.toLowerCase() == "color") {
return true;
}
}
return false;
})(),
dataProp: "_data_jscolor",
// usage:
// setData(obj, prop, value)
// setData(obj, {prop:value, ...})
//
setData: function () {
var obj = arguments[0];
if (arguments.length === 3) {
// setting a single property
var data = obj.hasOwnProperty(jsc.dataProp)
? obj[jsc.dataProp]
: (obj[jsc.dataProp] = {});
var prop = arguments[1];
var value = arguments[2];
data[prop] = value;
return true;
} else if (arguments.length === 2 && typeof arguments[1] === "object") {
// setting multiple properties
var data = obj.hasOwnProperty(jsc.dataProp)
? obj[jsc.dataProp]
: (obj[jsc.dataProp] = {});
var map = arguments[1];
for (var prop in map) {
if (map.hasOwnProperty(prop)) {
data[prop] = map[prop];
}
}
return true;
}
throw new Error("Invalid arguments");
},
// usage:
// removeData(obj, prop, [prop...])
//
removeData: function () {
var obj = arguments[0];
if (!obj.hasOwnProperty(jsc.dataProp)) {
return true; // data object does not exist
}
for (var i = 1; i < arguments.length; i += 1) {
var prop = arguments[i];
delete obj[jsc.dataProp][prop];
}
return true;
},
getData: function (obj, prop, setDefault) {
if (!obj.hasOwnProperty(jsc.dataProp)) {
// data object does not exist
if (setDefault !== undefined) {
obj[jsc.dataProp] = {}; // create data object
} else {
return undefined; // no value to return
}
}
var data = obj[jsc.dataProp];
if (!data.hasOwnProperty(prop) && setDefault !== undefined) {
data[prop] = setDefault;
}
return data[prop];
},
getDataAttr: function (el, name) {
var attrName = "data-" + name;
var attrValue = el.getAttribute(attrName);
return attrValue;
},
setDataAttr: function (el, name, value) {
var attrName = "data-" + name;
el.setAttribute(attrName, value);
},
_attachedGroupEvents: {},
attachGroupEvent: function (groupName, el, evnt, func) {
if (!jsc._attachedGroupEvents.hasOwnProperty(groupName)) {
jsc._attachedGroupEvents[groupName] = [];
}
jsc._attachedGroupEvents[groupName].push([el, evnt, func]);
el.addEventListener(evnt, func, false);
},
detachGroupEvents: function (groupName) {
if (jsc._attachedGroupEvents.hasOwnProperty(groupName)) {
for (
var i = 0;
i < jsc._attachedGroupEvents[groupName].length;
i += 1
) {
var evt = jsc._attachedGroupEvents[groupName][i];
evt[0].removeEventListener(evt[1], evt[2], false);
}
delete jsc._attachedGroupEvents[groupName];
}
},
preventDefault: function (e) {
if (e.preventDefault) {
e.preventDefault();
}
e.returnValue = false;
},
triggerEvent: function (el, eventName, bubbles, cancelable) {
if (!el) {
return;
}
var ev = null;
if (typeof Event === "function") {
ev = new Event(eventName, {
bubbles: bubbles,
cancelable: cancelable,
});
} else {
// IE
ev = window.document.createEvent("Event");
ev.initEvent(eventName, bubbles, cancelable);
}
if (!ev) {
return false;
}
// so that we know that the event was triggered internally
jsc.setData(ev, "internal", true);
el.dispatchEvent(ev);
return true;
},
triggerInputEvent: function (el, eventName, bubbles, cancelable) {
if (!el) {
return;
}
if (jsc.isTextInput(el)) {
jsc.triggerEvent(el, eventName, bubbles, cancelable);
}
},
eventKey: function (ev) {
var keys = {
9: "Tab",
13: "Enter",
27: "Escape",
};
if (typeof ev.code === "string") {
return ev.code;
} else if (
ev.keyCode !== undefined &&
keys.hasOwnProperty(ev.keyCode)
) {
return keys[ev.keyCode];
}
return null;
},
strList: function (str) {
if (!str) {
return [];
}
return str.replace(/^\s+|\s+$/g, "").split(/\s+/);
},
// The className parameter (str) can only contain a single class name
hasClass: function (elm, className) {
if (!className) {
return false;
}
if (elm.classList !== undefined) {
return elm.classList.contains(className);
}
// polyfill
return (
-1 !=
(" " + elm.className.replace(/\s+/g, " ") + " ").indexOf(
" " + className + " "
)
);
},
// The className parameter (str) can contain multiple class names separated by whitespace
addClass: function (elm, className) {
var classNames = jsc.strList(className);
if (elm.classList !== undefined) {
for (var i = 0; i < classNames.length; i += 1) {
elm.classList.add(classNames[i]);
}
return;
}
// polyfill
for (var i = 0; i < classNames.length; i += 1) {
if (!jsc.hasClass(elm, classNames[i])) {
elm.className += (elm.className ? " " : "") + classNames[i];
}
}
},
// The className parameter (str) can contain multiple class names separated by whitespace
removeClass: function (elm, className) {
var classNames = jsc.strList(className);
if (elm.classList !== undefined) {
for (var i = 0; i < classNames.length; i += 1) {
elm.classList.remove(classNames[i]);
}
return;
}
// polyfill
for (var i = 0; i < classNames.length; i += 1) {
var repl = new RegExp(
"^\\s*" +
classNames[i] +
"\\s*|" +
"\\s*" +
classNames[i] +
"\\s*$|" +
"\\s+" +
classNames[i] +
"(\\s+)",
"g"
);
elm.className = elm.className.replace(repl, "$1");
}
},
getCompStyle: function (elm) {
var compStyle = window.getComputedStyle
? window.getComputedStyle(elm)
: elm.currentStyle;
// Note: In Firefox, getComputedStyle returns null in a hidden iframe,
// that's why we need to check if the returned value is non-empty
if (!compStyle) {
return {};
}
return compStyle;
},
// Note:
// Setting a property to NULL reverts it to the state before it was first set
// with the 'reversible' flag enabled
//
setStyle: function (elm, styles, important, reversible) {
// using '' for standard priority (IE10 apparently doesn't like value undefined)
var priority = important ? "important" : "";
var origStyle = null;
for (var prop in styles) {
if (styles.hasOwnProperty(prop)) {
var setVal = null;
if (styles[prop] === null) {
// reverting a property value
if (!origStyle) {
// get the original style object, but dont't try to create it if it doesn't exist
origStyle = jsc.getData(elm, "origStyle");
}
if (origStyle && origStyle.hasOwnProperty(prop)) {
// we have property's original value -> use it
setVal = origStyle[prop];
}
} else {
// setting a property value
if (reversible) {
if (!origStyle) {
// get the original style object and if it doesn't exist, create it
origStyle = jsc.getData(elm, "origStyle", {});
}
if (!origStyle.hasOwnProperty(prop)) {
// original property value not yet stored -> store it
origStyle[prop] = elm.style[prop];
}
}
setVal = styles[prop];
}
if (setVal !== null) {
elm.style.setProperty(prop, setVal, priority);
}
}
}
},
appendCss: function (css) {
var head = document.querySelector("head");
var style = document.createElement("style");
style.innerText = css;
head.appendChild(style);
},
appendDefaultCss: function (css) {
jsc.appendCss(
[
".jscolor-wrap, .jscolor-wrap div, .jscolor-wrap canvas { " +
"position:static; display:block; visibility:visible; overflow:visible; margin:0; padding:0; " +
"border:none; border-radius:0; outline:none; z-index:auto; float:none; " +
"width:auto; height:auto; left:auto; right:auto; top:auto; bottom:auto; min-width:0; min-height:0; max-width:none; max-height:none; " +
"background:none; clip:auto; opacity:1; transform:none; box-shadow:none; box-sizing:content-box; " +
"}",
".jscolor-wrap { clear:both; }",
".jscolor-wrap .jscolor-picker { position:relative; }",
".jscolor-wrap .jscolor-shadow { position:absolute; left:0; top:0; width:100%; height:100%; }",
".jscolor-wrap .jscolor-border { position:relative; }",
".jscolor-wrap .jscolor-palette { position:absolute; }",
".jscolor-wrap .jscolor-palette-sw { position:absolute; display:block; cursor:pointer; }",
".jscolor-wrap .jscolor-btn { position:absolute; overflow:hidden; white-space:nowrap; font:13px sans-serif; text-align:center; cursor:pointer; }",
].join("\n")
);
},
hexColor: function (r, g, b) {
return (
"#" +
(
("0" + Math.round(r).toString(16)).slice(-2) +
("0" + Math.round(g).toString(16)).slice(-2) +
("0" + Math.round(b).toString(16)).slice(-2)
).toUpperCase()
);
},
hexaColor: function (r, g, b, a) {
return (
"#" +
(
("0" + Math.round(r).toString(16)).slice(-2) +
("0" + Math.round(g).toString(16)).slice(-2) +
("0" + Math.round(b).toString(16)).slice(-2) +
("0" + Math.round(a * 255).toString(16)).slice(-2)
).toUpperCase()
);
},
rgbColor: function (r, g, b) {
return (
"rgb(" +
Math.round(r) +
"," +
Math.round(g) +
"," +
Math.round(b) +
")"
);
},
rgbaColor: function (r, g, b, a) {
return (
"rgba(" +
Math.round(r) +
"," +
Math.round(g) +
"," +
Math.round(b) +
"," +
Math.round((a === undefined || a === null ? 1 : a) * 100) / 100 +
")"
);
},
linearGradient: (function () {
function getFuncName() {
var stdName = "linear-gradient";
var prefixes = ["", "-webkit-", "-moz-", "-o-", "-ms-"];
var helper = window.document.createElement("div");
for (var i = 0; i < prefixes.length; i += 1) {
var tryFunc = prefixes[i] + stdName;
var tryVal = tryFunc + "(to right, rgba(0,0,0,0), rgba(0,0,0,0))";
helper.style.background = tryVal;
if (helper.style.background) {
// CSS background successfully set -> function name is supported
return tryFunc;
}
}
return stdName; // fallback to standard 'linear-gradient' without vendor prefix
}
var funcName = getFuncName();
return function () {
return (
funcName + "(" + Array.prototype.join.call(arguments, ", ") + ")"
);
};
})(),
setBorderRadius: function (elm, value) {
jsc.setStyle(elm, { "border-radius": value || "0" });
},
setBoxShadow: function (elm, value) {
jsc.setStyle(elm, { "box-shadow": value || "none" });
},
getElementPos: function (e, relativeToViewport) {
var x = 0,
y = 0;
var rect = e.getBoundingClientRect();
x = rect.left;
y = rect.top;
if (!relativeToViewport) {
var viewPos = jsc.getViewPos();
x += viewPos[0];
y += viewPos[1];
}
return [x, y];
},
getElementSize: function (e) {
return [e.offsetWidth, e.offsetHeight];
},
// get pointer's X/Y coordinates relative to viewport
getAbsPointerPos: function (e) {
var x = 0,
y = 0;
if (
typeof e.changedTouches !== "undefined" &&
e.changedTouches.length
) {
// touch devices
x = e.changedTouches[0].clientX;
y = e.changedTouches[0].clientY;
} else if (typeof e.clientX === "number") {
x = e.clientX;
y = e.clientY;
}
return { x: x, y: y };
},
// get pointer's X/Y coordinates relative to target element
getRelPointerPos: function (e) {
var target = e.target || e.srcElement;
var targetRect = target.getBoundingClientRect();
var x = 0,
y = 0;
var clientX = 0,
clientY = 0;
if (
typeof e.changedTouches !== "undefined" &&
e.changedTouches.length
) {
// touch devices
clientX = e.changedTouches[0].clientX;
clientY = e.changedTouches[0].clientY;
} else if (typeof e.clientX === "number") {
clientX = e.clientX;
clientY = e.clientY;
}
x = clientX - targetRect.left;
y = clientY - targetRect.top;
return { x: x, y: y };
},
getViewPos: function () {
var doc = window.document.documentElement;
return [
(window.pageXOffset || doc.scrollLeft) - (doc.clientLeft || 0),
(window.pageYOffset || doc.scrollTop) - (doc.clientTop || 0),
];
},
getViewSize: function () {
var doc = window.document.documentElement;
return [
window.innerWidth || doc.clientWidth,
window.innerHeight || doc.clientHeight,
];
},
// r: 0-255
// g: 0-255
// b: 0-255
//
// returns: [ 0-360, 0-100, 0-100 ]
//
RGB_HSV: function (r, g, b) {
r /= 255;
g /= 255;
b /= 255;
var n = Math.min(Math.min(r, g), b);
var v = Math.max(Math.max(r, g), b);
var m = v - n;
if (m === 0) {
return [null, 0, 100 * v];
}
var h =
r === n
? 3 + (b - g) / m
: g === n
? 5 + (r - b) / m
: 1 + (g - r) / m;
return [60 * (h === 6 ? 0 : h), 100 * (m / v), 100 * v];
},
// h: 0-360
// s: 0-100
// v: 0-100
//
// returns: [ 0-255, 0-255, 0-255 ]
//
HSV_RGB: function (h, s, v) {
var u = 255 * (v / 100);
if (h === null) {
return [u, u, u];
}
h /= 60;
s /= 100;
var i = Math.floor(h);
var f = i % 2 ? h - i : 1 - (h - i);
var m = u * (1 - s);
var n = u * (1 - s * f);
switch (i) {
case 6:
case 0:
return [u, n, m];
case 1:
return [n, u, m];
case 2:
return [m, u, n];
case 3:
return [m, n, u];
case 4:
return [n, m, u];
case 5:
return [u, m, n];
}
},
parseColorString: function (str) {
var ret = {
rgba: null,
format: null, // 'hex' | 'hexa' | 'rgb' | 'rgba'
};
var m;
if ((m = str.match(/^\W*([0-9A-F]{3,8})\W*$/i))) {
// HEX notation
if (m[1].length === 8) {
// 8-char notation (= with alpha)
ret.format = "hexa";
ret.rgba = [
parseInt(m[1].slice(0, 2), 16),
parseInt(m[1].slice(2, 4), 16),
parseInt(m[1].slice(4, 6), 16),
parseInt(m[1].slice(6, 8), 16) / 255,
];
} else if (m[1].length === 6) {
// 6-char notation
ret.format = "hex";
ret.rgba = [
parseInt(m[1].slice(0, 2), 16),
parseInt(m[1].slice(2, 4), 16),
parseInt(m[1].slice(4, 6), 16),
null,
];
} else if (m[1].length === 3) {
// 3-char notation
ret.format = "hex";
ret.rgba = [
parseInt(m[1].charAt(0) + m[1].charAt(0), 16),
parseInt(m[1].charAt(1) + m[1].charAt(1), 16),
parseInt(m[1].charAt(2) + m[1].charAt(2), 16),
null,
];
} else {
return false;
}
return ret;
}
if ((m = str.match(/^\W*rgba?\(([^)]*)\)\W*$/i))) {
// rgb(...) or rgba(...) notation
var par = m[1].split(",");
var re = /^\s*(\d+|\d*\.\d+|\d+\.\d*)\s*$/;
var mR, mG, mB, mA;
if (
par.length >= 3 &&
(mR = par[0].match(re)) &&
(mG = par[1].match(re)) &&
(mB = par[2].match(re))
) {
ret.format = "rgb";
ret.rgba = [
parseFloat(mR[1]) || 0,
parseFloat(mG[1]) || 0,
parseFloat(mB[1]) || 0,
null,
];
if (par.length >= 4 && (mA = par[3].match(re))) {
ret.format = "rgba";
ret.rgba[3] = parseFloat(mA[1]) || 0;
}
return ret;
}
}
return false;
},
parsePaletteValue: function (mixed) {
var vals = [];
if (typeof mixed === "string") {
// input is a string of space separated color values
// rgb() and rgba() may contain spaces too, so let's find all color values by regex
mixed.replace(
/#[0-9A-F]{3}\b|#[0-9A-F]{6}([0-9A-F]{2})?\b|rgba?\(([^)]*)\)/gi,
function (val) {
vals.push(val);
}
);
} else if (Array.isArray(mixed)) {
// input is an array of color values
vals = mixed;
}
// convert all values into uniform color format
var colors = [];
for (var i = 0; i < vals.length; i++) {
var color = jsc.parseColorString(vals[i]);
if (color) {
colors.push(color);
}
}
return colors;
},
containsTranparentColor: function (colors) {
for (var i = 0; i < colors.length; i++) {
var a = colors[i].rgba[3];
if (a !== null && a < 1.0) {
return true;
}
}
return false;
},
isAlphaFormat: function (format) {
switch (format.toLowerCase()) {
case "hexa":
case "rgba":
return true;
}
return false;
},
// Canvas scaling for retina displays
//
// adapted from https://www.html5rocks.com/en/tutorials/canvas/hidpi/
//
scaleCanvasForHighDPR: function (canvas) {
var dpr = window.devicePixelRatio || 1;
canvas.width *= dpr;
canvas.height *= dpr;
var ctx = canvas.getContext("2d");
ctx.scale(dpr, dpr);
},
genColorPreviewCanvas: function (
color,
separatorPos,
specWidth,
scaleForHighDPR
) {
var sepW = Math.round(jsc.pub.previewSeparator.length);
var sqSize = jsc.pub.chessboardSize;
var sqColor1 = jsc.pub.chessboardColor1;
var sqColor2 = jsc.pub.chessboardColor2;
var cWidth = specWidth ? specWidth : sqSize * 2;
var cHeight = sqSize * 2;
var canvas = jsc.createEl("canvas");
var ctx = canvas.getContext("2d");
canvas.width = cWidth;
canvas.height = cHeight;
if (scaleForHighDPR) {
jsc.scaleCanvasForHighDPR(canvas);
}
// transparency chessboard - background
ctx.fillStyle = sqColor1;
ctx.fillRect(0, 0, cWidth, cHeight);
// transparency chessboard - squares
ctx.fillStyle = sqColor2;
for (var x = 0; x < cWidth; x += sqSize * 2) {
ctx.fillRect(x, 0, sqSize, sqSize);
ctx.fillRect(x + sqSize, sqSize, sqSize, sqSize);
}
if (color) {
// actual color in foreground
ctx.fillStyle = color;
ctx.fillRect(0, 0, cWidth, cHeight);
}
var start = null;
switch (separatorPos) {
case "left":
start = 0;
ctx.clearRect(0, 0, sepW / 2, cHeight);
break;
case "right":
start = cWidth - sepW;
ctx.clearRect(cWidth - sepW / 2, 0, sepW / 2, cHeight);
break;
}
if (start !== null) {
ctx.lineWidth = 1;
for (var i = 0; i < jsc.pub.previewSeparator.length; i += 1) {
ctx.beginPath();
ctx.strokeStyle = jsc.pub.previewSeparator[i];
ctx.moveTo(0.5 + start + i, 0);
ctx.lineTo(0.5 + start + i, cHeight);
ctx.stroke();
}
}
return {
canvas: canvas,
width: cWidth,
height: cHeight,
};
},
// if position or width is not set => fill the entire element (0%-100%)
genColorPreviewGradient: function (color, position, width) {
var params = [];
if (position && width) {
params = [
"to " + { left: "right", right: "left" }[position],
color + " 0%",
color + " " + width + "px",
"rgba(0,0,0,0) " + (width + 1) + "px",
"rgba(0,0,0,0) 100%",
];
} else {
params = ["to right", color + " 0%", color + " 100%"];
}
return jsc.linearGradient.apply(this, params);
},
redrawPosition: function () {
if (!jsc.picker || !jsc.picker.owner) {
return; // picker is not shown
}
var thisObj = jsc.picker.owner;
if (thisObj.container !== window.document.body) {
jsc._drawPosition(thisObj, 0, 0, "relative", false);
} else {
var tp, vp;
if (thisObj.fixed) {
// Fixed elements are positioned relative to viewport,
// therefore we can ignore the scroll offset
tp = jsc.getElementPos(thisObj.targetElement, true); // target pos
vp = [0, 0]; // view pos
} else {
tp = jsc.getElementPos(thisObj.targetElement); // target pos
vp = jsc.getViewPos(); // view pos
}
var ts = jsc.getElementSize(thisObj.targetElement); // target size
var vs = jsc.getViewSize(); // view size
var pd = jsc.getPickerDims(thisObj);
var ps = [pd.borderW, pd.borderH]; // picker outer size
var a, b, c;
switch (thisObj.position.toLowerCase()) {
case "left":
a = 1;
b = 0;
c = -1;
break;
case "right":
a = 1;
b = 0;
c = 1;
break;
case "top":
a = 0;
b = 1;
c = -1;
break;
default:
a = 0;
b = 1;
c = 1;
break;
}
var l = (ts[b] + ps[b]) / 2;
// compute picker position
if (!thisObj.smartPosition) {
var pp = [tp[a], tp[b] + ts[b] - l + l * c];
} else {
var pp = [
-vp[a] + tp[a] + ps[a] > vs[a]
? -vp[a] + tp[a] + ts[a] / 2 > vs[a] / 2 &&
tp[a] + ts[a] - ps[a] >= 0
? tp[a] + ts[a] - ps[a]
: tp[a]
: tp[a],
-vp[b] + tp[b] + ts[b] + ps[b] - l + l * c > vs[b]
? -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
: tp[b] + ts[b] - l + l * c >= 0
? tp[b] + ts[b] - l + l * c
: tp[b] + ts[b] - l - l * c,
];
}
var x = pp[a];
var y = pp[b];
var positionValue = thisObj.fixed ? "fixed" : "absolute";
var contractShadow =
(pp[0] + ps[0] > tp[0] || pp[0] < tp[0] + ts[0]) &&
pp[1] + ps[1] < tp[1] + ts[1];
jsc._drawPosition(thisObj, x, y, positionValue, contractShadow);
}
},
_drawPosition: function (thisObj, x, y, positionValue, contractShadow) {
var vShadow = contractShadow ? 0 : thisObj.shadowBlur; // px
jsc.picker.wrap.style.position = positionValue;
if (
// To avoid unnecessary repositioning during scroll
Math.round(parseFloat(jsc.picker.wrap.style.left)) !==
Math.round(x) ||
Math.round(parseFloat(jsc.picker.wrap.style.top)) !== Math.round(y)
) {
jsc.picker.wrap.style.left = x + "px";
jsc.picker.wrap.style.top = y + "px";
}
jsc.setBoxShadow(
jsc.picker.boxS,
thisObj.shadow
? new jsc.BoxShadow(
0,
vShadow,
thisObj.shadowBlur,
0,
thisObj.shadowColor
)
: null
);
},
getPickerDims: function (thisObj) {
var w = 2 * thisObj.controlBorderWidth + thisObj.width;
var h = 2 * thisObj.controlBorderWidth + thisObj.height;
var sliderSpace =
2 * thisObj.controlBorderWidth +
2 * jsc.getControlPadding(thisObj) +
thisObj.sliderSize;
if (jsc.getSliderChannel(thisObj)) {
w += sliderSpace;
}
if (thisObj.hasAlphaChannel()) {
w += sliderSpace;
}
var pal = jsc.getPaletteDims(thisObj, w);
if (pal.height) {
h += pal.height + thisObj.padding;
}
if (thisObj.closeButton) {
h +=
2 * thisObj.controlBorderWidth +
thisObj.padding +
thisObj.buttonHeight;
}
var pW = w + 2 * thisObj.padding;
var pH = h + 2 * thisObj.padding;
return {
contentW: w,
contentH: h,
paddedW: pW,
paddedH: pH,
borderW: pW + 2 * thisObj.borderWidth,
borderH: pH + 2 * thisObj.borderWidth,
palette: pal,
};
},
getPaletteDims: function (thisObj, width) {
var cols = 0,
rows = 0,
cellW = 0,
cellH = 0,
height = 0;
var sampleCount = thisObj._palette ? thisObj._palette.length : 0;
if (sampleCount) {
cols = thisObj.paletteCols;
rows = cols > 0 ? Math.ceil(sampleCount / cols) : 0;
// color sample's dimensions (includes border)
cellW = Math.max(
1,
Math.floor((width - (cols - 1) * thisObj.paletteSpacing) / cols)
);
cellH = thisObj.paletteHeight
? Math.min(thisObj.paletteHeight, cellW)
: cellW;
}
if (rows) {
height = rows * cellH + (rows - 1) * thisObj.paletteSpacing;
}
return {
cols: cols,
rows: rows,
cellW: cellW,
cellH: cellH,
width: width,
height: height,
};
},
getControlPadding: function (thisObj) {
return Math.max(
thisObj.padding / 2,
2 * thisObj.pointerBorderWidth +
thisObj.pointerThickness -
thisObj.controlBorderWidth
);
},
getPadYChannel: function (thisObj) {
switch (thisObj.mode.charAt(1).toLowerCase()) {
case "v":
return "v";
break;
}
return "s";
},
getSliderChannel: function (thisObj) {
if (thisObj.mode.length > 2) {
switch (thisObj.mode.charAt(2).toLowerCase()) {
case "s":
return "s";
break;
case "v":
return "v";
break;
}
}
return null;
},
// calls function specified in picker's property
triggerCallback: function (thisObj, prop) {
if (!thisObj[prop]) {
return; // callback func not specified
}
var callback = null;
if (typeof thisObj[prop] === "string") {
// string with code
try {
callback = new Function(thisObj[prop]);
} catch (e) {
console.error(e);
}
} else {
// function
callback = thisObj[prop];
}
if (callback) {
callback.call(thisObj);
}
},
// Triggers a color change related event(s) on all picker instances.
// It is possible to specify multiple events separated with a space.
triggerGlobal: function (eventNames) {
var inst = jsc.getInstances();
for (var i = 0; i < inst.length; i += 1) {
inst[i].trigger(eventNames);
}
},
_pointerMoveEvent: {
mouse: "mousemove",
touch: "touchmove",
},
_pointerEndEvent: {
mouse: "mouseup",
touch: "touchend",
},
_pointerOrigin: null,
onDocumentKeyUp: function (e) {
if (["Tab", "Escape"].indexOf(jsc.eventKey(e)) !== -1) {
if (jsc.picker && jsc.picker.owner) {
jsc.picker.owner.tryHide();
}
}
},
onWindowResize: function (e) {
jsc.redrawPosition();
},
onWindowScroll: function (e) {
jsc.redrawPosition();
},
onParentScroll: function (e) {
// hide the picker when one of the parent elements is scrolled
if (jsc.picker && jsc.picker.owner) {
jsc.picker.owner.tryHide();
}
},
onDocumentMouseDown: function (e) {
var target = e.target || e.srcElement;
if (target.jscolor && target.jscolor instanceof jsc.pub) {
// clicked targetElement -> show picker
if (target.jscolor.showOnClick && !target.disabled) {
target.jscolor.show();
}
} else if (jsc.getData(target, "gui")) {
// clicked jscolor's GUI element
var control = jsc.getData(target, "control");
if (control) {
// jscolor's control
jsc.onControlPointerStart(
e,
target,
jsc.getData(target, "control"),
"mouse"
);
}
} else {
// mouse is outside the picker's controls -> hide the color picker!
if (jsc.picker && jsc.picker.owner) {
jsc.picker.owner.tryHide();
}
}
},
onPickerTouchStart: function (e) {
var target = e.target || e.srcElement;
if (jsc.getData(target, "control")) {
jsc.onControlPointerStart(
e,
target,
jsc.getData(target, "control"),
"touch"
);
}
},
onControlPointerStart: function (e, target, controlName, pointerType) {
var thisObj = jsc.getData(target, "instance");
jsc.preventDefault(e);
var registerDragEvents = function (doc, offset) {
jsc.attachGroupEvent(
"drag",
doc,
jsc._pointerMoveEvent[pointerType],
jsc.onDocumentPointerMove(
e,
target,
controlName,
pointerType,
offset
)
);
jsc.attachGroupEvent(
"drag",
doc,
jsc._pointerEndEvent[pointerType],
jsc.onDocumentPointerEnd(e, target, controlName, pointerType)
);
};
registerDragEvents(window.document, [0, 0]);
if (window.parent && window.frameElement) {
var rect = window.frameElement.getBoundingClientRect();
var ofs = [-rect.left, -rect.top];
registerDragEvents(window.parent.window.document, ofs);
}
var abs = jsc.getAbsPointerPos(e);
var rel = jsc.getRelPointerPos(e);
jsc._pointerOrigin = {
x: abs.x - rel.x,
y: abs.y - rel.y,
};
switch (controlName) {
case "pad":
// if the value slider is at the bottom, move it up
if (
jsc.getSliderChannel(thisObj) === "v" &&
thisObj.channels.v === 0
) {
thisObj.fromHSVA(null, null, 100, null);
}
jsc.setPad(thisObj, e, 0, 0);
break;
case "sld":
jsc.setSld(thisObj, e, 0);
break;
case "asld":
jsc.setASld(thisObj, e, 0);
break;
}
thisObj.trigger("input");
},
onDocumentPointerMove: function (
e,
target,
controlName,
pointerType,
offset
) {
return function (e) {
var thisObj = jsc.getData(target, "instance");
switch (controlName) {
case "pad":
jsc.setPad(thisObj, e, offset[0], offset[1]);
break;
case "sld":
jsc.setSld(thisObj, e, offset[1]);
break;
case "asld":
jsc.setASld(thisObj, e, offset[1]);
break;
}
thisObj.trigger("input");
};
},
onDocumentPointerEnd: function (e, target, controlName, pointerType) {
return function (e) {
var thisObj = jsc.getData(target, "instance");
jsc.detachGroupEvents("drag");
// Always trigger changes AFTER detaching outstanding mouse handlers,
// in case some color change that occured in user-defined onChange/onInput handler
// intruded into current mouse events
thisObj.trigger("input");
thisObj.trigger("change");
};
},
onPaletteSampleClick: function (e) {
var target = e.currentTarget;
var thisObj = jsc.getData(target, "instance");
var color = jsc.getData(target, "color");
// when format is flexible, use the original format of this color sample
if (thisObj.format.toLowerCase() === "any") {
thisObj._setFormat(color.format); // adapt format
if (!jsc.isAlphaFormat(thisObj.getFormat())) {
color.rgba[3] = 1.0; // when switching to a format that doesn't support alpha, set full opacity
}
}
// if this color doesn't specify alpha, use alpha of 1.0 (if applicable)
if (color.rgba[3] === null) {
if (
thisObj.paletteSetsAlpha === true ||
(thisObj.paletteSetsAlpha === "auto" &&
thisObj._paletteHasTransparency)
) {
color.rgba[3] = 1.0;
}
}
thisObj.fromRGBA.apply(thisObj, color.rgba);
thisObj.trigger("input");
thisObj.trigger("change");
if (thisObj.hideOnPaletteClick) {
thisObj.hide();
}
},
setPad: function (thisObj, e, ofsX, ofsY) {
var pointerAbs = jsc.getAbsPointerPos(e);
var x =
ofsX +
pointerAbs.x -
jsc._pointerOrigin.x -
thisObj.padding -
thisObj.controlBorderWidth;
var y =
ofsY +
pointerAbs.y -
jsc._pointerOrigin.y -
thisObj.padding -
thisObj.controlBorderWidth;
var xVal = x * (360 / (thisObj.width - 1));
var yVal = 100 - y * (100 / (thisObj.height - 1));
switch (jsc.getPadYChannel(thisObj)) {
case "s":
thisObj.fromHSVA(xVal, yVal, null, null);
break;
case "v":
thisObj.fromHSVA(xVal, null, yVal, null);
break;
}
},
setSld: function (thisObj, e, ofsY) {
var pointerAbs = jsc.getAbsPointerPos(e);
var y =
ofsY +
pointerAbs.y -
jsc._pointerOrigin.y -
thisObj.padding -
thisObj.controlBorderWidth;
var yVal = 100 - y * (100 / (thisObj.height - 1));
switch (jsc.getSliderChannel(thisObj)) {
case "s":
thisObj.fromHSVA(null, yVal, null, null);
break;
case "v":
thisObj.fromHSVA(null, null, yVal, null);
break;
}
},
setASld: function (thisObj, e, ofsY) {
var pointerAbs = jsc.getAbsPointerPos(e);
var y =
ofsY +
pointerAbs.y -
jsc._pointerOrigin.y -
thisObj.padding -
thisObj.controlBorderWidth;
var yVal = 1.0 - y * (1.0 / (thisObj.height - 1));
if (yVal < 1.0) {
// if format is flexible and the current format doesn't support alpha, switch to a suitable one
var fmt = thisObj.getFormat();
if (
thisObj.format.toLowerCase() === "any" &&
!jsc.isAlphaFormat(fmt)
) {
thisObj._setFormat(fmt === "hex" ? "hexa" : "rgba");
}
}
thisObj.fromHSVA(null, null, null, yVal);
},
createPadCanvas: function () {
var ret = {
elm: null,
draw: null,
};
var canvas = jsc.createEl("canvas");
var ctx = canvas.getContext("2d");
var drawFunc = function (width, height, type) {
canvas.width = width;
canvas.height = height;
ctx.clearRect(0, 0, canvas.width, canvas.height);
var hGrad = ctx.createLinearGradient(0, 0, canvas.width, 0);
hGrad.addColorStop(0 / 6, "#F00");
hGrad.addColorStop(1 / 6, "#FF0");
hGrad.addColorStop(2 / 6, "#0F0");
hGrad.addColorStop(3 / 6, "#0FF");
hGrad.addColorStop(4 / 6, "#00F");
hGrad.addColorStop(5 / 6, "#F0F");
hGrad.addColorStop(6 / 6, "#F00");
ctx.fillStyle = hGrad;
ctx.fillRect(0, 0, canvas.width, canvas.height);
var vGrad = ctx.createLinearGradient(0, 0, 0, canvas.height);
switch (type.toLowerCase()) {
case "s":
vGrad.addColorStop(0, "rgba(255,255,255,0)");
vGrad.addColorStop(1, "rgba(255,255,255,1)");
break;
case "v":
vGrad.addColorStop(0, "rgba(0,0,0,0)");
vGrad.addColorStop(1, "rgba(0,0,0,1)");
break;
}
ctx.fillStyle = vGrad;
ctx.fillRect(0, 0, canvas.width, canvas.height);
};
ret.elm = canvas;
ret.draw = drawFunc;
return ret;
},
createSliderGradient: function () {
var ret = {
elm: null,
draw: null,
};
var canvas = jsc.createEl("canvas");
var ctx = canvas.getContext("2d");
var drawFunc = function (width, height, color1, color2) {
canvas.width = width;
canvas.height = height;
ctx.clearRect(0, 0, canvas.width, canvas.height);
var grad = ctx.createLinearGradient(0, 0, 0, canvas.height);
grad.addColorStop(0, color1);
grad.addColorStop(1, color2);
ctx.fillStyle = grad;
ctx.fillRect(0, 0, canvas.width, canvas.height);
};
ret.elm = canvas;
ret.draw = drawFunc;
return ret;
},
createASliderGradient: function () {
var ret = {
elm: null,
draw: null,
};
var canvas = jsc.createEl("canvas");
var ctx = canvas.getContext("2d");
var drawFunc = function (width, height, color) {
canvas.width = width;
canvas.height = height;
ctx.clearRect(0, 0, canvas.width, canvas.height);
var sqSize = canvas.width / 2;
var sqColor1 = jsc.pub.chessboardColor1;
var sqColor2 = jsc.pub.chessboardColor2;
// dark gray background
ctx.fillStyle = sqColor1;
ctx.fillRect(0, 0, canvas.width, canvas.height);
if (sqSize > 0) {
// to avoid infinite loop
for (var y = 0; y < canvas.height; y += sqSize * 2) {
// light gray squares
ctx.fillStyle = sqColor2;
ctx.fillRect(0, y, sqSize, sqSize);
ctx.fillRect(sqSize, y + sqSize, sqSize, sqSize);
}
}
var grad = ctx.createLinearGradient(0, 0, 0, canvas.height);
grad.addColorStop(0, color);
grad.addColorStop(1, "rgba(0,0,0,0)");
ctx.fillStyle = grad;
ctx.fillRect(0, 0, canvas.width, canvas.height);
};
ret.elm = canvas;
ret.draw = drawFunc;
return ret;
},
BoxShadow: (function () {
var BoxShadow = function (
hShadow,
vShadow,
blur,
spread,
color,
inset
) {
this.hShadow = hShadow;
this.vShadow = vShadow;
this.blur = blur;
this.spread = spread;
this.color = color;
this.inset = !!inset;
};
BoxShadow.prototype.toString = function () {
var vals = [
Math.round(this.hShadow) + "px",
Math.round(this.vShadow) + "px",
Math.round(this.blur) + "px",
Math.round(this.spread) + "px",
this.color,
];
if (this.inset) {
vals.push("inset");
}
return vals.join(" ");
};
return BoxShadow;
})(),
flags: {
leaveValue: 1 << 0,
leaveAlpha: 1 << 1,
leavePreview: 1 << 2,
},
enumOpts: {
format: ["auto", "any", "hex", "hexa", "rgb", "rgba"],
previewPosition: ["left", "right"],
mode: ["hsv", "hvs", "hs", "hv"],
position: ["left", "right", "top", "bottom"],
alphaChannel: ["auto", true, false],
paletteSetsAlpha: ["auto", true, false],
},
deprecatedOpts: {
// <old_option>: <new_option> (<new_option> can be null)
styleElement: "previewElement",
onFineChange: "onInput",
overwriteImportant: "forceStyle",
closable: "closeButton",
insetWidth: "controlBorderWidth",
insetColor: "controlBorderColor",
refine: null,
},
docsRef: " " + "See https://jscolor.com/docs/",
//
// Usage:
// var myPicker = new JSColor(<targetElement> [, <options>])
//
// (constructor is accessible via both 'jscolor' and 'JSColor' name)
//
pub: function (targetElement, opts) {
var THIS = this;
if (!opts) {
opts = {};
}
this.channels = {
r: 255, // red [0-255]
g: 255, // green [0-255]
b: 255, // blue [0-255]
h: 0, // hue [0-360]
s: 0, // saturation [0-100]
v: 100, // value (brightness) [0-100]
a: 1.0, // alpha (opacity) [0.0 - 1.0]
};
// General options
//
this.format = "auto"; // 'auto' | 'any' | 'hex' | 'hexa' | 'rgb' | 'rgba' - Format of the input/output value
this.value = undefined; // INITIAL color value in any supported format. To change it later, use method fromString(), fromHSVA(), fromRGBA() or channel()
this.alpha = undefined; // INITIAL alpha value. To change it later, call method channel('A', <value>)
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]
this.onChange = undefined; // called when color changes. Value can be either a function or a string with JS code.
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.
this.valueElement = undefined; // element that will be used to display and input the color value
this.alphaElement = undefined; // element that will be used to display and input the alpha (opacity) value
this.previewElement = undefined; // element that will preview the picked color using CSS background
this.previewPosition = "left"; // 'left' | 'right' - position of the color preview in previewElement
this.previewSize = 32; // (px) width of the color preview displayed in previewElement
this.previewPadding = 8; // (px) space between color preview and content of the previewElement
this.required = true; // whether the associated text input must always contain a color value. If false, the input can be left empty.
this.hash = true; // whether to prefix the HEX color code with # symbol (only applicable for HEX format)
this.uppercase = true; // whether to show the HEX color code in upper case (only applicable for HEX format)
this.forceStyle = true; // whether to overwrite CSS style of the previewElement using !important flag
// Color Picker options
//
this.width = 181; // width of the color spectrum (in px)
this.height = 101; // height of the color spectrum (in px)
this.mode = "HSV"; // 'HSV' | 'HVS' | 'HS' | 'HV' - layout of the color picker controls
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
this.position = "bottom"; // 'left' | 'right' | 'top' | 'bottom' - position relative to the target element
this.smartPosition = true; // automatically change picker position when there is not enough space for it
this.showOnClick = true; // whether to show the picker when user clicks its target element
this.hideOnLeave = true; // whether to automatically hide the picker when user leaves its target element (e.g. upon clicking the document)
this.palette = []; // colors to be displayed in the palette, specified as an array or a string of space separated color values (in any supported format)
this.paletteCols = 10; // number of columns in the palette
this.paletteSetsAlpha = "auto"; // 'auto' | true | false - if true, palette colors that don't specify alpha will set alpha to 1.0
this.paletteHeight = 16; // maximum height (px) of a row in the palette
this.paletteSpacing = 4; // distance (px) between color samples in the palette
this.hideOnPaletteClick = false; // when set to true, clicking the palette will also hide the color picker
this.sliderSize = 16; // px
this.crossSize = 8; // px
this.closeButton = false; // whether to display the Close button
this.closeText = "Close";
this.buttonColor = "rgba(0,0,0,1)"; // CSS color
this.buttonHeight = 18; // px
this.padding = 12; // px
this.backgroundColor = "rgba(255,255,255,1)"; // CSS color
this.borderWidth = 1; // px
this.borderColor = "rgba(187,187,187,1)"; // CSS color
this.borderRadius = 8; // px
this.controlBorderWidth = 1; // px
this.controlBorderColor = "rgba(187,187,187,1)"; // CSS color
this.shadow = true; // whether to display a shadow
this.shadowBlur = 15; // px
this.shadowColor = "rgba(0,0,0,0.2)"; // CSS color
this.pointerColor = "rgba(76,76,76,1)"; // CSS color
this.pointerBorderWidth = 1; // px
this.pointerBorderColor = "rgba(255,255,255,1)"; // CSS color
this.pointerThickness = 2; // px
this.zIndex = 5000;
this.container = undefined; // where to append the color picker (BODY element by default)
// Experimental
//
this.minS = 0; // min allowed saturation (0 - 100)
this.maxS = 100; // max allowed saturation (0 - 100)
this.minV = 0; // min allowed value (brightness) (0 - 100)
this.maxV = 100; // max allowed value (brightness) (0 - 100)
this.minA = 0.0; // min allowed alpha (opacity) (0.0 - 1.0)
this.maxA = 1.0; // max allowed alpha (opacity) (0.0 - 1.0)
// Getter: option(name)
// Setter: option(name, value)
// option({name:value, ...})
//
this.option = function () {
if (!arguments.length) {
throw new Error("No option specified");
}
if (arguments.length === 1 && typeof arguments[0] === "string") {
// getting a single option
try {
return getOption(arguments[0]);
} catch (e) {
console.warn(e);
}
return false;
} else if (
arguments.length >= 2 &&
typeof arguments[0] === "string"
) {
// setting a single option
try {
if (!setOption(arguments[0], arguments[1])) {
return false;
}
} catch (e) {
console.warn(e);
return false;
}
this.redraw(); // immediately redraws the picker, if it's displayed
this.exposeColor(); // in case some preview-related or format-related option was changed
return true;
} else if (
arguments.length === 1 &&
typeof arguments[0] === "object"
) {
// setting multiple options
var opts = arguments[0];
var success = true;
for (var opt in opts) {
if (opts.hasOwnProperty(opt)) {
try {
if (!setOption(opt, opts[opt])) {
success = false;
}
} catch (e) {
console.warn(e);
success = false;
}
}
}
this.redraw(); // immediately redraws the picker, if it's displayed
this.exposeColor(); // in case some preview-related or format-related option was changed
return success;
}
throw new Error("Invalid arguments");
};
// Getter: channel(name)
// Setter: channel(name, value)
//
this.channel = function (name, value) {
if (typeof name !== "string") {
throw new Error("Invalid value for channel name: " + name);
}
if (value === undefined) {
// getting channel value
if (!this.channels.hasOwnProperty(name.toLowerCase())) {
console.warn("Getting unknown channel: " + name);
return false;
}
return this.channels[name.toLowerCase()];
} else {
// setting channel value
var res = false;
switch (name.toLowerCase()) {
case "r":
res = this.fromRGBA(value, null, null, null);
break;
case "g":
res = this.fromRGBA(null, value, null, null);
break;
case "b":
res = this.fromRGBA(null, null, value, null);
break;
case "h":
res = this.fromHSVA(value, null, null, null);
break;
case "s":
res = this.fromHSVA(null, value, null, null);
break;
case "v":
res = this.fromHSVA(null, null, value, null);
break;
case "a":
res = this.fromHSVA(null, null, null, value);
break;
default:
console.warn("Setting unknown channel: " + name);
return false;
}
if (res) {
this.redraw(); // immediately redraws the picker, if it's displayed
return true;
}
}
return false;
};
// Triggers given input event(s) by:
// - executing on<Event> callback specified as picker's option
// - triggering standard DOM event listeners attached to the value element
//
// It is possible to specify multiple events separated with a space.
//
this.trigger = function (eventNames) {
var evs = jsc.strList(eventNames);
for (var i = 0; i < evs.length; i += 1) {
var ev = evs[i].toLowerCase();
// trigger a callback
var callbackProp = null;
switch (ev) {
case "input":
callbackProp = "onInput";
break;
case "change":
callbackProp = "onChange";
break;
}
if (callbackProp) {
jsc.triggerCallback(this, callbackProp);
}
// trigger standard DOM event listeners on the value element
jsc.triggerInputEvent(this.valueElement, ev, true, true);
}
};
// h: 0-360
// s: 0-100
// v: 0-100
// a: 0.0-1.0
//
this.fromHSVA = function (h, s, v, a, flags) {
// null = don't change
if (h === undefined) {
h = null;
}
if (s === undefined) {
s = null;
}
if (v === undefined) {
v = null;
}
if (a === undefined) {
a = null;
}
if (h !== null) {
if (isNaN(h)) {
return false;
}
this.channels.h = Math.max(0, Math.min(360, h));
}
if (s !== null) {
if (isNaN(s)) {
return false;
}
this.channels.s = Math.max(
0,
Math.min(100, this.maxS, s),
this.minS
);
}
if (v !== null) {
if (isNaN(v)) {
return false;
}
this.channels.v = Math.max(
0,
Math.min(100, this.maxV, v),
this.minV
);
}
if (a !== null) {
if (isNaN(a)) {
return false;
}
this.channels.a = this.hasAlphaChannel()
? Math.max(0, Math.min(1, this.maxA, a), this.minA)
: 1.0; // if alpha channel is disabled, the color should stay 100% opaque
}
var rgb = jsc.HSV_RGB(
this.channels.h,
this.channels.s,
this.channels.v
);
this.channels.r = rgb[0];
this.channels.g = rgb[1];
this.channels.b = rgb[2];
this.exposeColor(flags);
return true;
};
// r: 0-255
// g: 0-255
// b: 0-255
// a: 0.0-1.0
//
this.fromRGBA = function (r, g, b, a, flags) {
// null = don't change
if (r === undefined) {
r = null;
}
if (g === undefined) {
g = null;
}
if (b === undefined) {
b = null;
}
if (a === undefined) {
a = null;
}
if (r !== null) {
if (isNaN(r)) {
return false;
}
r = Math.max(0, Math.min(255, r));
}
if (g !== null) {
if (isNaN(g)) {
return false;
}
g = Math.max(0, Math.min(255, g));
}
if (b !== null) {
if (isNaN(b)) {
return false;
}
b = Math.max(0, Math.min(255, b));
}
if (a !== null) {
if (isNaN(a)) {
return false;
}
this.channels.a = this.hasAlphaChannel()
? Math.max(0, Math.min(1, this.maxA, a), this.minA)
: 1.0; // if alpha channel is disabled, the color should stay 100% opaque
}
var hsv = jsc.RGB_HSV(
r === null ? this.channels.r : r,
g === null ? this.channels.g : g,
b === null ? this.channels.b : b
);
if (hsv[0] !== null) {
this.channels.h = Math.max(0, Math.min(360, hsv[0]));
}
if (hsv[2] !== 0) {
// fully black color stays black through entire saturation range, so let's not change saturation
this.channels.s = Math.max(
0,
this.minS,
Math.min(100, this.maxS, hsv[1])
);
}
this.channels.v = Math.max(
0,
this.minV,
Math.min(100, this.maxV, hsv[2])
);
// update RGB according to final HSV, as some values might be trimmed
var rgb = jsc.HSV_RGB(
this.channels.h,
this.channels.s,
this.channels.v
);
this.channels.r = rgb[0];
this.channels.g = rgb[1];
this.channels.b = rgb[2];
this.exposeColor(flags);
return true;
};
// DEPRECATED. Use .fromHSVA() instead
//
this.fromHSV = function (h, s, v, flags) {
console.warn(
"fromHSV() method is DEPRECATED. Using fromHSVA() instead." +
jsc.docsRef
);
return this.fromHSVA(h, s, v, null, flags);
};
// DEPRECATED. Use .fromRGBA() instead
//
this.fromRGB = function (r, g, b, flags) {
console.warn(
"fromRGB() method is DEPRECATED. Using fromRGBA() instead." +
jsc.docsRef
);
return this.fromRGBA(r, g, b, null, flags);
};
this.fromString = function (str, flags) {
if (!this.required && str.trim() === "") {
// setting empty string to an optional color input
this.setPreviewElementBg(null);
this.setValueElementValue("");
return true;
}
var color = jsc.parseColorString(str);
if (!color) {
return false; // could not parse
}
if (this.format.toLowerCase() === "any") {
this._setFormat(color.format); // adapt format
if (!jsc.isAlphaFormat(this.getFormat())) {
color.rgba[3] = 1.0; // when switching to a format that doesn't support alpha, set full opacity
}
}
this.fromRGBA(
color.rgba[0],
color.rgba[1],
color.rgba[2],
color.rgba[3],
flags
);
return true;
};
this.randomize = function (
minV,
maxV,
minS,
maxS,
minH,
maxH,
minA,
maxA
) {
if (minV === undefined) {
minV = 0;
}
if (maxV === undefined) {
maxV = 100;
}
if (minS === undefined) {
minS = 0;
}
if (maxS === undefined) {
maxS = 100;
}
if (minH === undefined) {
minH = 0;
}
if (maxH === undefined) {
maxH = 359;
}
if (minA === undefined) {
minA = 1;
}
if (maxA === undefined) {
maxA = 1;
}
this.fromHSVA(
minH + Math.floor(Math.random() * (maxH - minH + 1)),
minS + Math.floor(Math.random() * (maxS - minS + 1)),
minV + Math.floor(Math.random() * (maxV - minV + 1)),
(100 * minA +
Math.floor(Math.random() * (100 * (maxA - minA) + 1))) /
100
);
};
this.toString = function (format) {
if (format === undefined) {
format = this.getFormat(); // format not specified -> use the current format
}
switch (format.toLowerCase()) {
case "hex":
return this.toHEXString();
break;
case "hexa":
return this.toHEXAString();
break;
case "rgb":
return this.toRGBString();
break;
case "rgba":
return this.toRGBAString();
break;
}
return false;
};
this.toHEXString = function () {
return jsc.hexColor(
this.channels.r,
this.channels.g,
this.channels.b
);
};
this.toHEXAString = function () {
return jsc.hexaColor(
this.channels.r,
this.channels.g,
this.channels.b,
this.channels.a
);
};
this.toRGBString = function () {
return jsc.rgbColor(
this.channels.r,
this.channels.g,
this.channels.b
);
};
this.toRGBAString = function () {
return jsc.rgbaColor(
this.channels.r,
this.channels.g,
this.channels.b,
this.channels.a
);
};
this.toGrayscale = function () {
return (
0.213 * this.channels.r +
0.715 * this.channels.g +
0.072 * this.channels.b
);
};
this.toCanvas = function () {
return jsc.genColorPreviewCanvas(this.toRGBAString()).canvas;
};
this.toDataURL = function () {
return this.toCanvas().toDataURL();
};
this.toBackground = function () {
return jsc.pub.background(this.toRGBAString());
};
this.isLight = function () {
return this.toGrayscale() > 255 / 2;
};
this.hide = function () {
if (isPickerOwner()) {
detachPicker();
}
};
this.show = function () {
drawPicker();
};
this.redraw = function () {
if (isPickerOwner()) {
drawPicker();
}
};
this.getFormat = function () {
return this._currentFormat;
};
this._setFormat = function (format) {
this._currentFormat = format.toLowerCase();
};
this.hasAlphaChannel = function () {
if (this.alphaChannel === "auto") {
return (
this.format.toLowerCase() === "any" || // format can change on the fly (e.g. from hex to rgba), so let's consider the alpha channel enabled
jsc.isAlphaFormat(this.getFormat()) || // the current format supports alpha channel
this.alpha !== undefined || // initial alpha value is set, so we're working with alpha channel
this.alphaElement !== undefined // the alpha value is redirected, so we're working with alpha channel
);
}
return this.alphaChannel; // the alpha channel is explicitly set
};
this.processValueInput = function (str) {
if (!this.fromString(str)) {
// could not parse the color value - let's just expose the current color
this.exposeColor();
}
};
this.processAlphaInput = function (str) {
if (!this.fromHSVA(null, null, null, parseFloat(str))) {
// could not parse the alpha value - let's just expose the current color
this.exposeColor();
}
};
this.exposeColor = function (flags) {
var colorStr = this.toString();
var fmt = this.getFormat();
// reflect current color in data- attribute
jsc.setDataAttr(this.targetElement, "current-color", colorStr);
if (!(flags & jsc.flags.leaveValue) && this.valueElement) {
if (fmt === "hex" || fmt === "hexa") {
if (!this.uppercase) {
colorStr = colorStr.toLowerCase();
}
if (!this.hash) {
colorStr = colorStr.replace(/^#/, "");
}
}
this.setValueElementValue(colorStr);
}
if (!(flags & jsc.flags.leaveAlpha) && this.alphaElement) {
var alphaVal = Math.round(this.channels.a * 100) / 100;
this.setAlphaElementValue(alphaVal);
}
if (!(flags & jsc.flags.leavePreview) && this.previewElement) {
var previewPos = null; // 'left' | 'right' (null -> fill the entire element)
if (
jsc.isTextInput(this.previewElement) || // text input
(jsc.isButton(this.previewElement) &&
!jsc.isButtonEmpty(this.previewElement)) // button with text
) {
previewPos = this.previewPosition;
}
this.setPreviewElementBg(this.toRGBAString());
}
if (isPickerOwner()) {
redrawPad();
redrawSld();
redrawASld();
}
};
this.setPreviewElementBg = function (color) {
if (!this.previewElement) {
return;
}
var position = null; // color preview position: null | 'left' | 'right'
var width = null; // color preview width: px | null = fill the entire element
if (
jsc.isTextInput(this.previewElement) || // text input
(jsc.isButton(this.previewElement) &&
!jsc.isButtonEmpty(this.previewElement)) // button with text
) {
position = this.previewPosition;
width = this.previewSize;
}
var backgrounds = [];
if (!color) {
// there is no color preview to display -> let's remove any previous background image
backgrounds.push({
image: "none",
position: "left top",
size: "auto",
repeat: "no-repeat",
origin: "padding-box",
});
} else {
// CSS gradient for background color preview
backgrounds.push({
image: jsc.genColorPreviewGradient(
color,
position,
width ? width - jsc.pub.previewSeparator.length : null
),
position: "left top",
size: "auto",
repeat: position ? "repeat-y" : "repeat",
origin: "padding-box",
});
// data URL of generated PNG image with a gray transparency chessboard
var preview = jsc.genColorPreviewCanvas(
"rgba(0,0,0,0)",
position ? { left: "right", right: "left" }[position] : null,
width,
true
);
backgrounds.push({
image: "url('" + preview.canvas.toDataURL() + "')",
position: (position || "left") + " top",
size: preview.width + "px " + preview.height + "px",
repeat: position ? "repeat-y" : "repeat",
origin: "padding-box",
});
}
var bg = {
image: [],
position: [],
size: [],
repeat: [],
origin: [],
};
for (var i = 0; i < backgrounds.length; i += 1) {
bg.image.push(backgrounds[i].image);
bg.position.push(backgrounds[i].position);
bg.size.push(backgrounds[i].size);
bg.repeat.push(backgrounds[i].repeat);
bg.origin.push(backgrounds[i].origin);
}
// set previewElement's background-images
var sty = {
"background-image": bg.image.join(", "),
"background-position": bg.position.join(", "),
"background-size": bg.size.join(", "),
"background-repeat": bg.repeat.join(", "),
"background-origin": bg.origin.join(", "),
};
jsc.setStyle(this.previewElement, sty, this.forceStyle);
// set/restore previewElement's padding
var padding = {
left: null,
right: null,
};
if (position) {
padding[position] = this.previewSize + this.previewPadding + "px";
}
var sty = {
"padding-left": padding.left,
"padding-right": padding.right,
};
jsc.setStyle(this.previewElement, sty, this.forceStyle, true);
};
this.setValueElementValue = function (str) {
if (this.valueElement) {
if (jsc.nodeName(this.valueElement) === "input") {
this.valueElement.value = str;
} else {
this.valueElement.innerHTML = str;
}
}
};
this.setAlphaElementValue = function (str) {
if (this.alphaElement) {
if (jsc.nodeName(this.alphaElement) === "input") {
this.alphaElement.value = str;
} else {
this.alphaElement.innerHTML = str;
}
}
};
this._processParentElementsInDOM = function () {
if (this._parentElementsProcessed) {
return;
}
this._parentElementsProcessed = true;
var elm = this.targetElement;
do {
// If the target element or one of its parent nodes has fixed position,
// then use fixed positioning instead
var compStyle = jsc.getCompStyle(elm);
if (
compStyle.position &&
compStyle.position.toLowerCase() === "fixed"
) {
this.fixed = true;
}
if (elm !== this.targetElement) {
// Ensure to attach onParentScroll only once to each parent element
// (multiple targetElements can share the same parent nodes)
//
// Note: It's not just offsetParents that can be scrollable,
// that's why we loop through all parent nodes
if (!jsc.getData(elm, "hasScrollListener")) {
elm.addEventListener("scroll", jsc.onParentScroll, false);
jsc.setData(elm, "hasScrollListener", true);
}
}
} while ((elm = elm.parentNode) && jsc.nodeName(elm) !== "body");
};
this.tryHide = function () {
if (this.hideOnLeave) {
this.hide();
}
};
this.set__palette = function (val) {
this.palette = val;
this._palette = jsc.parsePaletteValue(val);
this._paletteHasTransparency = jsc.containsTranparentColor(
this._palette
);
};
function setOption(option, value) {
if (typeof option !== "string") {
throw new Error("Invalid value for option name: " + option);
}
// enum option
if (jsc.enumOpts.hasOwnProperty(option)) {
if (typeof value === "string") {
// enum string values are case insensitive
value = value.toLowerCase();
}
if (jsc.enumOpts[option].indexOf(value) === -1) {
throw new Error(
"Option '" + option + "' has invalid value: " + value
);
}
}
// deprecated option
if (jsc.deprecatedOpts.hasOwnProperty(option)) {
var oldOpt = option;
var newOpt = jsc.deprecatedOpts[option];
if (newOpt) {
// if we have a new name for this option, let's log a warning and use the new name
console.warn(
"Option '%s' is DEPRECATED, using '%s' instead." + jsc.docsRef,
oldOpt,
newOpt
);
option = newOpt;
} else {
// new name not available for the option
throw new Error("Option '" + option + "' is DEPRECATED");
}
}
var setter = "set__" + option;
if (typeof THIS[setter] === "function") {
// a setter exists for this option
THIS[setter](value);
return true;
} else if (option in THIS) {
// option exists as a property
THIS[option] = value;
return true;
}
throw new Error("Unrecognized configuration option: " + option);
}
function getOption(option) {
if (typeof option !== "string") {
throw new Error("Invalid value for option name: " + option);
}
// deprecated option
if (jsc.deprecatedOpts.hasOwnProperty(option)) {
var oldOpt = option;
var newOpt = jsc.deprecatedOpts[option];
if (newOpt) {
// if we have a new name for this option, let's log a warning and use the new name
console.warn(
"Option '%s' is DEPRECATED, using '%s' instead." + jsc.docsRef,
oldOpt,
newOpt
);
option = newOpt;
} else {
// new name not available for the option
throw new Error("Option '" + option + "' is DEPRECATED");
}
}
var getter = "get__" + option;
if (typeof THIS[getter] === "function") {
// a getter exists for this option
return THIS[getter](value);
} else if (option in THIS) {
// option exists as a property
return THIS[option];
}
throw new Error("Unrecognized configuration option: " + option);
}
function detachPicker() {
jsc.removeClass(THIS.targetElement, jsc.pub.activeClassName);
jsc.picker.wrap.parentNode.removeChild(jsc.picker.wrap);
delete jsc.picker.owner;
}
function drawPicker() {
// At this point, when drawing the picker, we know what the parent elements are
// and we can do all related DOM operations, such as registering events on them
// or checking their positioning
THIS._processParentElementsInDOM();
if (!jsc.picker) {
jsc.picker = {
owner: null, // owner picker instance
wrap: jsc.createEl("div"),
box: jsc.createEl("div"),
boxS: jsc.createEl("div"), // shadow area
boxB: jsc.createEl("div"), // border
pad: jsc.createEl("div"),
padB: jsc.createEl("div"), // border
padM: jsc.createEl("div"), // mouse/touch area
padCanvas: jsc.createPadCanvas(),
cross: jsc.createEl("div"),
crossBY: jsc.createEl("div"), // border Y
crossBX: jsc.createEl("div"), // border X
crossLY: jsc.createEl("div"), // line Y
crossLX: jsc.createEl("div"), // line X
sld: jsc.createEl("div"), // slider
sldB: jsc.createEl("div"), // border
sldM: jsc.createEl("div"), // mouse/touch area
sldGrad: jsc.createSliderGradient(),
sldPtrS: jsc.createEl("div"), // slider pointer spacer
sldPtrIB: jsc.createEl("div"), // slider pointer inner border
sldPtrMB: jsc.createEl("div"), // slider pointer middle border
sldPtrOB: jsc.createEl("div"), // slider pointer outer border
asld: jsc.createEl("div"), // alpha slider
asldB: jsc.createEl("div"), // border
asldM: jsc.createEl("div"), // mouse/touch area
asldGrad: jsc.createASliderGradient(),
asldPtrS: jsc.createEl("div"), // slider pointer spacer
asldPtrIB: jsc.createEl("div"), // slider pointer inner border
asldPtrMB: jsc.createEl("div"), // slider pointer middle border
asldPtrOB: jsc.createEl("div"), // slider pointer outer border
pal: jsc.createEl("div"), // palette
btn: jsc.createEl("div"),
btnT: jsc.createEl("div"), // text
};
jsc.picker.pad.appendChild(jsc.picker.padCanvas.elm);
jsc.picker.padB.appendChild(jsc.picker.pad);
jsc.picker.cross.appendChild(jsc.picker.crossBY);
jsc.picker.cross.appendChild(jsc.picker.crossBX);
jsc.picker.cross.appendChild(jsc.picker.crossLY);
jsc.picker.cross.appendChild(jsc.picker.crossLX);
jsc.picker.padB.appendChild(jsc.picker.cross);
jsc.picker.box.appendChild(jsc.picker.padB);
jsc.picker.box.appendChild(jsc.picker.padM);
jsc.picker.sld.appendChild(jsc.picker.sldGrad.elm);
jsc.picker.sldB.appendChild(jsc.picker.sld);
jsc.picker.sldB.appendChild(jsc.picker.sldPtrOB);
jsc.picker.sldPtrOB.appendChild(jsc.picker.sldPtrMB);
jsc.picker.sldPtrMB.appendChild(jsc.picker.sldPtrIB);
jsc.picker.sldPtrIB.appendChild(jsc.picker.sldPtrS);
jsc.picker.box.appendChild(jsc.picker.sldB);
jsc.picker.box.appendChild(jsc.picker.sldM);
jsc.picker.asld.appendChild(jsc.picker.asldGrad.elm);
jsc.picker.asldB.appendChild(jsc.picker.asld);
jsc.picker.asldB.appendChild(jsc.picker.asldPtrOB);
jsc.picker.asldPtrOB.appendChild(jsc.picker.asldPtrMB);
jsc.picker.asldPtrMB.appendChild(jsc.picker.asldPtrIB);
jsc.picker.asldPtrIB.appendChild(jsc.picker.asldPtrS);
jsc.picker.box.appendChild(jsc.picker.asldB);
jsc.picker.box.appendChild(jsc.picker.asldM);
jsc.picker.box.appendChild(jsc.picker.pal);
jsc.picker.btn.appendChild(jsc.picker.btnT);
jsc.picker.box.appendChild(jsc.picker.btn);
jsc.picker.boxB.appendChild(jsc.picker.box);
jsc.picker.wrap.appendChild(jsc.picker.boxS);
jsc.picker.wrap.appendChild(jsc.picker.boxB);
jsc.picker.wrap.addEventListener(
"touchstart",
jsc.onPickerTouchStart,
jsc.isPassiveEventSupported ? { passive: false } : false
);
}
var p = jsc.picker;
var displaySlider = !!jsc.getSliderChannel(THIS);
var displayAlphaSlider = THIS.hasAlphaChannel();
var pickerDims = jsc.getPickerDims(THIS);
var crossOuterSize =
2 * THIS.pointerBorderWidth +
THIS.pointerThickness +
2 * THIS.crossSize;
var controlPadding = jsc.getControlPadding(THIS);
var borderRadius = Math.min(
THIS.borderRadius,
Math.round(THIS.padding * Math.PI)
); // px
var padCursor = "crosshair";
// wrap
p.wrap.className = "jscolor-wrap";
p.wrap.style.width = pickerDims.borderW + "px";
p.wrap.style.height = pickerDims.borderH + "px";
p.wrap.style.zIndex = THIS.zIndex;
// picker
p.box.className = "jscolor-picker";
p.box.style.width = pickerDims.paddedW + "px";
p.box.style.height = pickerDims.paddedH + "px";
// picker shadow
p.boxS.className = "jscolor-shadow";
jsc.setBorderRadius(p.boxS, borderRadius + "px");
// picker border
p.boxB.className = "jscolor-border";
p.boxB.style.border = THIS.borderWidth + "px solid";
p.boxB.style.borderColor = THIS.borderColor;
p.boxB.style.background = THIS.backgroundColor;
jsc.setBorderRadius(p.boxB, borderRadius + "px");
// IE hack:
// If the element is transparent, IE will trigger the event on the elements under it,
// e.g. on Canvas or on elements with border
p.padM.style.background = "rgba(255,0,0,.2)";
p.sldM.style.background = "rgba(0,255,0,.2)";
p.asldM.style.background = "rgba(0,0,255,.2)";
p.padM.style.opacity =
p.sldM.style.opacity =
p.asldM.style.opacity =
"0";
// pad
p.pad.style.position = "relative";
p.pad.style.width = THIS.width + "px";
p.pad.style.height = THIS.height + "px";
// pad - color spectrum (HSV and HVS)
p.padCanvas.draw(THIS.width, THIS.height, jsc.getPadYChannel(THIS));
// pad border
p.padB.style.position = "absolute";
p.padB.style.left = THIS.padding + "px";
p.padB.style.top = THIS.padding + "px";
p.padB.style.border = THIS.controlBorderWidth + "px solid";
p.padB.style.borderColor = THIS.controlBorderColor;
// pad mouse area
p.padM.style.position = "absolute";
p.padM.style.left = 0 + "px";
p.padM.style.top = 0 + "px";
p.padM.style.width =
THIS.padding +
2 * THIS.controlBorderWidth +
THIS.width +
controlPadding +
"px";
p.padM.style.height =
2 * THIS.controlBorderWidth + 2 * THIS.padding + THIS.height + "px";
p.padM.style.cursor = padCursor;
jsc.setData(p.padM, {
instance: THIS,
control: "pad",
});
// pad cross
p.cross.style.position = "absolute";
p.cross.style.left = p.cross.style.top = "0";
p.cross.style.width = p.cross.style.height = crossOuterSize + "px";
// pad cross border Y and X
p.crossBY.style.position = p.crossBX.style.position = "absolute";
p.crossBY.style.background = p.crossBX.style.background =
THIS.pointerBorderColor;
p.crossBY.style.width = p.crossBX.style.height =
2 * THIS.pointerBorderWidth + THIS.pointerThickness + "px";
p.crossBY.style.height = p.crossBX.style.width =
crossOuterSize + "px";
p.crossBY.style.left = p.crossBX.style.top =
Math.floor(crossOuterSize / 2) -
Math.floor(THIS.pointerThickness / 2) -
THIS.pointerBorderWidth +
"px";
p.crossBY.style.top = p.crossBX.style.left = "0";
// pad cross line Y and X
p.crossLY.style.position = p.crossLX.style.position = "absolute";
p.crossLY.style.background = p.crossLX.style.background =
THIS.pointerColor;
p.crossLY.style.height = p.crossLX.style.width =
crossOuterSize - 2 * THIS.pointerBorderWidth + "px";
p.crossLY.style.width = p.crossLX.style.height =
THIS.pointerThickness + "px";
p.crossLY.style.left = p.crossLX.style.top =
Math.floor(crossOuterSize / 2) -
Math.floor(THIS.pointerThickness / 2) +
"px";
p.crossLY.style.top = p.crossLX.style.left =
THIS.pointerBorderWidth + "px";
// slider
p.sld.style.overflow = "hidden";
p.sld.style.width = THIS.sliderSize + "px";
p.sld.style.height = THIS.height + "px";
// slider gradient
p.sldGrad.draw(THIS.sliderSize, THIS.height, "#000", "#000");
// slider border
p.sldB.style.display = displaySlider ? "block" : "none";
p.sldB.style.position = "absolute";
p.sldB.style.left =
THIS.padding +
THIS.width +
2 * THIS.controlBorderWidth +
2 * controlPadding +
"px";
p.sldB.style.top = THIS.padding + "px";
p.sldB.style.border = THIS.controlBorderWidth + "px solid";
p.sldB.style.borderColor = THIS.controlBorderColor;
// slider mouse area
p.sldM.style.display = displaySlider ? "block" : "none";
p.sldM.style.position = "absolute";
p.sldM.style.left =
THIS.padding +
THIS.width +
2 * THIS.controlBorderWidth +
controlPadding +
"px";
p.sldM.style.top = 0 + "px";
p.sldM.style.width =
THIS.sliderSize +
2 * controlPadding +
2 * THIS.controlBorderWidth +
(displayAlphaSlider
? 0
: Math.max(0, THIS.padding - controlPadding)) + // remaining padding to the right edge
"px";
p.sldM.style.height =
2 * THIS.controlBorderWidth + 2 * THIS.padding + THIS.height + "px";
p.sldM.style.cursor = "default";
jsc.setData(p.sldM, {
instance: THIS,
control: "sld",
});
// slider pointer inner and outer border
p.sldPtrIB.style.border = p.sldPtrOB.style.border =
THIS.pointerBorderWidth + "px solid " + THIS.pointerBorderColor;
// slider pointer outer border
p.sldPtrOB.style.position = "absolute";
p.sldPtrOB.style.left =
-(2 * THIS.pointerBorderWidth + THIS.pointerThickness) + "px";
p.sldPtrOB.style.top = "0";
// slider pointer middle border
p.sldPtrMB.style.border =
THIS.pointerThickness + "px solid " + THIS.pointerColor;
// slider pointer spacer
p.sldPtrS.style.width = THIS.sliderSize + "px";
p.sldPtrS.style.height = jsc.pub.sliderInnerSpace + "px";
// alpha slider
p.asld.style.overflow = "hidden";
p.asld.style.width = THIS.sliderSize + "px";
p.asld.style.height = THIS.height + "px";
// alpha slider gradient
p.asldGrad.draw(THIS.sliderSize, THIS.height, "#000");
// alpha slider border
p.asldB.style.display = displayAlphaSlider ? "block" : "none";
p.asldB.style.position = "absolute";
p.asldB.style.left =
THIS.padding +
THIS.width +
2 * THIS.controlBorderWidth +
controlPadding +
(displaySlider
? THIS.sliderSize +
3 * controlPadding +
2 * THIS.controlBorderWidth
: 0) +
"px";
p.asldB.style.top = THIS.padding + "px";
p.asldB.style.border = THIS.controlBorderWidth + "px solid";
p.asldB.style.borderColor = THIS.controlBorderColor;
// alpha slider mouse area
p.asldM.style.display = displayAlphaSlider ? "block" : "none";
p.asldM.style.position = "absolute";
p.asldM.style.left =
THIS.padding +
THIS.width +
2 * THIS.controlBorderWidth +
controlPadding +
(displaySlider
? THIS.sliderSize +
2 * controlPadding +
2 * THIS.controlBorderWidth
: 0) +
"px";
p.asldM.style.top = 0 + "px";
p.asldM.style.width =
THIS.sliderSize +
2 * controlPadding +
2 * THIS.controlBorderWidth +
Math.max(0, THIS.padding - controlPadding) + // remaining padding to the right edge
"px";
p.asldM.style.height =
2 * THIS.controlBorderWidth + 2 * THIS.padding + THIS.height + "px";
p.asldM.style.cursor = "default";
jsc.setData(p.asldM, {
instance: THIS,
control: "asld",
});
// alpha slider pointer inner and outer border
p.asldPtrIB.style.border = p.asldPtrOB.style.border =
THIS.pointerBorderWidth + "px solid " + THIS.pointerBorderColor;
// alpha slider pointer outer border
p.asldPtrOB.style.position = "absolute";
p.asldPtrOB.style.left =
-(2 * THIS.pointerBorderWidth + THIS.pointerThickness) + "px";
p.asldPtrOB.style.top = "0";
// alpha slider pointer middle border
p.asldPtrMB.style.border =
THIS.pointerThickness + "px solid " + THIS.pointerColor;
// alpha slider pointer spacer
p.asldPtrS.style.width = THIS.sliderSize + "px";
p.asldPtrS.style.height = jsc.pub.sliderInnerSpace + "px";
// palette
p.pal.className = "jscolor-palette";
p.pal.style.display = pickerDims.palette.rows ? "block" : "none";
p.pal.style.left = THIS.padding + "px";
p.pal.style.top =
2 * THIS.controlBorderWidth + 2 * THIS.padding + THIS.height + "px";
// palette's color samples
p.pal.innerHTML = "";
var chessboard = jsc.genColorPreviewCanvas("rgba(0,0,0,0)");
var si = 0; // color sample's index
for (var r = 0; r < pickerDims.palette.rows; r++) {
for (
var c = 0;
c < pickerDims.palette.cols && si < THIS._palette.length;
c++, si++
) {
var sampleColor = THIS._palette[si];
var sampleCssColor = jsc.rgbaColor.apply(null, sampleColor.rgba);
var sc = jsc.createEl("div"); // color sample's color
sc.style.width =
pickerDims.palette.cellW - 2 * THIS.controlBorderWidth + "px";
sc.style.height =
pickerDims.palette.cellH - 2 * THIS.controlBorderWidth + "px";
sc.style.backgroundColor = sampleCssColor;
var sw = jsc.createEl("div"); // color sample's wrap
sw.className = "jscolor-palette-sw";
sw.style.left =
(pickerDims.palette.cols <= 1
? 0
: Math.round(
10 *
(c *
((pickerDims.contentW - pickerDims.palette.cellW) /
(pickerDims.palette.cols - 1)))
) / 10) + "px";
sw.style.top =
r * (pickerDims.palette.cellH + THIS.paletteSpacing) + "px";
sw.style.border = THIS.controlBorderWidth + "px solid";
sw.style.borderColor = THIS.controlBorderColor;
if (sampleColor.rgba[3] !== null && sampleColor.rgba[3] < 1.0) {
// only create chessboard background if the sample has transparency
sw.style.backgroundImage =
"url('" + chessboard.canvas.toDataURL() + "')";
sw.style.backgroundRepeat = "repeat";
sw.style.backgroundPosition = "center center";
}
jsc.setData(sw, {
instance: THIS,
control: "palette-sw",
color: sampleColor,
});
sw.addEventListener("click", jsc.onPaletteSampleClick, false);
sw.appendChild(sc);
p.pal.appendChild(sw);
}
}
// the Close button
function setBtnBorder() {
var insetColors = THIS.controlBorderColor.split(/\s+/);
var outsetColor =
insetColors.length < 2
? insetColors[0]
: insetColors[1] +
" " +
insetColors[0] +
" " +
insetColors[0] +
" " +
insetColors[1];
p.btn.style.borderColor = outsetColor;
}
var btnPadding = 15; // px
p.btn.className = "jscolor-btn jscolor-btn-close";
p.btn.style.display = THIS.closeButton ? "block" : "none";
p.btn.style.left = THIS.padding + "px";
p.btn.style.bottom = THIS.padding + "px";
p.btn.style.padding = "0 " + btnPadding + "px";
p.btn.style.maxWidth =
pickerDims.contentW -
2 * THIS.controlBorderWidth -
2 * btnPadding +
"px";
p.btn.style.height = THIS.buttonHeight + "px";
p.btn.style.border = THIS.controlBorderWidth + "px solid";
setBtnBorder();
p.btn.style.color = THIS.buttonColor;
p.btn.onmousedown = function () {
THIS.hide();
};
p.btnT.style.display = "inline";
p.btnT.style.lineHeight = THIS.buttonHeight + "px";
p.btnT.innerText = THIS.closeText;
// reposition the pointers
redrawPad();
redrawSld();
redrawASld();
// If we are changing the owner without first closing the picker,
// make sure to first deal with the old owner
if (jsc.picker.owner && jsc.picker.owner !== THIS) {
jsc.removeClass(
jsc.picker.owner.targetElement,
jsc.pub.activeClassName
);
}
// Set a new picker owner
jsc.picker.owner = THIS;
// The redrawPosition() method needs picker.owner to be set, that's why we call it here,
// after setting the owner
jsc.redrawPosition();
if (p.wrap.parentNode !== THIS.container) {
THIS.container.appendChild(p.wrap);
}
jsc.addClass(THIS.targetElement, jsc.pub.activeClassName);
}
function redrawPad() {
// redraw the pad pointer
var yChannel = jsc.getPadYChannel(THIS);
var x = Math.round((THIS.channels.h / 360) * (THIS.width - 1));
var y = Math.round(
(1 - THIS.channels[yChannel] / 100) * (THIS.height - 1)
);
var crossOuterSize =
2 * THIS.pointerBorderWidth +
THIS.pointerThickness +
2 * THIS.crossSize;
var ofs = -Math.floor(crossOuterSize / 2);
jsc.picker.cross.style.left = x + ofs + "px";
jsc.picker.cross.style.top = y + ofs + "px";
// redraw the slider
switch (jsc.getSliderChannel(THIS)) {
case "s":
var rgb1 = jsc.HSV_RGB(THIS.channels.h, 100, THIS.channels.v);
var rgb2 = jsc.HSV_RGB(THIS.channels.h, 0, THIS.channels.v);
var color1 =
"rgb(" +
Math.round(rgb1[0]) +
"," +
Math.round(rgb1[1]) +
"," +
Math.round(rgb1[2]) +
")";
var color2 =
"rgb(" +
Math.round(rgb2[0]) +
"," +
Math.round(rgb2[1]) +
"," +
Math.round(rgb2[2]) +
")";
jsc.picker.sldGrad.draw(
THIS.sliderSize,
THIS.height,
color1,
color2
);
break;
case "v":
var rgb = jsc.HSV_RGB(THIS.channels.h, THIS.channels.s, 100);
var color1 =
"rgb(" +
Math.round(rgb[0]) +
"," +
Math.round(rgb[1]) +
"," +
Math.round(rgb[2]) +
")";
var color2 = "#000";
jsc.picker.sldGrad.draw(
THIS.sliderSize,
THIS.height,
color1,
color2
);
break;
}
// redraw the alpha slider
jsc.picker.asldGrad.draw(
THIS.sliderSize,
THIS.height,
THIS.toHEXString()
);
}
function redrawSld() {
var sldChannel = jsc.getSliderChannel(THIS);
if (sldChannel) {
// redraw the slider pointer
var y = Math.round(
(1 - THIS.channels[sldChannel] / 100) * (THIS.height - 1)
);
jsc.picker.sldPtrOB.style.top =
y -
(2 * THIS.pointerBorderWidth + THIS.pointerThickness) -
Math.floor(jsc.pub.sliderInnerSpace / 2) +
"px";
}
// redraw the alpha slider
jsc.picker.asldGrad.draw(
THIS.sliderSize,
THIS.height,
THIS.toHEXString()
);
}
function redrawASld() {
var y = Math.round((1 - THIS.channels.a) * (THIS.height - 1));
jsc.picker.asldPtrOB.style.top =
y -
(2 * THIS.pointerBorderWidth + THIS.pointerThickness) -
Math.floor(jsc.pub.sliderInnerSpace / 2) +
"px";
}
function isPickerOwner() {
return jsc.picker && jsc.picker.owner === THIS;
}
function onValueKeyDown(ev) {
if (jsc.eventKey(ev) === "Enter") {
if (THIS.valueElement) {
THIS.processValueInput(THIS.valueElement.value);
}
THIS.tryHide();
}
}
function onAlphaKeyDown(ev) {
if (jsc.eventKey(ev) === "Enter") {
if (THIS.alphaElement) {
THIS.processAlphaInput(THIS.alphaElement.value);
}
THIS.tryHide();
}
}
function onValueChange(ev) {
if (jsc.getData(ev, "internal")) {
return; // skip if the event was internally triggered by jscolor
}
var oldVal = THIS.valueElement.value;
THIS.processValueInput(THIS.valueElement.value); // this might change the value
jsc.triggerCallback(THIS, "onChange");
if (THIS.valueElement.value !== oldVal) {
// value was additionally changed -> let's trigger the change event again, even though it was natively dispatched
jsc.triggerInputEvent(THIS.valueElement, "change", true, true);
}
}
function onAlphaChange(ev) {
if (jsc.getData(ev, "internal")) {
return; // skip if the event was internally triggered by jscolor
}
var oldVal = THIS.alphaElement.value;
THIS.processAlphaInput(THIS.alphaElement.value); // this might change the value
jsc.triggerCallback(THIS, "onChange");
// triggering valueElement's onChange (because changing alpha changes the entire color, e.g. with rgba format)
jsc.triggerInputEvent(THIS.valueElement, "change", true, true);
if (THIS.alphaElement.value !== oldVal) {
// value was additionally changed -> let's trigger the change event again, even though it was natively dispatched
jsc.triggerInputEvent(THIS.alphaElement, "change", true, true);
}
}
function onValueInput(ev) {
if (jsc.getData(ev, "internal")) {
return; // skip if the event was internally triggered by jscolor
}
if (THIS.valueElement) {
THIS.fromString(THIS.valueElement.value, jsc.flags.leaveValue);
}
jsc.triggerCallback(THIS, "onInput");
// triggering valueElement's onInput
// (not needed, it was dispatched normally by the browser)
}
function onAlphaInput(ev) {
if (jsc.getData(ev, "internal")) {
return; // skip if the event was internally triggered by jscolor
}
if (THIS.alphaElement) {
THIS.fromHSVA(
null,
null,
null,
parseFloat(THIS.alphaElement.value),
jsc.flags.leaveAlpha
);
}
jsc.triggerCallback(THIS, "onInput");
// triggering valueElement's onInput (because changing alpha changes the entire color, e.g. with rgba format)
jsc.triggerInputEvent(THIS.valueElement, "input", true, true);
}
// let's process the DEPRECATED 'options' property (this will be later removed)
if (jsc.pub.options) {
// let's set custom default options, if specified
for (var opt in jsc.pub.options) {
if (jsc.pub.options.hasOwnProperty(opt)) {
try {
setOption(opt, jsc.pub.options[opt]);
} catch (e) {
console.warn(e);
}
}
}
}
// let's apply configuration presets
//
var presetsArr = [];
if (opts.preset) {
if (typeof opts.preset === "string") {
presetsArr = opts.preset.split(/\s+/);
} else if (Array.isArray(opts.preset)) {
presetsArr = opts.preset.slice(); // slice() to clone
} else {
console.warn("Unrecognized preset value");
}
}
// always use the 'default' preset. If it's not listed, append it to the end.
if (presetsArr.indexOf("default") === -1) {
presetsArr.push("default");
}
// let's apply the presets in reverse order, so that should there be any overlapping options,
// the formerly listed preset will override the latter
for (var i = presetsArr.length - 1; i >= 0; i -= 1) {
var pres = presetsArr[i];
if (!pres) {
continue; // preset is empty string
}
if (!jsc.pub.presets.hasOwnProperty(pres)) {
console.warn("Unknown preset: %s", pres);
continue;
}
for (var opt in jsc.pub.presets[pres]) {
if (jsc.pub.presets[pres].hasOwnProperty(opt)) {
try {
setOption(opt, jsc.pub.presets[pres][opt]);
} catch (e) {
console.warn(e);
}
}
}
}
// let's set specific options for this color picker
var nonProperties = [
// these options won't be set as instance properties
"preset",
];
for (var opt in opts) {
if (opts.hasOwnProperty(opt)) {
if (nonProperties.indexOf(opt) === -1) {
try {
setOption(opt, opts[opt]);
} catch (e) {
console.warn(e);
}
}
}
}
//
// Install the color picker on chosen element(s)
//
// Determine picker's container element
if (this.container === undefined) {
this.container = window.document.body; // default container is BODY element
} else {
// explicitly set to custom element
this.container = jsc.node(this.container);
}
if (!this.container) {
throw new Error(
"Cannot instantiate color picker without a container element"
);
}
// Fetch the target element
this.targetElement = jsc.node(targetElement);
if (!this.targetElement) {
// temporarily customized error message to help with migrating from versions prior to 2.2
if (
typeof targetElement === "string" &&
/^[a-zA-Z][\w:.-]*$/.test(targetElement)
) {
// targetElement looks like valid ID
var possiblyId = targetElement;
throw new Error(
"If '" +
possiblyId +
"' is supposed to be an ID, please use '#" +
possiblyId +
"' or any valid CSS selector."
);
}
throw new Error(
"Cannot instantiate color picker without a target element"
);
}
if (
this.targetElement.jscolor &&
this.targetElement.jscolor instanceof jsc.pub
) {
throw new Error("Color picker already installed on this element");
}
// link this instance with the target element
this.targetElement.jscolor = this;
jsc.addClass(this.targetElement, jsc.pub.className);
// register this instance
jsc.instances.push(this);
// if target is BUTTON
if (jsc.isButton(this.targetElement)) {
if (this.targetElement.type.toLowerCase() !== "button") {
// on buttons, always force type to be 'button', e.g. in situations the target <button> has no type
// and thus defaults to 'submit' and would submit the form when clicked
this.targetElement.type = "button";
}
if (jsc.isButtonEmpty(this.targetElement)) {
// empty button
// it is important to clear element's contents first.
// if we're re-instantiating color pickers on DOM that has been modified by changing page's innerHTML,
// we would keep adding more non-breaking spaces to element's content (because element's contents survive
// innerHTML changes, but picker instances don't)
jsc.removeChildren(this.targetElement);
// let's insert a non-breaking space
this.targetElement.appendChild(
window.document.createTextNode("\xa0")
);
// set min-width = previewSize, if not already greater
var compStyle = jsc.getCompStyle(this.targetElement);
var currMinWidth = parseFloat(compStyle["min-width"]) || 0;
if (currMinWidth < this.previewSize) {
jsc.setStyle(
this.targetElement,
{
"min-width": this.previewSize + "px",
},
this.forceStyle
);
}
}
}
// Determine the value element
if (this.valueElement === undefined) {
if (jsc.isTextInput(this.targetElement)) {
// for text inputs, default valueElement is targetElement
this.valueElement = this.targetElement;
} else {
// leave it undefined
}
} else if (this.valueElement === null) {
// explicitly set to null
// leave it null
} else {
// explicitly set to custom element
this.valueElement = jsc.node(this.valueElement);
}
// Determine the alpha element
if (this.alphaElement) {
this.alphaElement = jsc.node(this.alphaElement);
}
// Determine the preview element
if (this.previewElement === undefined) {
this.previewElement = this.targetElement; // default previewElement is targetElement
} else if (this.previewElement === null) {
// explicitly set to null
// leave it null
} else {
// explicitly set to custom element
this.previewElement = jsc.node(this.previewElement);
}
// valueElement
if (this.valueElement && jsc.isTextInput(this.valueElement)) {
// If the value element has onInput event already set, we need to detach it and attach AFTER our listener.
// otherwise the picker instance would still contain the old color when accessed from the onInput handler.
var valueElementOrigEvents = {
onInput: this.valueElement.oninput,
};
this.valueElement.oninput = null;
this.valueElement.addEventListener("keydown", onValueKeyDown, false);
this.valueElement.addEventListener("change", onValueChange, false);
this.valueElement.addEventListener("input", onValueInput, false);
// the original event listener must be attached AFTER our handler (to let it first set picker's color)
if (valueElementOrigEvents.onInput) {
this.valueElement.addEventListener(
"input",
valueElementOrigEvents.onInput,
false
);
}
this.valueElement.setAttribute("autocomplete", "off");
this.valueElement.setAttribute("autocorrect", "off");
this.valueElement.setAttribute("autocapitalize", "off");
this.valueElement.setAttribute("spellcheck", false);
}
// alphaElement
if (this.alphaElement && jsc.isTextInput(this.alphaElement)) {
this.alphaElement.addEventListener("keydown", onAlphaKeyDown, false);
this.alphaElement.addEventListener("change", onAlphaChange, false);
this.alphaElement.addEventListener("input", onAlphaInput, false);
this.alphaElement.setAttribute("autocomplete", "off");
this.alphaElement.setAttribute("autocorrect", "off");
this.alphaElement.setAttribute("autocapitalize", "off");
this.alphaElement.setAttribute("spellcheck", false);
}
// determine initial color value
//
var initValue = "FFFFFF";
if (this.value !== undefined) {
initValue = this.value; // get initial color from the 'value' property
} else if (this.valueElement && this.valueElement.value !== undefined) {
initValue = this.valueElement.value; // get initial color from valueElement's value
}
// determine initial alpha value
//
var initAlpha = undefined;
if (this.alpha !== undefined) {
initAlpha = "" + this.alpha; // get initial alpha value from the 'alpha' property
} else if (this.alphaElement && this.alphaElement.value !== undefined) {
initAlpha = this.alphaElement.value; // get initial color from alphaElement's value
}
// determine current format based on the initial color value
//
this._currentFormat = null;
if (["auto", "any"].indexOf(this.format.toLowerCase()) > -1) {
// format is 'auto' or 'any' -> let's auto-detect current format
var color = jsc.parseColorString(initValue);
this._currentFormat = color ? color.format : "hex";
} else {
// format is specified
this._currentFormat = this.format.toLowerCase();
}
// let's parse the initial color value and expose color's preview
this.processValueInput(initValue);
// let's also parse and expose the initial alpha value, if any
//
// Note: If the initial color value contains alpha value in it (e.g. in rgba format),
// this will overwrite it. So we should only process alpha input if there was initial
// alpha explicitly set, otherwise we could needlessly lose initial value's alpha
if (initAlpha !== undefined) {
this.processAlphaInput(initAlpha);
}
if (this.random) {
// randomize the initial color value
this.randomize.apply(
this,
Array.isArray(this.random) ? this.random : []
);
}
},
};
//================================
// Public properties and methods
//================================
//
// These will be publicly available via jscolor.<name> and JSColor.<name>
//
// class that will be set to elements having jscolor installed on them
jsc.pub.className = "jscolor";
// class that will be set to elements having jscolor active on them
jsc.pub.activeClassName = "jscolor-active";
// whether to try to parse the options string by evaluating it using 'new Function()'
// in case it could not be parsed with JSON.parse()
jsc.pub.looseJSON = true;
// presets
jsc.pub.presets = {};
// built-in presets
jsc.pub.presets["default"] = {}; // baseline for customization
jsc.pub.presets["light"] = {
// default color scheme
backgroundColor: "rgba(255,255,255,1)",
controlBorderColor: "rgba(187,187,187,1)",
buttonColor: "rgba(0,0,0,1)",
};
jsc.pub.presets["dark"] = {
backgroundColor: "rgba(51,51,51,1)",
controlBorderColor: "rgba(153,153,153,1)",
buttonColor: "rgba(240,240,240,1)",
};
jsc.pub.presets["small"] = {
width: 101,
height: 101,
padding: 10,
sliderSize: 14,
paletteCols: 8,
};
jsc.pub.presets["medium"] = {
width: 181,
height: 101,
padding: 12,
sliderSize: 16,
paletteCols: 10,
}; // default size
jsc.pub.presets["large"] = {
width: 271,
height: 151,
padding: 12,
sliderSize: 24,
paletteCols: 15,
};
jsc.pub.presets["thin"] = {
borderWidth: 1,
controlBorderWidth: 1,
pointerBorderWidth: 1,
}; // default thickness
jsc.pub.presets["thick"] = {
borderWidth: 2,
controlBorderWidth: 2,
pointerBorderWidth: 2,
};
// size of space in the sliders
jsc.pub.sliderInnerSpace = 3; // px
// transparency chessboard
jsc.pub.chessboardSize = 8; // px
jsc.pub.chessboardColor1 = "#666666";
jsc.pub.chessboardColor2 = "#999999";
// preview separator
jsc.pub.previewSeparator = [
"rgba(255,255,255,.65)",
"rgba(128,128,128,.65)",
];
// Initializes jscolor
jsc.pub.init = function () {
if (jsc.initialized) {
return;
}
// attach some necessary handlers
window.document.addEventListener(
"mousedown",
jsc.onDocumentMouseDown,
false
);
window.document.addEventListener("keyup", jsc.onDocumentKeyUp, false);
window.addEventListener("resize", jsc.onWindowResize, false);
window.addEventListener("scroll", jsc.onWindowScroll, false);
// append default CSS to HEAD
jsc.appendDefaultCss();
// install jscolor on current DOM
jsc.pub.install();
jsc.initialized = true;
// call functions waiting in the queue
while (jsc.readyQueue.length) {
var func = jsc.readyQueue.shift();
func();
}
};
// Installs jscolor on current DOM tree
jsc.pub.install = function (rootNode) {
var success = true;
try {
jsc.installBySelector("[data-jscolor]", rootNode);
} catch (e) {
success = false;
console.warn(e);
}
// for backward compatibility with DEPRECATED installation using class name
if (jsc.pub.lookupClass) {
try {
jsc.installBySelector(
"input." +
jsc.pub.lookupClass +
", " +
"button." +
jsc.pub.lookupClass,
rootNode
);
} catch (e) {}
}
return success;
};
// Registers function to be called as soon as jscolor is initialized (or immediately, if it already is).
//
jsc.pub.ready = function (func) {
if (typeof func !== "function") {
console.warn("Passed value is not a function");
return false;
}
if (jsc.initialized) {
func();
} else {
jsc.readyQueue.push(func);
}
return true;
};
// Triggers given input event(s) (e.g. 'input' or 'change') on all color pickers.
//
// It is possible to specify multiple events separated with a space.
// If called before jscolor is initialized, then the events will be triggered after initialization.
//
jsc.pub.trigger = function (eventNames) {
var triggerNow = function () {
jsc.triggerGlobal(eventNames);
};
if (jsc.initialized) {
triggerNow();
} else {
jsc.pub.ready(triggerNow);
}
};
// Hides current color picker box
jsc.pub.hide = function () {
if (jsc.picker && jsc.picker.owner) {
jsc.picker.owner.hide();
}
};
// Returns a data URL of a gray chessboard image that indicates transparency
jsc.pub.chessboard = function (color) {
if (!color) {
color = "rgba(0,0,0,0)";
}
var preview = jsc.genColorPreviewCanvas(color);
return preview.canvas.toDataURL();
};
// Returns a data URL of a gray chessboard image that indicates transparency
jsc.pub.background = function (color) {
var backgrounds = [];
// CSS gradient for background color preview
backgrounds.push(jsc.genColorPreviewGradient(color));
// data URL of generated PNG image with a gray transparency chessboard
var preview = jsc.genColorPreviewCanvas();
backgrounds.push(
[
"url('" + preview.canvas.toDataURL() + "')",
"left top",
"repeat",
].join(" ")
);
return backgrounds.join(", ");
};
//
// DEPRECATED properties and methods
//
// DEPRECATED. Use jscolor.presets.default instead.
//
// Custom default options for all color pickers, e.g. { hash: true, width: 300 }
jsc.pub.options = {};
// DEPRECATED. Use data-jscolor attribute instead, which installs jscolor on given element.
//
// By default, we'll search for all elements with class="jscolor" and install a color picker on them.
//
// You can change what class name will be looked for by setting the property jscolor.lookupClass
// anywhere in your HTML document. To completely disable the automatic lookup, set it to null.
//
jsc.pub.lookupClass = "jscolor";
// DEPRECATED. Use data-jscolor attribute instead, which installs jscolor on given element.
//
// Install jscolor on all elements that have the specified class name
jsc.pub.installByClassName = function () {
console.error(
'jscolor.installByClassName() is DEPRECATED. Use data-jscolor="" attribute instead of a class name.' +
jsc.docsRef
);
return false;
};
jsc.register();
return jsc.pub;
})(); // END jscolor
if (typeof window.jscolor === "undefined") {
window.jscolor = window.JSColor = jscolor;
}
// END jscolor code
return jscolor;
}); // END factory
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment