(function(root, factory) { if(typeof exports === 'object') { module.exports = factory(require('onecolor')); } else if(typeof define === 'function' && define.amd) { define(['onecolor'], factory); } else { root.colorjoe = factory(root.one.color); } }(this, function(onecolor) { /*! colorjoe - v0.9.2 - Juho Vepsalainen - MIT https://bebraw.github.com/colorjoe - 2013-09-24 */ /*! dragjs - v0.4.0 - Juho Vepsalainen - MIT https://bebraw.github.com/dragjs - 2013-07-17 */ var drag = (function() { function drag(elem, cbs) { if(!elem) { console.warn('drag is missing elem!'); return; } if(isTouch()) dragTemplate(elem, cbs, 'touchstart', 'touchmove', 'touchend'); else dragTemplate(elem, cbs, 'mousedown', 'mousemove', 'mouseup'); } function xyslider(o) { var twod = div(o['class'] || '', o.parent); var pointer = div('pointer', twod); div('shape shape1', pointer); div('shape shape2', pointer); div('bg bg1', twod); div('bg bg2', twod); drag(twod, attachPointer(o.cbs, pointer)); return { background: twod, pointer: pointer }; } function slider(o) { var oned = div(o['class'], o.parent); var pointer = div('pointer', oned); div('shape', pointer); div('bg', oned); drag(oned, attachPointer(o.cbs, pointer)); return { background: oned, pointer: pointer }; } drag.xyslider = xyslider; drag.slider = slider; return drag; function attachPointer(cbs, pointer) { var ret = {}; for(var n in cbs) ret[n] = wrap(cbs[n]); function wrap(fn) { return function(p) { p.pointer = pointer; fn(p); }; } return ret; } // move to elemutils lib? function div(klass, p) { return e('div', klass, p); } function e(type, klass, p) { var elem = document.createElement(type); if(klass) elem.className = klass; p.appendChild(elem); return elem; } // http://stackoverflow.com/questions/4817029/whats-the-best-way-to-detect-a-touch-screen-device-using-javascript function isTouch() { return typeof(window.ontouchstart) != 'undefined'; } function dragTemplate(elem, cbs, down, move, up) { var dragging = false; cbs = getCbs(cbs); var beginCb = cbs.begin; var changeCb = cbs.change; var endCb = cbs.end; on(elem, down, function(e) { dragging = true; var moveHandler = partial(callCb, changeCb, elem); function upHandler() { dragging = false; off(document, move, moveHandler); off(document, up, upHandler); callCb(endCb, elem, e); } on(document, move, moveHandler); on(document, up, upHandler); callCb(beginCb, elem, e); }); } function on(elem, evt, handler) { if(elem.addEventListener) elem.addEventListener(evt, handler, false); else if(elem.attachEvent) elem.attachEvent('on' + evt, handler); } function off(elem, evt, handler) { if(elem.removeEventListener) elem.removeEventListener(evt, handler, false); else if(elem.detachEvent) elem.detachEvent('on' + evt, handler); } function getCbs(cbs) { if(!cbs) { var initialOffset; var initialPos; return { begin: function(c) { initialOffset = {x: c.elem.offsetLeft, y: c.elem.offsetTop}; initialPos = c.cursor; }, change: function(c) { style(c.elem, 'left', (initialOffset.x + c.cursor.x - initialPos.x) + 'px'); style(c.elem, 'top', (initialOffset.y + c.cursor.y - initialPos.y) + 'px'); }, end: empty }; } else { return { begin: cbs.begin || empty, change: cbs.change || empty, end: cbs.end || empty }; } } // TODO: set draggable class (handy for fx) function style(e, prop, value) { e.style[prop] = value; } function empty() {} function callCb(cb, elem, e) { e.preventDefault(); var offset = findPos(elem); var width = elem.clientWidth; var height = elem.clientHeight; var cursor = { x: cursorX(elem, e), y: cursorY(elem, e) }; var x = (cursor.x - offset.x) / width; var y = (cursor.y - offset.y) / height; cb({ x: isNaN(x)? 0: x, y: isNaN(y)? 0: y, cursor: cursor, elem: elem, e: e }); } // http://stackoverflow.com/questions/4394747/javascript-curry-function function partial(fn) { var slice = Array.prototype.slice; var args = slice.apply(arguments, [1]); return function() { return fn.apply(null, args.concat(slice.apply(arguments))); }; } // http://www.quirksmode.org/js/findpos.html function findPos(e) { var x = 0; var y = 0; if(e.offsetParent) { do { x += e.offsetLeft; y += e.offsetTop; } while (e = e.offsetParent); } return {x: x, y: y}; } // http://javascript.about.com/library/blmousepos.htm function cursorX(elem, evt) { if(isFixed(elem)) { var bodyLeft = parseInt(getStyle(document.body, 'marginLeft'), 10) - calc(elem, 'scrollLeft') + window.pageXOffset + elem.style.marginLeft; return evt.clientX - bodyLeft; } if(evt.pageX) return evt.pageX; else if(evt.clientX) return evt.clientX + document.body.scrollLeft; } function cursorY(elem, evt) { if(isFixed(elem)) { var bodyTop = parseInt(getStyle(document.body, 'marginTop'), 10) - calc(elem, 'scrollTop') + window.pageYOffset + elem.style.marginTop; return evt.clientY - bodyTop; } if(evt.pageY) return evt.pageY; else if(evt.clientY) return evt.clientY + document.body.scrollTop; } function calc(element, prop) { var ret = 0; while (element.nodeName != "HTML") { ret += element[prop]; element = element.parentNode; } return ret; } // http://www.velocityreviews.com/forums/t942580-mouse-position-in-both-fixed-and-relative-positioning.html function isFixed(element) { // While not at the top of the document tree, or not fixed, keep // searching upwards. while (element.nodeName != "HTML" && usedStyle(element, "position") != "fixed") element = element.parentNode; if(element.nodeName == "HTML") return false; else return true; } // http://www.javascriptkit.com/dhtmltutors/dhtmlcascade4.shtml function getStyle(el, cssprop){ if (el.currentStyle) // IE return el.currentStyle[cssprop]; if(document.defaultView && document.defaultView.getComputedStyle) return document.defaultView.getComputedStyle(el, "")[cssprop]; //try and get inline style return el.style[cssprop]; } // Used style is to get around browsers' different methods of getting // the currently used (e.g. inline, class, etc) style for an element function usedStyle(element, property) { var s; // getComputedStyle is the standard way but some ie versions don't // support it if(window.getComputedStyle) s = window.getComputedStyle(element, null); else s = element.currentStyle; return s[property]; } })(); var div = partial(e, 'div'); function e(type, klass, p) { var elem = document.createElement(type); elem.className = klass; p.appendChild(elem); return elem; } // http://stackoverflow.com/questions/4394747/javascript-curry-function function partial(fn) { var slice = Array.prototype.slice; var args = slice.apply(arguments, [1]); return function() { return fn.apply(null, args.concat(slice.apply(arguments))); }; } function labelInput(klass, n, p, maxLen) { var d = div(klass, p); var l = label(n, d); var i = input('text', d, maxLen); return {label: l, input: i}; } function label(c, p) { var elem = e('label', '', p); elem.innerHTML = c; return elem; } function input(t, p, maxLen) { var elem = e('input', '', p); elem.type = t; if(maxLen) elem.maxLength = maxLen; return elem; } function X(p, a) {p.style.left = clamp(a * 100, 0, 100) + '%';} function Y(p, a) {p.style.top = clamp(a * 100, 0, 100) + '%';} function BG(e, c) {e.style.background = c;} function clamp(a, minValue, maxValue) { return Math.min(Math.max(a, minValue), maxValue); } var utils = { clamp: clamp, e: e, div: div, partial: partial, labelInput: labelInput, X: X, Y: Y, BG: BG }; function currentColor(p) { var e1 = utils.div('currentColorContainer', p); var e = utils.div('currentColor', e1); return { change: function(col) { utils.BG(e, col.cssa()); } }; } function fields(p, joe, o) { var cs = o.space; var fac = o.limit || 255; var fix = o.fix >= 0? o.fix: 0; var inputLen = ('' + fac).length + fix; inputLen = fix? inputLen + 1: inputLen; var initials = cs.split(''); var useAlpha = cs[cs.length - 1] == 'A'; cs = useAlpha? cs.slice(0, -1): cs; if(['RGB', 'HSL', 'HSV', 'CMYK'].indexOf(cs) < 0) return console.warn('Invalid field names', cs); var c = utils.div('colorFields', p); var elems = initials.map(function(n, i) { n = n.toLowerCase(); var e = utils.labelInput('color ' + n, n, c, inputLen); e.input.onkeyup = update; return {name: n, e: e}; }); function update() { var col = [cs]; elems.forEach(function(o) {col.push(o.e.input.value / fac);}); if(!useAlpha) col.push(joe.getAlpha()); joe.set(col); } return { change: function(col) { elems.forEach(function(o) { o.e.input.value = (col[o.name]() * fac).toFixed(fix); }); } }; } function alpha(p, joe) { var e = drag.slider({ parent: p, 'class': 'oned alpha', cbs: { begin: change, change: change } }); function change(p) { var val = utils.clamp(p.y, 0, 1); utils.Y(p.pointer, val); joe.setAlpha(1 - val); } return { change: function(col) { utils.Y(e.pointer, 1 - col.alpha()); } }; } function hex(p, joe, o) { var e = utils.labelInput('hex', o.label || '', p, 7); e.input.value = '#'; e.input.onkeyup = function(elem) { var key = elem.keyCode || elem.which; var val = elem.target.value; val = val[0] == '#'? val: '#' + val; val = pad(val, 7, '0'); if(key == 13) joe.set(val); }; e.input.onblur = function(elem) { joe.set(elem.target.value); }; return { change: function(col) { e.input.value = e.input.value[0] == '#'? '#': ''; e.input.value += col.hex().slice(1); } }; } function close(p, joe, o) { var elem = utils.e('a', o['class'] || 'close', p); elem.href = '#'; elem.innerHTML = o.label || 'Close'; elem.onclick = function(e) { e.preventDefault(); joe.hide(); }; } function pad(a, n, c) { var ret = a; for(var i = a.length, len = n; i < n; i++) ret += c; return ret; } var extras = { currentColor: currentColor, fields: fields, hex: hex, alpha: alpha, close: close }; var colorjoe = function(cbs) { if(!all(isFunction, [cbs.init, cbs.xy, cbs.z])) return console.warn('colorjoe: missing cb'); return function(element, initialColor, extras) { return setup({ e: element, color: initialColor, cbs: cbs, extras: extras }); }; }; /* pickers */ colorjoe.rgb = colorjoe({ init: function(col, xy, z) { var ret = onecolor(col).hsl(); this.xy(ret, {x: ret.saturation(), y: 1 - ret.value()}, xy, z); this.z(ret, ret.hue(), xy, z); return ret; }, xy: function(col, p, xy, z) { utils.X(xy.pointer, p.x); utils.Y(xy.pointer, p.y); return col.saturation(p.x).value(1 - p.y); }, z: function(col, v, xy, z) { utils.Y(z.pointer, v); RGB_BG(xy.background, v); return col.hue(v); } }); colorjoe.hsl = colorjoe({ init: function(col, xy, z) { var ret = onecolor(col).hsl(); this.xy(ret, {x: ret.hue(), y: 1 - ret.saturation()}, xy, z); this.z(ret, 1 - ret.lightness(), xy, z); return ret; }, xy: function(col, p, xy, z) { utils.X(xy.pointer, p.x); utils.Y(xy.pointer, p.y); RGB_BG(z.background, p.x); return col.hue(p.x).saturation(1 - p.y); }, z: function(col, v, xy, z) { utils.Y(z.pointer, v); return col.lightness(1 - v); } }); colorjoe._extras = {}; colorjoe.registerExtra = function(name, fn) { if(name in colorjoe._extras) console.warn('Extra "' + name + '"has been registered already!'); colorjoe._extras[name] = fn; }; for(var k in extras) { colorjoe.registerExtra(k, extras[k]); } function RGB_BG(e, h) { utils.BG(e, new onecolor.HSV(h, 1, 1).cssa()); } function setup(o) { if(!o.e) return console.warn('colorjoe: missing element'); var e = isString(o.e)? document.getElementById(o.e): o.e; e.className = 'colorPicker'; var cbs = o.cbs; var xy = drag.xyslider({ parent: e, 'class': 'twod', cbs: { begin: changeXY, change: changeXY, end: done } }); function changeXY(p) { col = cbs.xy(col, { x: utils.clamp(p.x, 0, 1), y: utils.clamp(p.y, 0, 1) }, xy, z); changed(); } var z = drag.slider({ parent: e, 'class': 'oned', cbs: { begin: changeZ, change: changeZ, end: done } }); function changeZ(p) { col = cbs.z(col, utils.clamp(p.y, 0, 1), xy, z); changed(); } // Initial color var previous = getColor(o.color); var col = cbs.init(previous, xy, z); var listeners = {change: [], done: []}; function changed(skip) { skip = isArray(skip)? skip: []; var li = listeners.change; var v; for(var i = 0, len = li.length; i < len; i++) { v = li[i]; if(skip.indexOf(v.name) == -1) v.fn(col); } } function done() { // Do not call done callback if the color did not change if (previous.equals(col)) return; for(var i = 0, len = listeners.done.length; i < len; i++) { listeners.done[i].fn(col); } previous = col; } var ob = { e: e, update: function(skip) { changed(skip); return this; }, hide: function() { e.style.display = 'none'; return this; }, show: function() { e.style.display = ''; return this; }, get: function() { return col; }, set: function(c) { var oldCol = this.get(); col = cbs.init(getColor(c), xy, z); if(!oldCol.equals(col)) this.update(); return this; }, getAlpha: function() { return col.alpha(); }, setAlpha: function(v) { col = col.alpha(v); this.update(); return this; }, on: function(evt, cb, name) { if(evt == 'change' || evt == 'done') { listeners[evt].push({name: name, fn: cb}); } else console.warn('Passed invalid evt name "' + evt + '" to colorjoe.on'); return this; }, removeAllListeners: function(evt) { if (evt) { delete listeners[evt]; } else { for(var key in listeners) { delete listeners[key]; } } return this; } }; setupExtras(e, ob, o.extras); changed(); return ob; } function getColor(c) { if(!isDefined(c)) return onecolor('#000'); if(c.isColor) return c; var ret = onecolor(c); if(ret) return ret; if(isDefined(c)) console.warn('Passed invalid color to colorjoe, using black instead'); return onecolor('#000'); } function setupExtras(p, joe, extras) { if(!extras) return; var c = utils.div('extras', p); var cbs; var name; var params; extras.forEach(function(e, i) { if(isArray(e)) { name = e[0]; params = e.length > 1? e[1]: {}; } else { name = e; params = {}; } extra = name in colorjoe._extras? colorjoe._extras[name]: null; if(extra) { cbs = extra(c, extraProxy(joe, name + i), params); for(var k in cbs) joe.on(k, cbs[k], name); } }); } function extraProxy(joe, name) { var ret = copy(joe); ret.update = function() { joe.update([name]); }; return ret; } function copy(o) { // returns a shallow copy var ret = {}; for(var k in o) { ret[k] = o[k]; } return ret; } function all(cb, a) {return a.map(cb).filter(id).length == a.length;} function isArray(o) { return Object.prototype.toString.call(o) === "[object Array]"; } function isString(o) {return typeof(o) === 'string';} function isDefined(input) {return typeof input !== "undefined";} function isFunction(input) {return typeof input === "function";} function id(a) {return a;} return colorjoe; }));