Last active
March 24, 2024 19:40
-
-
Save Oscar0159/262dd340a113c1c8ca43e0a01f92649f to your computer and use it in GitHub Desktop.
df2profiler map drawer
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==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